이 가이드의 목표는 다음과 같습니다.
- 바이너리 형식 설명 파일의 구조와 문법을 알아봅니다.
- 바이너리 형식 설명이 ISA 설명과 일치하는 방식을 알아봅니다.
- RiscV RV32I 명령어 하위 집합의 바이너리 설명을 작성합니다.
개요
RiscV 바이너리 명령 인코딩
바이너리 명령 인코딩은 마이크로프로세서에서 실행할 명령을 인코딩하는 표준 방법입니다. 일반적으로 실행 파일(일반적으로 ELF 형식)에 저장됩니다. 명령어는 고정 너비 또는 가변 너비일 수 있습니다.
일반적으로 명령어는 인코딩된 명령어 유형에 맞게 맞춤설정된 각 형식을 사용하여 소수의 인코딩 형식을 사용합니다. 예를 들어 레지스터-레지스터 명령어는 사용 가능한 명령 코드 수를 최대화하는 형식을 사용할 수 있지만 레지스터-즉시 명령어는 인코딩할 수 있는 즉시 값의 크기를 늘리기 위해 사용 가능한 명령 코드 수를 희생하는 다른 형식을 사용합니다. 브랜치 및 점프 명령어는 거의 항상 더 큰 오프셋이 있는 브랜치를 지원하기 위해 즉시 크기를 최대화하는 형식을 사용합니다.
RiscV 시뮬레이터에서 디코딩하려는 명령어에서 사용하는 명령어 형식은 다음과 같습니다.
레지스터-레지스터 명령어에 사용되는 R 유형 형식:
31..25 | 2024년 2월 24일 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | rs2 | rs1 | func3 | rd | 명령 코드 |
레지스터 즉시 명령어, 로드 명령어, jalr
명령어에 사용되는 I 유형 형식, 12비트 즉시.
31..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | rd | 명령 코드 |
즉시 명령어와 함께 이동하는 데 사용되는 특수 I 유형 형식, 5비트 즉시:
2025년 31월 25일 | 2024년 2월 24일 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | uimm5 | rs1 | func3 | rd | 명령 코드 |
U-Type 형식, 긴 즉각적인 명령 (lui
, auipc
), 20비트 즉시:
2012년 12월 31일 | 11..7 | 6..0 |
---|---|---|
20 | 5 | 7 |
uimm20 | rd | 명령 코드 |
B-Type 형식, 조건부 브랜치에 사용, 12비트 즉시 실행.
31 | 2025년 30월 25일 | 24..20 | 19..15 | 2012년 14월 14일 | 11..8 | 7 | 6..0 |
---|---|---|---|---|---|---|---|
1 | 6 | 5 | 5 | 3 | 4 | 1 | 7 |
imm | imm | rs2 | rs1 | func3 | imm | imm | 명령 코드 |
J 유형 형식, jal
명령에 사용됨, 20비트 즉시.
31 | 30..21 | 20 | 2012년 19월 12일 | 11..7 | 6..0 |
---|---|---|---|---|---|
1 | 10 | 1 | 8 | 5 | 7 |
imm | imm | imm | imm | rd | 명령 코드 |
저장 명령어에 사용되는 S-Type 형식, 12비트 즉시.
31..25 | 2024년 2월 24일 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
imm | rs2 | rs1 | func3 | imm | 명령 코드 |
이러한 형식에서 알 수 있듯이 모든 명령어의 길이는 32비트이며 각 형식의 하위 7비트는 opcode 필드입니다. 또한 여러 형식의 즉시 값 크기는 동일하지만 비트는 명령어의 서로 다른 부분에서 가져옵니다. 나중에 보겠지만 바이너리 디코더 사양 형식은 이를 표현할 수 있습니다.
바이너리 인코딩 설명
명령어의 바이너리 인코딩은 바이너리 형식(.bin_fmt
) 설명 파일로 표현됩니다. 바이너리 형식 명령 디코더가 생성될 수 있도록 ISA에서 명령어의 바이너리 인코딩을 설명합니다. 생성된 디코더는 이전 튜토리얼에 설명된 ISA 인코딩에 관계없는 디코더에 필요한 정보를 제공하기 위해 opcode를 결정하고 피연산자 및 즉시 필드의 값을 추출합니다.
이 튜토리얼에서는 작은 'Hello World' 프로그램에 사용된 명령어를 시뮬레이션하는 데 필요한 RiscV32I 명령어의 하위 집합에 관한 바이너리 인코딩 설명 파일을 작성합니다. RiscV ISA에 관한 자세한 내용은 Risc-V 사양{.external}을 참고하세요.
먼저 riscv_bin_decoder/riscv32i.bin_fmt
파일을 엽니다.
파일의 콘텐츠는 여러 섹션으로 나뉩니다.
첫 번째는 decoder
정의입니다.
decoder RiscV32I {
// The namespace in which code will be generated.
namespace mpact::sim::codelab;
// The name (including any namespace qualifiers) of the opcode enum type.
opcode_enum = "OpcodeEnum";
// Include files specific to this decoder.
includes {
#include "riscv_isa_decoder/solution/riscv32i_decoder.h"
}
// Instruction groups for which to generate decode functions.
RiscVInst32;
};
디코더 정의에서는 디코더 RiscV32I
의 이름과 4가지 추가 정보를 지정합니다. 첫 번째는 namespace
로, 생성된 코드가 배치될 네임스페이스를 정의합니다. 두 번째는 opcode_enum
로, ISA 디코더에 의해 생성된 opcode 열거형 유형이 생성된 코드 내에서 어떻게 참조되어야 하는지 지정합니다. 세 번째로, includes {}
는 이 디코더에 관해 생성된 코드에 필요한 포함 파일을 지정합니다.
이 경우에는 이전 튜토리얼의 ISA 디코더에서 생성한 파일입니다.
추가 포함 파일은 전역 범위의 includes {}
정의에서 지정할 수 있습니다. 이는 여러 디코더가 정의되어 있고 모두 동일한 파일 중 일부를 포함해야 하는 경우에 유용합니다. 네 번째는 디코더가 생성되는 명령어를 구성하는 명령어 그룹의 이름 목록입니다. 이 경우에는 하나(RiscVInst32
)만 있습니다.
다음은 세 가지 형식 정의입니다. 이는 파일에 이미 정의된 명령어에서 사용하는 32비트 명령어 단어의 다양한 명령어 형식을 나타냅니다.
// The generic RiscV 32 bit instruction format.
format Inst32Format[32] {
fields:
unsigned bits[25];
unsigned opcode[7];
};
// RiscV 32 bit instruction format used by a number of instructions
// needing a 12 bit immediate, including CSR instructions.
format IType[32] : Inst32Format {
fields:
signed imm12[12];
unsigned rs1[5];
unsigned func3[3];
unsigned rd[5];
unsigned opcode[7];
};
// RiscV instruction format used by fence instructions.
format Fence[32] : Inst32Format {
fields:
unsigned fm[4];
unsigned pred[4];
unsigned succ[4];
unsigned rs1[5];
unsigned func3[3];
unsigned rd[5];
unsigned opcode[7];
};
첫 번째는 bits
(25비트 너비) 및 opcode
(7비트 너비)라는 두 필드가 있는 Inst32Format
라는 32비트 너비 명령 형식을 정의합니다. 각 필드는 unsigned
입니다. 즉, 값이 추출되어 C++ 정수 유형에 배치될 때 0으로 확장됩니다. 비트 필드의 너비 합계는 형식의 너비와 같아야 합니다. 일치하지 않는 경우 도구에서 오류가
발생합니다. 이 형식은 다른 형식에서 파생되지 않으므로 최상위 형식으로 간주됩니다.
두 번째는 Inst32Format
에서 파생된 32비트 명령 형식인 IType
를 정의하여 두 형식을 연결합니다. 이 형식에는 imm12
, rs1
, func3
, rd
, opcode
의 5가지 필드가 포함됩니다. imm12
필드는 signed
입니다. 즉, 값이 추출되어 C++ 정수 유형에 배치될 때 값이 부호 확장됩니다. IType.opcode
는 모두 동일한 signed/unsigned 속성을 가지며 Inst32Format.opcode
와 동일한 명령어 단어 비트를 참조합니다.
세 번째 형식은 이미 지정된 명령어인 fence
명령어에서만 사용되는 맞춤 형식입니다. 이 명령어는 이 튜토리얼에서 신경 쓸 필요가 없습니다.
핵심사항: 동일한 비트를 나타내고 signed/unsigned 속성이 동일한 한 관련 형식의 필드 이름을 재사용합니다.
riscv32i.bin_fmt
의 형식 정의 뒤에는 명령 그룹 정의가 나옵니다. 명령 그룹의 모든 명령은 동일한 비트 길이를 가져야 하며 동일한 최상위 명령 형식에서 (간접적으로) 파생되는 형식을 사용합니다. ISA에 길이가 다른 명령이 있을 수 있는 경우, 각 길이에 다른 명령 그룹이 사용됩니다. 또한 타겟 ISA 디코딩이 Arm 및 Thumb 명령어와 같은 실행 모드에 종속되는 경우 각 모드에 별도의 명령어 그룹이 필요합니다. bin_fmt
파서는 각 명령어 그룹에 대한 바이너리 디코더를 생성합니다.
instruction group RiscV32I[32] "OpcodeEnum" : Inst32Format {
fence : Fence : func3 == 0b000, opcode == 0b000'1111;
csrs : IType : func3 == 0b010, rs1 != 0, opcode == 0b111'0011;
csrw_nr : IType : func3 == 0b001, rd == 0, opcode == 0b111'0011;
csrs_nw : IType : func3 == 0b010, rs1 == 0, opcode == 0b111'0011;
};
명령 그룹은 이름 RiscV32I
, 너비 [32]
, 사용할 opcode 열거형 유형의 이름 "OpcodeEnum"
, 기본 명령 형식을 정의합니다. opcode 열거형 유형은 ISA 디코더 튜토리얼에서 다룬 형식 독립 명령 디코더에서 생성한 것과 동일해야 합니다.
각 명령 인코딩 설명은 다음 세 부분으로 구성됩니다.
- opcode 이름입니다. 이 두 가지가 함께 작동하려면 명령 디코더 설명에 사용된 것과 동일해야 합니다.
- 명령 코드에 사용할 명령 형식입니다. 이는 마지막 부분에서 비트필드 참조를 충족하는 데 사용되는 형식입니다.
- 쉼표로 구분된 비트 필드 제약 조건 목록(
==
,!=
,<
,<=
,>
,>=
)으로, 모두 참이어야만 opcode가 명령어 단어와 일치합니다.
.bin_fmt
파서는 이 모든 정보를 사용하여 다음과 같은 디코더를 빌드합니다.
- 모든 형식의 각 비트 필드에 적절한 추출 함수 (부호 있음/부호 없음)를 제공합니다. 추출기 함수는 형식 이름의 뱀 문자 대소문자 변형으로 이름이 지정된 네임스페이스에 배치됩니다. 예를 들어 형식
IType
의 추출기 함수는i_type
네임스페이스에 배치됩니다. 각 추출기 함수는inline
로 선언되고 형식의 너비를 보유하는 가장 좁은uint_t
유형을 사용하며 추출된 필드 너비를 보유하는 가장 좁은int_t
(부호가 있는 경우) 또는uint_t
(부호가 없는 경우) 유형을 반환합니다. 예:
inline uint8_t ExtractOpcode(uint32_t value) {
return value & 0x7f;
}
- 각 명령 그룹의 디코딩 함수입니다.
OpcodeEnum
유형의 값을 반환하고 명령 그룹 형식의 너비를 보유하는 가장 좁은uint_t
유형을 사용합니다.
초기 빌드 실행
디렉터리를 riscv_bin_decoder
로 변경하고 다음 명령어를 사용하여 프로젝트를 빌드합니다.
$ cd riscv_bin_decoder
$ bazel build :all
이제 디렉터리를 저장소 루트로 다시 변경한 다음 생성된 소스를 살펴보겠습니다. 이렇게 하려면 디렉터리를 bazel-out/k8-fastbuild/bin/riscv_bin_decoder
로 변경합니다(x86 호스트에 있다고 가정 - 다른 호스트의 경우 k8-fastbuild가 다른 문자열임).
$ cd ..
$ cd bazel-out/k8-fastbuild/bin/riscv_bin_decoder
riscv32i_bin_decoder.h
riscv32i_bin_decoder.cc
생성된 헤더 파일 (.h)
riscv32i_bin_decoder.h
. 파일의 첫 번째 부분에는 표준 불고용 구문 가드, 포함 파일, 네임스페이스 선언이 포함됩니다. 그 뒤에 internal
네임스페이스에 템플릿화된 도우미 함수가 있습니다. 이 함수는 64비트 C++ 정수에 맞지 않을 만큼 긴 형식에서 비트 필드를 추출하는 데 사용됩니다.
#ifndef RISCV32I_BIN_DECODER_H
#define RISCV32I_BIN_DECODER_H
#include <iostream>
#include <cstdint>
#include "third_party/absl/functional/any_invocable.h"
#include "learning/brain/research/mpact/sim/codelab/riscv_isa_decoder/solution/riscv32i_decoder.h"
namespace mpact {
namespace sim {
namespace codelab {
namespace internal {
template <typename T>
static inline T ExtractBits(const uint8_t *data, int data_size,
int bit_index, int width) {
if (width == 0) return 0;
int byte_pos = bit_index >> 3;
int end_byte = (bit_index + width - 1) >> 3;
int start_bit = bit_index & 0x7;
// If it is only from one byte, extract and return.
if (byte_pos == end_byte) {
uint8_t mask = 0xff >> start_bit;
return (mask & data[byte_pos]) >> (8 - start_bit - width);
}
// Extract from the first byte.
T val = 0;
val = data[byte_pos++] & 0xff >> start_bit;
int remainder = width - (8 - start_bit);
while (remainder >= 8) {
val = (val << 8) | data[byte_pos++];
remainder -= 8;
}
// Extract any remaining bits.
if (remainder > 0) {
val <<= remainder;
int shift = 8 - remainder;
uint8_t mask = 0b1111'1111 << shift;
val |= (data[byte_pos] & mask) >> shift;
}
return val;
}
} // namespace internal
첫 번째 섹션 뒤에는 riscv32i.bin_fmt
파일의 format
선언마다 하나씩 세 개의 네임스페이스가 있습니다.
namespace fence {
...
} // namespace fence
namespace i_type {
...
} // namespace i_type
namespace inst32_format {
...
} // namespace inst32_format
이러한 각 네임스페이스 내에서 해당 형식의 각 비트 필드에 관한 inline
비트 필드 추출 함수가 정의됩니다. 또한 기본 형식은 1) 필드 이름이 단일 필드 이름에서만 발생하거나 2) 필드 이름이 각 형식에서 동일한 유형 필드 (부호 있는/부호 없는 위치 및 비트 위치)를 참조하는 하위 형식에서 추출 함수를 복제합니다. 이렇게 하면 최상위 형식 네임스페이스의 함수를 사용하여 동일한 비트를 설명하는 비트 필드를 추출할 수 있습니다.
i_type
네임스페이스의 함수는 아래와 같습니다.
namespace i_type {
inline uint8_t ExtractFunc3(uint32_t value) {
return (value >> 12) & 0x7;
}
inline int16_t ExtractImm12(uint32_t value) {
int16_t result = ( (value >> 20) & 0xfff) << 4;
result = result >> 4;
return result;
}
inline uint8_t ExtractOpcode(uint32_t value) {
return value & 0x7f;
}
inline uint8_t ExtractRd(uint32_t value) {
return (value >> 7) & 0x1f;
}
inline uint8_t ExtractRs1(uint32_t value) {
return (value >> 15) & 0x1f;
}
} // namespace i_type
마지막으로 명령 그룹 RiscVInst32
의 디코더 함수의 함수 선언이 선언됩니다. 32비트 부호 없는 값을 명령어 워드의 값으로 사용하고 일치하는 OpcodeEnum
열거형 클래스 멤버를 반환하거나 일치하는 항목이 없는 경우 OpcodeEnum::kNone
를 반환합니다.
OpcodeEnum DecodeRiscVInst32(uint32_t inst_word);
생성된 소스 파일(.cc)
이제 riscv32i_bin_decoder.cc
를 엽니다. 파일의 첫 번째 부분에는 #include
및 네임스페이스 선언이 포함되며 그 뒤에 디코더 함수 선언이 나옵니다.
#include "riscv32i_bin_decoder.h"
namespace mpact {
namespace sim {
namespace codelab {
OpcodeEnum DecodeRiscVInst32None(uint32_t);
OpcodeEnum DecodeRiscVInst32_0(uint32_t inst_word);
OpcodeEnum DecodeRiscVInst32_0_3(uint32_t inst_word);
OpcodeEnum DecodeRiscVInst32_0_3c(uint32_t inst_word);
OpcodeEnum DecodeRiscVInst32_0_5c(uint32_t inst_word);
DecodeRiscVInst32None
는 빈 디코딩 작업(OpcodeEnum::kNone
을 반환하는 작업)에 사용됩니다. 나머지 세 함수는 생성된 디코더를 구성합니다. 전체 디코더는 계층 구조로 작동합니다. 명령어 단어의 비트 집합은 최상위 수준의 명령어 또는 명령어 그룹을 구분하도록 계산됩니다. 비트가 연속적일 필요는 없습니다. 비트 수는 두 번째 수준의 디코더 함수로 채워진 참고표의 크기를 결정합니다. 이는 파일의 다음 섹션에서 확인할 수 있습니다.
absl::AnyInvocable<OpcodeEnum(uint32_t)> parse_group_RiscVInst32_0[kParseGroupRiscVInst32_0_Size] = {
&DecodeRiscVInst32None, &DecodeRiscVInst32None,
&DecodeRiscVInst32None, &DecodeRiscVInst32_0_3,
&DecodeRiscVInst32None, &DecodeRiscVInst32None,
...
&DecodeRiscVInst32None, &DecodeRiscVInst32None,
&DecodeRiscVInst32None, &DecodeRiscVInst32None,
&DecodeRiscVInst32_0_3c, &DecodeRiscVInst32None,
...
};
마지막으로 디코더 함수가 정의됩니다.
OpcodeEnum DecodeRiscVInst32None(uint32_t) {
return OpcodeEnum::kNone;
}
OpcodeEnum DecodeRiscVInst32_0(uint32_t inst_word) {
if ((inst_word & 0x4003) != 0x3) return OpcodeEnum::kNone;
uint32_t index;
index = (inst_word >> 2) & 0x1f;
index |= (inst_word >> 7) & 0x60;
return parse_group_RiscVInst32_0[index](inst_word);
}
OpcodeEnum DecodeRiscVInst32_0_3(uint32_t inst_word) {
return OpcodeEnum::kFence;
}
OpcodeEnum DecodeRiscVInst32_0_3c(uint32_t inst_word) {
if ((inst_word & 0xf80) != 0x0) return OpcodeEnum::kNone;
return OpcodeEnum::kCsrwNr;
}
OpcodeEnum DecodeRiscVInst32_0_5c(uint32_t inst_word) {
uint32_t rs1_value = (inst_word >> 15) & 0x1f;
if (rs1_value != 0x0)
return OpcodeEnum::kCsrs;
if (rs1_value == 0x0)
return OpcodeEnum::kCsrsNw;
return OpcodeEnum::kNone;
}
OpcodeEnum DecodeRiscVInst32(uint32_t inst_word) {
OpcodeEnum opcode;
opcode = DecodeRiscVInst32_0(inst_word);
return opcode;
}
명령이 4개만 정의된 경우에는 단일 수준의 디코딩과 매우 희소한 참고표만 있습니다. 명령어가 추가되면 디코더의 구조가 변경되고 디코더 테이블 계층 구조의 수준 수가 증가할 수 있습니다.
레지스터 레지스터 ALU 명령 추가
이제 riscv32i.bin_fmt
파일에 새 안내를 추가합니다. 첫 번째 명령 그룹은 add
, and
등과 같은 레지스터-레지스터 ALU 명령입니다. RiscV32에서는 모두 R 유형 바이너리 명령 형식을 사용합니다.
31..25 | 2024년 2월 24일 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | rs2 | rs1 | func3 | rd | 명령 코드 |
가장 먼저 해야 할 일은 형식을 추가하는 것입니다. 좋아하는 편집기에서 riscv32i.bin_fmt
를 엽니다. Inst32Format
바로 뒤에 Inst32Format
에서 파생된 RType
라는 형식을 추가합니다. RType
의 모든 비트 필드는 unsigned
입니다. 위의 표에서 이름, 비트 너비, 순서(왼쪽에서 오른쪽)를 사용하여 형식을 정의합니다. 힌트가 필요하거나 전체 솔루션을 보려면 여기를 클릭하세요.
다음으로 안내를 추가해야 합니다. 지침은 다음과 같습니다.
add
- 정수 추가and
- 비트 AND.or
- 비트 ORsll
- 왼쪽으로 논리 이동sltu
- 비서명된 소수점 아래 값을 설정합니다.sub
- 정수 빼기xor
- 비트 XOR
인코딩은 다음과 같습니다.
31..25 | 2024년 2월 24일 | 19..15 | 14..12 | 11..7 | 6..0 | 명령 코드 이름 |
---|---|---|---|---|---|---|
000 0000 | rs2 | rs1 | 000 | 번째 | 011 0011 | 추가 |
000 0000 | rs2 | rs1 | 111 | 번째 | 011 0011 | 및 |
000 0000 | rs2 | rs1 | 110 | 번째 | 011 0011 | 또는 |
000 0000 | rs2 | rs1 | 001 | rd | 011 0011 | sll |
000 0000 | rs2 | rs1 | 011 | rd | 011 0011 | sltu |
010 0000 | rs2 | rs1 | 000 | 번째 | 011 0011 | sub |
000 0000 | rs2 | rs1 | 100 | rd | 011 0011 | xor |
func7 | func3 | 명령 코드 |
이러한 명령어 정의를 RiscVInst32
명령어 그룹의 다른 명령어 앞에 추가합니다. 바이너리 문자열은 선행 접두사 0b
로 지정됩니다(16진수 숫자의 0x
과 유사). 긴 비트 문자열을 더 쉽게 읽을 수 있도록 필요에 따라 홑따옴표 '
를 자릿수 구분자로 삽입할 수도 있습니다.
이러한 명령어 정의에는 각각 func7
, func3
, opcode
에 관한 세 가지 제약 조건이 있습니다. sub
를 제외한 모든 경우 func7
제약 조건은 다음과 같습니다.
func7 == 0b000'0000
func3
제약 조건은 대부분의 안내에서 다릅니다. add
및 sub
의 경우 다음과 같습니다.
func3 == 0b000
opcode
제약 조건은 각 안내에서 동일합니다.
opcode == 0b011'0011
각 줄은 세미콜론 ;
으로 끝내야 합니다.
완성된 솔루션은 여기에 있습니다.
이제 이전과 같이 프로젝트를 빌드하고 생성된 riscv32i_bin_decoder.cc
파일을 엽니다. 새 명령어를 처리하기 위해 추가 디코더 함수가 생성된 것을 볼 수 있습니다. 대부분 이전에 생성된 것과 비슷하지만 add
/sub
디코딩에 사용되는 DecodeRiscVInst32_0_c
를 살펴보세요.
OpcodeEnum DecodeRiscVInst32_0_c(uint32_t inst_word) {
static constexpr OpcodeEnum opcodes[2] = {
OpcodeEnum::kAdd,
OpcodeEnum::kSub,
};
if ((inst_word & 0xbe000000) != 0x0) return OpcodeEnum::kNone;
uint32_t index;
index = (inst_word >> 30) & 0x1;
return opcodes[index];
}
이 함수에는 생성된 정적 디코딩 테이블이 있으며, 참조 값은 명령어 단어에서 추출되어 적절한 색인을 선택합니다. 이렇게 하면 명령 디코더 계층 구조에 두 번째 레이어가 추가되지만, 더 이상 비교하지 않고도 테이블에서 opcode를 직접 조회할 수 있으므로 다른 함수 호출이 필요하지 않고 이 함수에 인라인 처리됩니다.
즉시 실행 ALU 명령 추가
다음으로 추가할 명령어 세트는 레지스터 중 하나 대신 즉시 값을 사용하는 ALU 명령어입니다. 이러한 명령에는 세 가지 그룹 (즉, 즉시 필드 기반)이 있습니다. 12비트 부호 즉시 값을 사용하는 I-Type 즉시 명령, 시프트를 위한 특수한 I-Type 즉시 명령, 20비트 부호 없는 즉시 값을 사용하는 U-Type 즉시 명령입니다. 형식은 다음과 같습니다.
I-Type 즉시 형식:
2020년 31월 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | rd | 명령 코드 |
특수화된 I-Type 즉시 형식:
31..25 | 2024년 2월 24일 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | uimm5 | rs1 | func3 | rd | 명령 코드 |
U-Type 즉시 형식:
31..12 | 11..7 | 6..0 |
---|---|---|
20 | 5 | 7 |
uimm20 | rd | 명령 코드 |
I-Type 형식은 이미 riscv32i.bin_fmt
에 있으므로 이 형식을 추가할 필요가 없습니다.
특수화된 I 유형 형식을 이전 연습에서 정의한 R 유형 형식과 비교하면 rs2
필드의 이름이 uimm5
로 변경된 것만 다릅니다. 완전히 새로운 형식을 추가하는 대신 R-Type 형식을 보강할 수 있습니다. 필드를 추가하면 형식의 너비가 늘어나므로 필드를 추가할 수는 없지만 오버레이를 추가할 수는 있습니다. 오버레이는 형식의 비트 집합에 대한 별칭이며 형식의 여러 하위 시퀀스를 별도의 이름이 지정된 항목으로 결합하는 데 사용할 수 있습니다. 부작용으로 생성된 코드에는 이제 필드의 추출 함수 외에도 오버레이의 추출 함수도 포함됩니다. 이 경우 rs2
와 uimm5
가 모두 부호가 없는 경우 필드가 즉시로 사용된다는 점을 명시하는 것 외에는 큰 차이가 없습니다. R-Type 형식에 uimm5
라는 오버레이를 추가하려면 마지막 필드 뒤에 다음을 추가합니다.
overlays:
unsigned uimm5[5] = rs2;
추가해야 하는 유일한 새 형식은 U-Type 형식입니다. 형식을 추가하기 전에 이 형식을 사용하는 두 가지 명령어인 auipc
및 lui
를 살펴보겠습니다. 두 경우 모두 20비트 즉시 값을 12만큼 왼쪽으로 이동한 다음 이를 사용하여 pc를 추가하거나(auipc
) 레지스터에 직접 씁니다(lui
). 오버레이를 사용하면 즉시 값의 사전 전환 버전을 제공하여 명령어 실행에서 명령어 디코딩으로 약간의 계산을 전환할 수 있습니다. 먼저 위 표에 지정된 필드에 따라 형식을 추가합니다. 그런 다음 다음 오버레이를 추가할 수 있습니다.
overlays:
unsigned uimm32[32] = uimm20, 0b0000'0000'0000;
오버레이 구문을 사용하면 필드뿐만 아니라 리터럴도 연결할 수 있습니다. 이 경우 12개의 0과 연결하여 12만큼 왼쪽으로 이동합니다.
추가해야 하는 I-Type 안내는 다음과 같습니다.
addi
- 즉시 추가.andi
- 비트 및 즉시.ori
- 비트 OR(즉시 포함)xori
- 즉시 값을 사용한 비트 XOR
인코딩은 다음과 같습니다.
31..20 | 19..15 | 14..12 | 11..7 | 6..0 | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | rd | 001 0011 | addi |
imm12 | rs1 | 111 | rd | 001 0011 | andi |
imm12 | rs1 | 110 | rd | 001 0011 | ori |
imm12 | rs1 | 100 | rd | 001 0011 | xori |
func3 | 명령 코드 |
추가해야 하는 R 유형(특수화된 I 유형) 명령은 다음과 같습니다.
slli
- 즉시 값으로 왼쪽 논리 이동srai
- 즉시 오른쪽 산술을 이동합니다.srli
- 즉시 오른쪽 논리 시프트
인코딩은 다음과 같습니다.
31..25 | 2024년 2월 24일 | 19..15 | 14..12 | 11..7 | 6..0 | 명령 코드 이름 |
---|---|---|---|---|---|---|
000 0000 | uimm5 | rs1 | 001 | rd | 001 0011 | slli |
010 0000 | uimm5 | rs1 | 101 | rd | 001 0011 | srai |
000 0000 | uimm5 | rs1 | 101 | rd | 001 0011 | srli |
func7 | func3 | 명령 코드 |
추가해야 하는 U-Type 안내는 다음과 같습니다.
auipc
- pc에 상위 즉시 추가lui
- 상위 즉시 로드
인코딩은 다음과 같습니다.
31..12 | 11..7 | 6..0 | 명령 코드 이름 |
---|---|---|---|
uimm20 | 번째 | 001 0111 | auipc |
uimm20 | rd | 011 0111 | lui |
명령 코드 |
변경한 후 빌드합니다. 생성된 출력을 확인합니다. 이전과 마찬가지로 riscv32i.bin_fmt을 기준으로 작업을 확인할 수 있습니다.
브랜치 및 점프 및 링크 안내 추가
다음으로 정의해야 하는 명령어 세트는 조건부 분기 명령어, 점프 및 연결 명령어, 점프 및 연결 레지스터 명령어입니다.
추가하는 조건부 브랜치는 모두 B 유형 인코딩을 사용합니다.
31..25 | 2024년 2월 24일 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
imm7 | rs2 | rs1 | func3 | imm5 | 명령 코드 |
B-유형 인코딩은 레이아웃 면에서 R-유형 인코딩과 동일하지만, 여기서는 RiscV 문서와 일치하도록 새로운 형식 유형을 사용합니다.
그러나 R-유형 인코딩의 func7
및 rd
필드를 사용하여 적절한 분기 이동을 즉시 가져오는 오버레이를 추가할 수도 있습니다.
위에 지정된 필드가 포함된 형식 BType
을 추가하는 것은 필요하지만 충분하지는 않습니다. 보시다시피 즉각적인 값은 두 명령 필드로 나뉩니다.
또한 브랜치 명령어는 이를 두 필드의 단순 연결로 취급하지 않습니다. 대신 각 필드는 추가로 파티션화되고 이러한 파티션은 다른 순서로 연결됩니다. 마지막으로 이 값을 1만큼 왼쪽으로 이동하여 16비트 정렬 오프셋을 얻습니다.
즉시 값을 형성하는 데 사용되는 명령어 단어의 비트 순서는 31, 7, 30~25, 11~8입니다. 이는 다음 하위 필드 참조에 해당하며, 여기서 색인 또는 범위는 필드의 비트를 지정하며, 번호는 오른쪽에서 왼쪽으로 지정됩니다. 즉,
imm7[6]
는 imm7
의 msb를 나타내고 imm5[0]
는 imm5
의 lsb를 나타냅니다.
imm7[6], imm5[0], imm7[5..0], imm5[4..1]
이 비트 조작을 브랜치 명령의 일부로 만들면 두 가지 큰 단점이 있습니다. 먼저 시맨틱 함수의 구현을 바이너리 명령어 표현의 세부정보에 연결합니다. 둘째, 런타임 오버헤드가 더 추가됩니다. 답은 BType
형식에 오버레이를 추가하고 왼쪽 시프트를 고려하기 위해 뒤의 '0'을 포함하는 것입니다.
overlays:
signed b_imm[13] = imm7[6], imm5[0], imm7[5..0], imm5[4..1], 0b0;
오버레이는 서명되어 있으므로 명령어 단어에서 추출될 때 자동으로 부호 확장됩니다.
점프 및 연결(즉시) 명령어는 J 유형 인코딩을 사용합니다.
31..12 | 11..7 | 6..0 |
---|---|---|
20 | 5 | 7 |
imm20 | 번째 | 명령 코드 |
이 형식도 추가하기 쉽지만, 명령어에서 사용하는 즉시 값은 생각만큼 간단하지 않습니다. 전체 즉시 값을 형성하는 데 사용되는 비트 시퀀스는 31, 19~12, 20, 30~21이며, 최종 즉시 값은 절반 단어 정렬을 위해 1씩 왼쪽으로 이동합니다. 해결 방법은 형식에 다른 오버레이(왼쪽 시프트를 고려한 21비트)를 추가하는 것입니다.
overlays:
signed j_imm[21] = imm20[19, 7..0, 8, 18..9], 0b0;
보시다시피 오버레이의 문법은 필드에서 여러 범위를 축약 형식으로 지정하는 것을 지원합니다. 또한 필드 이름이 사용되지 않으면 비트 숫자가 명령어 자체를 참조하므로 위의 내용은 다음과 같이 작성할 수도 있습니다.
signed j_imm[21] = [31, 19..12, 20, 30..21], 0b0;
마지막으로 점프 및 링크 (레지스터)는 이전에 사용된 I 유형 형식을 사용합니다.
I-Type 즉시 형식:
2020년 31월 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | rd | 명령 코드 |
이번에는 형식을 변경할 필요가 없습니다.
추가해야 하는 브랜치 명령어는 다음과 같습니다.
beq
- 같으면 분기합니다.bge
- 크거나 같은 경우 브랜치bgeu
- 부호가 없는 경우보다 크거나 같은 경우 분기합니다.blt
- 값이 작은 경우 브랜치bltu
- 부호 없는 경우보다 작은 경우 분기합니다.bne
- 같지 않으면 브랜치합니다.
다음과 같이 인코딩됩니다.
2025년 31월 25일 | 2024년 2월 24일 | 19..15 | 14..12 | 11..7 | 6..0 | 명령 코드 이름 |
---|---|---|---|---|---|---|
imm7 | rs2 | rs1 | 000 | imm5 | 110 0011 | beq |
imm7 | rs2 | rs1 | 101 | imm5 | 110 0011 | bge |
imm7 | rs2 | rs1 | 111 | imm5 | 110 0011 | 브게우 |
imm7 | rs2 | rs1 | 100 | imm5 | 110 0011 | blt |
imm7 | rs2 | rs1 | 110 | imm5 | 110 0011 | bltu |
imm7 | rs2 | rs1 | 001 | imm5 | 110 0011 | BNE |
func3 | 명령 코드 |
jal
명령어는 다음과 같이 인코딩됩니다.
2012년 12월 31일 | 11..7 | 6..0 | 명령 코드 이름 |
---|---|---|---|
imm20 | 번째 | 110 1111 | Jal |
명령 코드 |
jalr
명령어는 다음과 같이 인코딩됩니다.
31..20 | 19..15 | 14..12 | 11..7 | 6..0 | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | 번째 | 110 0111 | jalr |
func3 | 명령 코드 |
변경한 후 빌드합니다. 생성된 출력을 확인합니다. 이전과 마찬가지로 riscv32i.bin_fmt에 대해 작업을 확인할 수 있습니다.
매장 안내 추가
저장 명령어는 S-Type 인코딩을 사용합니다. 이 인코딩은 즉시 구성을 제외하고 브랜치 명령어에 사용되는 B-Type 인코딩과 동일합니다. RiscV 문서와 일치하도록 SType
형식을 추가했습니다.
31..25 | 2024년 2월 24일 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
imm7 | rs2 | rs1 | func3 | imm5 | 명령 코드 |
SType
형식의 경우 즉시는 두 개의 즉시 필드를 직접 연결한 것이므로 오버레이 사양은 다음과 같습니다.
overlays:
signed s_imm[12] = imm7, imm5;
전체 필드를 연결할 때는 비트 범위 지정자가 필요하지 않습니다.
저장소 안내는 다음과 같이 인코딩됩니다.
31..25 | 2024년 2월 24일 | 19..15 | 14..12 | 11..7 | 6..0 | 명령 코드 이름 |
---|---|---|---|---|---|---|
imm7 | rs2 | rs1 | 000 | imm5 | 010 0011 | sb |
imm7 | rs2 | rs1 | 001 | imm5 | 010 0011 | sh |
imm7 | rs2 | rs1 | 010 | imm5 | 010 0011 | sw |
func3 | 명령 코드 |
변경한 후 빌드합니다. 생성된 출력을 확인합니다. 이전과 마찬가지로 riscv32i.bin_fmt에 대해 작업을 확인할 수 있습니다.
로드 안내 추가
로드 명령어는 I-Type 형식을 사용합니다. 여기서는 변경할 사항이 없습니다.
인코딩은 다음과 같습니다.
31..20 | 19..15 | 14..12 | 11..7 | 6..0 | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | rd | 000 0011 | lb |
imm12 | rs1 | 100 | rd | 000 0011 | lbu |
imm12 | rs1 | 001 | rd | 000 0011 | 하 |
imm12 | rs1 | 101 | rd | 000 0011 | lhu |
imm12 | rs1 | 010 | rd | 000 0011 | lw |
func3 | 명령 코드 |
변경한 후 빌드합니다. 생성된 출력을 확인합니다. 이전과 마찬가지로 riscv32i.bin_fmt을 기준으로 작업을 확인할 수 있습니다.
이 튜토리얼이 도움이 되었기를 바랍니다.