本教程的目标是:
- 了解二进制格式描述文件的结构和语法。
- 了解二进制格式说明如何与 ISA 说明匹配。
- 为指令的 RiscV RV32I 子集编写二元描述。
概览
RiscV 二进制指令编码
二进制指令编码是对编码器和解码器 在微处理器上进行执行。它们通常存储在可执行文件中 通常采用 ELF 格式。指令可以是固定宽度,也可以是可变的 宽度。
通常,指令使用一小组编码格式,每种格式 针对编码的指令类型自定义的。例如,register-register 指令可以使用一种使可用操作码数量最多的格式, 而注册直接指令则使用另一种指令 用于增加可编码立即数大小的可用操作码。 分支指令和跳转指令几乎总是使用会最大限度地增加 立即使用,以支持具有较大偏移量的分支。
我们要在 RiscV 中解码的指令使用的指令格式 模拟器如下所示:
R 类型格式,用于寄存器-寄存器指令:
31 月 25 日 | 20 月 24 日 | 19 月 15 日 | 12 月 14 日 | 11.7 | 6.0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | rs2 | rs1 | func3 | RD | 操作码 |
I-Type 格式,用于注册即时指令、加载指令和
jalr
指令,12 位立即数。
2020 年 31 月 20 日 | 19 月 15 日 | 12 月 14 日 | 11.7 | 6.0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | RD | 操作码 |
专用 I 型格式,用于有即时指令的移位,5 位 即时:
31 月 25 日 | 20 月 24 日 | 19 月 15 日 | 12 月 14 日 | 11.7 | 6.0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | uimm5 | rs1 | func3 | RD | 操作码 |
U 类型格式,用于较长的即时指令(lui
、auipc
),20 位
即时:
12 月 31 日 | 11.7 | 6.0 |
---|---|---|
20 | 5 | 7 |
uimm20 | RD | 操作码 |
B 类型格式,用于条件分支,12 位立即数。
31 | 30 月 25 日 | 20 月 24 日 | 19 月 15 日 | 12 月 14 日 | 11 月 8 日 | 7 | 6.0 |
---|---|---|---|---|---|---|---|
1 | 6 | 5 | 5 | 3 | 4 | 1 | 7 |
CANNOT TRANSLATE | CANNOT TRANSLATE | rs2 | rs1 | func3 | CANNOT TRANSLATE | CANNOT TRANSLATE | 操作码 |
J 类型格式,用于 jal
指令,20 位立即数。
31 | 21 月 30 日 | 20 | 12 月 19 日 | 11.7 | 6.0 |
---|---|---|---|---|---|
1 | 10 | 1 | 8 | 5 | 7 |
CANNOT TRANSLATE | CANNOT TRANSLATE | CANNOT TRANSLATE | CANNOT TRANSLATE | RD | 操作码 |
S 类型格式,用于存储指令,12 位立即数。
31 月 25 日 | 20 月 24 日 | 19 月 15 日 | 12 月 14 日 | 11.7 | 6.0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
CANNOT TRANSLATE | rs2 | rs1 | func3 | CANNOT TRANSLATE | 操作码 |
通过这些格式可以看出,所有这些指令都是 32 位, 每种格式的低 7 位是操作码字段。另请注意 多种格式都有大小相同的立即数,它们的位取自 说明的各个不同部分。我们将看到,二进制解码器 规范格式能够表达这一点。
二进制编码说明
指令的二进制编码以二进制格式表示
(.bin_fmt
) 描述文件。它描述了
ISA 中的指令,以便将二进制格式的指令解码器
。生成的解码器会确定操作码,提取
操作数和立即数字段,以提供 ISA 所需的信息
不限定编码的解码器。
在本教程中,我们将为一个子集编写一个二进制编码描述文件, 所需的 RiscV32I 说明,模拟 小型“Hello World”计划。如需详细了解 RiscV ISA,请参阅 风险 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 部分组成:
- 操作码名称,必须与指令中使用的相同 解码器描述,以便两者协同工作。
- 用于操作码的指令格式。这是 用于满足最后一部分中对位字段的引用。
- 以英文逗号分隔的位字段约束条件列表,即
==
、!=
、<
、<=
、>
以及>=
,它们必须全部为 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)
host - 对于其他主机,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
文件中添加一些新指令了。通过
第一组指令是寄存器-注册 ALU 指令,例如
add
、and
等。在 RiscV32 上,这些都使用 R 型二进制指令
格式:
31 月 25 日 | 20 月 24 日 | 19 月 15 日 | 12 月 14 日 | 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
- 按位异或。
其编码为:
31 月 25 日 | 20 月 24 日 | 19 月 15 日 | 12 月 14 日 | 11.7 | 6.0 | 操作码名称 |
---|---|---|---|---|---|---|
00 万 | rs2 | rs1 | 000 | RD | 011,0011 | add |
00 万 | rs2 | rs1 | 111 | RD | 011,0011 | 和 |
00 万 | rs2 | rs1 | 110 | RD | 011,0011 | 或 |
00 万 | rs2 | rs1 | 001 | RD | 011,0011 | sll |
00 万 | rs2 | rs1 | 011 | RD | 011,0011 | 斯尔图 |
010 万 | rs2 | rs1 | 000 | RD | 011,0011 | sub |
00 万 | rs2 | rs1 | 100 | RD | 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
文件。您将看到其他解码器函数
来处理新指令。大多数情况下
但再看看
DecodeRiscVInst32_0_c
,用于 add
/sub
解码:
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-Type 直接格式:
2020 年 31 月 20 日 | 19 月 15 日 | 12 月 14 日 | 11.7 | 6.0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | RD | 操作码 |
专用 I-Type 立即格式:
31 月 25 日 | 20 月 24 日 | 19 月 15 日 | 12 月 14 日 | 11.7 | 6.0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
func7 | uimm5 | rs1 | func3 | RD | 操作码 |
U-Type 直接格式:
12 月 31 日 | 11.7 | 6.0 |
---|---|---|
20 | 5 | 7 |
uimm20 | RD | 操作码 |
riscv32i.bin_fmt
中已存在 I-Type 格式,因此无需
添加该格式。
如果我们将专用 I-Type 格式与我们在
会看到唯一的区别在于 rs2
字段
已重命名为 uimm5
。我们无需添加全新的广告格式,
R-Type 格式。我们无法添加其他字段,因为这会增加
格式,但我们可以添加叠加层。叠加层是一组叠加层的别名
位,并可用于组合
转换为单独的命名实体。其副作用是生成的代码
除了
这些字段在本例中,当 rs2
和 uimm5
均未签名时,
除了明确说明该字段使用了
立即生效。若要将名为 uimm5
的叠加层添加到 R-Type 格式,请将
在最后一个字段之后:
overlays:
unsigned uimm5[5] = rs2;
我们需要添加的唯一新格式是 U-Type 格式。在添加
请考虑使用该格式的两条指令:auipc
和
lui
。在执行这两项操作之前,都会先将 20 位立即值向左移动 12,然后再使用该值
将 pc 添加到其中 (auipc
),或者直接将其写入寄存器
(lui
)。通过使用叠加层,我们可以提供即时、
将一些计算从指令执行转移到指令
解码。首先根据表中指定的字段添加格式
。然后,我们可以添加以下叠加层:
overlays:
unsigned uimm32[32] = uimm20, 0b0000'0000'0000;
利用叠加层语法,我们不仅可以将字段和字面量串联起来, 。在本示例中,我们将其与 12 个零串联起来,实际上是将其向左移 乘以 12。
我们需要添加的 I-Type 说明如下:
addi
- 立即添加。andi
- 按位立即,ori
- 按位或立即。xori
- 具有立即数的按位异或。
其编码为:
2020 年 31 月 20 日 | 19 月 15 日 | 12 月 14 日 | 11.7 | 6.0 | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | RD | 001 0011 | Addi |
imm12 | rs1 | 111 | RD | 001 0011 | 安迪 |
imm12 | rs1 | 110 | RD | 001 0011 | Ori |
imm12 | rs1 | 100 | RD | 001 0011 | Xori |
func3 | 操作码 |
我们需要添加的 R 类型(专用 I 类型)说明如下:
slli
- 按逻辑顺序左移。srai
- 将算术数右移。srli
- 按立即数右移逻辑右移。
其编码为:
31 月 25 日 | 20 月 24 日 | 19 月 15 日 | 12 月 14 日 | 11.7 | 6.0 | 操作码名称 |
---|---|---|---|---|---|---|
00 万 | uimm5 | rs1 | 001 | RD | 001 0011 | 斯利 |
010 万 | uimm5 | rs1 | 101 | RD | 001 0011 | Srai |
00 万 | uimm5 | rs1 | 101 | RD | 001 0011 | Srli |
func7 | func3 | 操作码 |
我们需要添加的 U-Type 说明如下:
auipc
- 向 PC 添加上部直接位置。lui
- 加载顶部立即。
其编码为:
12 月 31 日 | 11.7 | 6.0 | 操作码名称 |
---|---|---|---|
uimm20 | RD | 001 0111 | auipc |
uimm20 | RD | 011 0111 | lui |
操作码 |
继续进行更改,然后进行构建。检查生成的输出。只是 与之前一样 riscv32i.bin_fmt。
添加分支和跳转和链接指令
下一组需要定义的指令是条件分支 跳转和链接指令以及跳转和链接寄存器 指令。
我们要添加的条件分支都使用 B 型编码。
31 月 25 日 | 20 月 24 日 | 19 月 15 日 | 12 月 14 日 | 11.7 | 6.0 |
---|---|---|---|---|---|
7 | 5 | 5 | 3 | 5 | 7 |
imm7 | rs2 | rs1 | func3 | imm5 | 操作码 |
虽然 B 型编码在布局上与 R 型编码完全相同,
选择使用新的格式类型,使其与 RiscV 文档保持一致。
不过,您也可以只添加叠加层来获取相应分支
位移立即输出,使用 R 类型的 func7
和 rd
字段
编码。
必须使用上面指定的字段添加 BType
格式,但并非
。如您所见,立即被拆分成两个指令字段。
此外,分支指令不会将其视为
这两个字段相反,每个字段都会进一步分区,这些分区
按不同顺序串联在一起。最后,再将该值左移
一个来获取 16 位对齐的偏移量。
用于构成立即数的指令字中的位序列为:31,
7、30..25、11..8.这对应于以下子字段引用,其中
索引或范围指定字段中的位,从右到左编号,即
imm7[6]
是指 imm7
的 msb,imm5[0]
是指
imm5
。
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 | RD | 操作码 |
这种格式也很容易添加,但同样, 说明并不像看上去那么简单。位序列用于 形式的完整立即数为: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 月 20 日 | 19 月 15 日 | 12 月 14 日 | 11.7 | 6.0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | RD | 操作码 |
这次,不需要对格式进行任何更改。
我们需要添加的分支指令包括:
beq
- 如果相等,则分支。bge
- 如果大于或等于,则分支。bgeu
- 大于或等于无符号的分支。blt
- 如果小于,则分支。bltu
- 无符号数小于的分支。bne
- 如果不相等,则分支。
它们的编码方式如下:
31 月 25 日 | 20 月 24 日 | 19 月 15 日 | 12 月 14 日 | 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 | 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 | 操作码名称 |
---|---|---|---|
imm20 | RD | 110 1111 | 贾勒 |
操作码 |
jalr
指令的编码如下所示:
2020 年 31 月 20 日 | 19 月 15 日 | 12 月 14 日 | 11.7 | 6.0 | opcode_name |
---|---|---|---|---|---|
imm12 | rs1 | 000 | RD | 110 0111 | Jalr |
func3 | 操作码 |
继续进行更改,然后进行构建。检查生成的输出。只是 与之前一样 riscv32i.bin_fmt。
添加商店说明
商店说明使用 S-Type 编码,此编码与 B-Type 相同。
一种供分支指令使用的编码,但
。我们选择添加 SType
格式,以便与 RiscV 保持一致
文档。
31 月 25 日 | 20 月 24 日 | 19 月 15 日 | 12 月 14 日 | 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 日 | 20 月 24 日 | 19 月 15 日 | 12 月 14 日 | 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 格式。无需进行任何更改。
编码为:
2020 年 31 月 20 日 | 19 月 15 日 | 12 月 14 日 | 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。
本教程到此结束,希望它能对您有所帮助。