二进制指令解码器教程

本教程的目标是:

  • 了解二进制格式描述文件的结构和语法。
  • 了解二进制格式说明如何与 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 类型格式,用于较长的即时指令(luiauipc),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 字段:imm12rs1func3rdopcodeimm12 字段为 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 指令,例如 addand 等。在 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)。为了更轻松地 读取二进制数字的长字符串时,也可以插入单引号 ',如下所示 数字分隔符。

每个指令定义都有三个约束条件,即 func7func3opcode。对于除 sub 之外的所有类型,func7 约束条件将 是:

func7 == 0b000'0000

func3 约束条件因大多数指令而异。对于addsub

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 格式。我们无法添加其他字段,因为这会增加 格式,但我们可以添加叠加层。叠加层是一组叠加层的别名 位,并可用于组合 转换为单独的命名实体。其副作用是生成的代码 除了 这些字段在本例中,当 rs2uimm5 均未签名时, 除了明确说明该字段使用了 立即生效。若要将名为 uimm5 的叠加层添加到 R-Type 格式,请将 在最后一个字段之后:

  overlays:
    unsigned uimm5[5] = rs2;

我们需要添加的唯一新格式是 U-Type 格式。在添加 请考虑使用该格式的两条指令:auipclui。在执行这两项操作之前,都会先将 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 类型的 func7rd 字段 编码。

必须使用上面指定的字段添加 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

本教程到此结束,希望它能对您有所帮助。