本教學課程的目標是:
- 瞭解二進位格式說明檔的結構和語法。
- 瞭解二進位格式說明如何與 ISA 說明相符。
- 為 RiscV RV32I 部分指令撰寫二進位說明。
總覽
RiscV 二進位指令編碼
在微處理器上執行時,使用二進位檔指令編碼是將操作說明編碼的標準方式。這些檔案通常會以可執行檔儲存,通常為 ELF 格式。指令可以是固定寬度或可變寬度。
指令通常會使用一小組編碼格式,每個格式都會針對所編碼的指令類型進行自訂。舉例來說,寄存器-寄存器指令可能會使用一種格式,以便盡可能增加可用運算碼的數量,而寄存器-立即值指令則會使用另一種格式,以便在可用運算碼數量與可編碼立即值大小之間取得平衡。分支和跳躍指令幾乎總是使用格式,以便將立即值的大小盡可能加大,以便支援較大偏移量分支。
我們要在 RiscV 模擬器中解碼的指令所使用的指令格式如下:
R 型格式,用於註冊-註冊指令:
2025 年 31 月 31 日 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | rs2 | rs1 | func3 | rd | 運算程式碼 |
I 型格式,用於註冊即時指令、載入指令和 jalr
指令,12 位元即時指令。
2020 年 3 月 31 日 | 19:15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | rd | 運算程式碼 |
特殊的 I-Type 格式,用於與立即指示轉移,5 位元立即:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | uimm5 | rs1 | func3 | rd | 運算程式碼 |
U 型格式,用於長立即指令 (lui
、auipc
),20 位元立即值:
31..12 | 11..7 | 6..0 |
---|---|---|
20 | 5 | 7 |
uimm20 | rd | 運算程式碼 |
B 型格式,用於條件式分支,12 位元立即值。
31 | 30.25 | 24..20 | 19..15 | 14..12 | 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 | 19:12 | 11..7 | 6..0 |
---|---|---|---|---|---|
1 | 10 | 1 | 8 | 5 | 7 |
imm | imm | imm | imm | 日 | 運算程式碼 |
S 型格式,用於儲存指令,12 位元立即值。
31..25 | 24..20 | 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 編碼解碼器所需的資訊。
在本教學課程中,我們將為 RiscV32I 指令的子集編寫二進位編碼說明檔案,以模擬小型「Hello World」程式中使用的指令。如要進一步瞭解 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
的名稱,以及另外四個資訊。第一個是 namespace
,用來定義產生代碼的命名空間。第二個是 opcode_enum
,用於命名由 ISA 解碼器產生的運算碼列舉類型,以便在產生的程式碼中參照。第三,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];
};
第一個定義了名為 Inst32Format
的 32 位元寬指令格式,其中包含兩個欄位:bits
(寬度 25 位元) 和 opcode
(寬度 7 位元)。每個欄位都是 unsigned
,表示在擷取並放入 C++ 整數類型時,值會進行零延伸。位元欄的寬度總和必須等於格式的寬度。如果不一致,工具就會產生錯誤。這個格式不會衍生自任何其他格式,因此屬於頂層格式。
第二個定義會定義名為 IType
的 32 位元寬指令格式,該格式源自 Inst32Format
,因此這兩種格式相關聯。格式包含 5 個欄位:imm12
、rs1
、func3
、rd
和 opcode
。imm12
欄位是 signed
,這表示當值擷取並放入 C++ 整數類型時,值會進行符號延伸。請注意,IType.opcode
都具有相同的正負號/未簽署屬性,且參照與 Inst32Format.opcode
相同的指令文字位元。
第三種格式是只有 fence
指令使用的自訂格式,也就是已指定的指令,我們不必擔心本教學課程中的問題。
重點:只要欄位名稱代表相同位元且具有相同的正負號屬性,即可重複使用不同相關格式的欄位名稱。
在 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]
、要使用的操作碼列舉類型名稱 "OpcodeEnum"
,以及基礎指令格式。運算碼列舉類型應與 ISA 解碼器教學課程中說明的格式獨立指令解碼器產生的類型相同。
每個指令編碼說明包含 3 個部分:
- 指令碼名稱,必須與指令解碼器說明中使用的名稱相同,才能讓兩者搭配運作。
- 要用於指令碼的指令格式。這種格式會用來滿足最終部分對 Bitfields 的參照。
- 以半形逗號分隔的位元欄位限制清單,
==
、!=
、<
、<=
、>
和>=
都必須為 true,才能讓操作碼成功比對指令字。
.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
中解碼器函式的函式宣告。這個 ID 會採用 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
檔案中新增一些指令。第一組指令是寄存器-寄存器 ALU 指令,例如 add
、and
等。在 RiscV32 上,這些指令都使用 R 型二進位指令格式:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | rs2 | rs1 | func3 | rd | 運算程式碼 |
首先,我們需要新增格式。請在您偏好的編輯器中開啟 riscv32i.bin_fmt
。Inst32Format
之後,您可以新增格式 RType
,該格式源自 Inst32Format
。RType
中的所有位元欄都是 unsigned
。請使用上表中的名稱、位元寬度和順序 (從左到右) 定義格式。如需提示或查看完整解決方案,請按這裡。
接下來,我們需要新增操作說明。操作說明如下:
add
- 整數加法。and
:位元和。or
- 位元或。sll
- 邏輯左移。sltu
- 設定小於值,未簽署。sub
- 整數減法。xor
- 位元 xor。
其編碼如下:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 | opcode 名稱 |
---|---|---|---|---|---|---|
000 0000 | rs2 | rs1 | 000 | rd | 011 0011 | add |
000 0000 | rs2 | rs1 | 111 | rd | 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 | rd | 011 0011 | 替補球員 |
000 0000 | rs2 | rs1 | 100 | 日 | 011 0011 | xor |
func7 | func3 | 運算程式碼 |
請在 RiscVInst32
指令群組中的其他指令之前加入這些指令定義。二進位字串會以 0b
做為開頭前置字元 (類似十六進位數的 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];
}
這個函式會產生靜態解碼表,並從指令字取出查詢值,以選取適當的索引。這樣會在操作說明解碼器階層中新增第二層,但由於運算程式碼可直接查詢在表格中,不會進一步比較,因此會內嵌在這個函式中,而非需要其他函式呼叫。
新增含立即值的 ALU 指令
我們接下來要加入的下一個指令集是 ALU 指令,這類指令會使用立即值,而非其中一個暫存器。這類指令分為三組 (根據立即值欄位):I 型立即值指令 (具有 12 位元帶符號立即值)、專門用於位移的 I 型立即值指令,以及 U 型立即值 (具有 20 位元無符號立即值)。格式如下:
I 型立即格式:
31..20 | 19:15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | rd | 運算程式碼 |
專用的 I-Type 立即格式:
2025 年 31 月 31 日 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | uimm5 | rs1 | func3 | rd | 運算程式碼 |
U 型立即格式:
31..12 | 11..7 | 6..0 |
---|---|---|
20 | 5 | 7 |
uimm20 | rd | 運算程式碼 |
riscv32i.bin_fmt
中已存在 I 型格式,因此不需要新增該格式。
如果將專屬的 I 型格式與我們在前一個練習中定義的 R 型格式進行比較,我們會發現唯一的差異在於 rs2
欄位已重新命名為 uimm5
。我們可以擴充 R 型格式,而非新增全新格式。我們無法新增另一個欄位,因為它會增加格式的寬度,但您可以新增「疊加層」。疊加層是格式中一組位元的別名,可用於將格式的多個子序組合成個別命名的實體。副作用是,除了欄位之外,產生的程式碼現在也會包含疊加層的擷取函式。在這種情況下,如果 rs2
和 uimm5
都未簽署,則除了明確指出該欄位用於立即值之外,不會有太大差異。如要在 R-Type 格式中新增名為 uimm5
的疊加層,請在最後一個欄位後方加入以下內容:
overlays:
unsigned uimm5[5] = rs2;
我們只需要新增 U 型格式。在新增格式之前,請先考慮使用該格式的兩個指令:auipc
和 lui
。這兩種方法都會將 20 位元立即值左移 12 位元,然後用於將 pc 加到其中 (auipc
),或直接寫入註冊 (lui
)。我們可以使用疊加層提供預先移位版本的立即值,將一小部分運算從指令執行移至指令解碼。首先,請根據上表中指定的欄位新增格式。接著,我們可以新增下列疊加層:
overlays:
unsigned uimm32[32] = uimm20, 0b0000'0000'0000;
重疊語法可讓我們連結欄位,以及文字常值。在本例中,我們會將其與 12 個零連接,實際上會將其向左移動 12 位元。
我們需要新增的 I 型指令如下:
addi
- 立即加入。andi
- 位元 and 與立即運算子。ori
- 位元或運算與立即值。xori
- 位元 xor 與立即值。
其編碼如下:
31..20 | 19:15 | 14..12 | 11..7 | 6..0 | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | 日 | 001 0011 | addi |
imm12 | rs1 | 111 | rd | 001 0011 | andi |
imm12 | rs1 | 110 | 日 | 001 0011 | ori |
imm12 | rs1 | 100 | 日 | 001 0011 | Xori |
func3 | 運算程式碼 |
我們需要新增的 R-Type (專門的 I-Type) 指令如下:
slli
- 立即將左移邏輯列。srai
- 以立即值右移算術。srli
- 立即右移邏輯。
其編碼如下:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 | opcode 名稱 |
---|---|---|---|---|---|---|
000 0000 | uimm5 | rs1 | 001 | rd | 001 0011 | slli |
010,0000 人 | uimm5 | rs1 | 101 | 日 | 001 0011 | srai |
000 0000 | uimm5 | rs1 | 101 | 日 | 001 0011 | srli |
func7 | func3 | 運算程式碼 |
我們需要新增的 U 型指令如下:
auipc
- 將上限立即值新增至 pc。lui
- 立即載入上層。
其編碼如下:
12 月 31 日 | 11..7 | 6..0 | Opcode 名稱 |
---|---|---|---|
uimm20 | 日 | 001 0111 | auipc |
uimm20 | 日 | 011 0111 | lui |
運算程式碼 |
請繼續進行變更,然後進行建構。查看產生的輸出內容。如同先前所述,您可以根據 riscv32i.bin_fmt 檢查您的工作。
新增分支和跳躍連結指令
接下來要定義的另一組指令是條件分支指令、跳轉及連結指令,以及跳轉及連結暫存器指令。
我們新增的條件分支都使用 B 型編碼。
2025 年 31 月 31 日 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
imm7 | rs2 | rs1 | func3 | imm5 | 運算程式碼 |
雖然 B 型編碼的版面配置與 R 型編碼相同,但我們選擇使用新的格式類型,以便與 RiscV 說明文件保持一致。不過,您也可以使用 R-Type 編碼的 func7
和 rd
欄位,新增疊加層,以便立即取得適當的分支位移。
您必須新增格式 BType
,並加入上述指定的欄位,但這還不夠。如您所見,立即值會分散在兩個指令欄位。此外,分支指令不會將這項作業視為兩個欄位的簡單串連作業。相反地,每個欄位都會進一步分割,然後以不同的順序串連。最後,該值會向左移動,取得 16 位元對齊的位移。
用於形成立即值的指令字元序列為:31, 7, 30..25, 11..8。這會對應到下列子欄位參照,其中索引或範圍會指定欄位中的位元,從右到左編號,也就是imm7[6]
是 imm7
的 MSB,imm5[0]
是 imm5
的 MSB。
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 型編碼:
12 月 31 日 | 11..7 | 6..0 |
---|---|---|
20 | 5 | 7 |
imm20 | 日 | 運算程式碼 |
這也是很容易新增的格式,但同樣地,指令所使用的立即值並非看起來那麼簡單。用於形成完整立即值的位元序列為:31、19..12、20、30..21,而最終的立即值會向左偏移一個位元組,以便半字對齊。解決方法是在格式中新增另一個疊加層 (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 型立即格式:
31..20 | 19:15 | 14..12 | 11..7 | 6..0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | rd | 運算程式碼 |
這次格式不需要進行任何變更。
需要新增的分支版本指示如下:
beq
- 若相等則分支。bge
- 大於或等於分支版本。bgeu
- 大於或等於未簽署的分支版本。blt
- 所屬的分支版本,bltu
:如果小於未簽署值,則分支。bne
:如果不相等,則分支。
這些程式碼的編碼方式如下:
31..25 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 | opcode 名稱 |
---|---|---|---|---|---|---|
imm7 | rs2 | rs1 | 000 | imm5 | 110 0011 | beq |
imm7 | rs2 | rs1 | 101 | imm5 | 110 0011 | bge |
imm7 | rs2 | rs1 | 111 | imm5 | 110 0011 | Bgeu |
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
指令的編碼方式如下:
12 月 31 日 | 11..7 | 6..0 | Opcode 名稱 |
---|---|---|---|
imm20 | rd | 110 1111 | jal |
運算程式碼 |
jalr
指令的編碼方式如下:
2020 年 3 月 31 日 | 19:15 | 14..12 | 11..7 | 6..0 | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | rd | 110 0111 | jalr |
func3 | 運算程式碼 |
請繼續進行變更,然後進行建構。查看產生的輸出內容。如同先前所述,您可以根據 riscv32i.bin_fmt 檢查您的工作。
新增商店操作說明
儲存指令使用 S 型編碼,與分支指令使用的 B 型編碼相同,除了立即值的組合。我們選擇新增 SType
格式,以便與 RiscV 說明文件保持一致。
31..25 | 24..20 | 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 | 24..20 | 19..15 | 14..12 | 11..7 | 6..0 | opcode 名稱 |
---|---|---|---|---|---|---|
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 型格式。因此不需要在該處進行任何變更。
編碼方式如下:
31..20 | 19:15 | 14..12 | 11..7 | 6..0 | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | rd | 000 0011 | lb |
imm12 | rs1 | 100 | 日 | 000 0011 | lbu |
imm12 | rs1 | 001 | 日 | 000 0011 | lh |
imm12 | rs1 | 101 | rd | 000 0011 | lhu |
imm12 | rs1 | 010 | 日 | 000 0011 | lw |
func3 | 運算程式碼 |
請繼續進行變更,然後進行建構。查看產生的輸出內容。如同先前所述,您可以根據 riscv32i.bin_fmt 檢查您的工作。
本教學課程到此結束,希望對您有所幫助。