本教程的目标是:
- 了解 MPACT-Sim 模拟器如何表示指令。
- 了解 ISA 描述文件的结构和语法。
- 为 RiscV RV32I 说明子集撰写 ISA 说明
概览
在 MPACT-Sim 中,目标指令被解码,并存储在内部 使其信息更加丰富, 执行速度更快。这些指令实例在指令中缓存 以便减少频繁执行的指令 。
说明类
在我们开始之前,稍微了解一下说明
MPACT-Sim 卡中Instruction
类在
mpact-sim/mpact/sim/generic/instruction.h.
说明类实例包含 模拟指令“执行”时,例如:
- 指令地址、模拟指令大小,即 .text 格式的大小。
- 指令操作码。
- 谓词操作数接口指针(如果适用)。
- 源运算数接口指针的矢量。
- 目标运算数接口指针的向量。
- 语义函数可调用。
- 指向架构状态对象的指针。
- 指向上下文对象的指针。
- 指向子实例和下一个指令实例的指针。
- 反汇编字符串。
这些实例通常存储在指令(实例)缓存中,并且 会在重新执行该指令时重复使用该 ID。这有助于提升效果 错误。
除了指向上下文对象的指针之外,其他所有内容都由 指令解码器。为此 但没必要了解这些内容的详情, 直接使用它们相反,您可以大致了解它们的使用方式。 。
可调用的语义函数是 C++ 函数/方法/函数对象
(包括 lambda)。对于
例如,对于 add
指令,它会加载每个源运算数,将两个
运算数,并将结果写入单个目标运算数。以下主题的主题:
语义函数教程对语义函数进行了深入介绍。
指令运算数
指令类包括指向三种类型的运算数接口的指针: 谓词、来源和目标。这些接口允许语义函数 独立于底层指令的实际类型 操作数。例如,访问寄存器和立即数的值 通过相同的界面访问数据也就是说,执行相同操作 运算,但在不同的运算数(例如寄存器与 immedates)上可以 语义函数来实现。
谓词操作数接口,适用于支持谓词的 ISA 指令执行(对于其他 ISA 则为 null),用于确定 指定的指令应根据谓词的布尔值执行。
// The predicte operand interface is intended primarily as the interface to
// read the value of instruction predicates. It is separated from source
// predicates to avoid mixing it in with the source operands needed for modeling
// the instruction semantics.
class PredicateOperandInterface {
public:
virtual bool Value() = 0;
// Return a string representation of the operand suitable for display in
// disassembly.
virtual std::string AsString() const = 0;
virtual ~PredicateOperandInterface() = default;
};
源运算数接口允许指令语义函数读取 指令运算数中的值,而不考虑基础运算数 类型。接口方法支持标量和矢量值运算数。
// The source operand interface provides an interface to access input values
// to instructions in a way that is agnostic about the underlying implementation
// of those values (eg., register, fifo, immediate, predicate, etc).
class SourceOperandInterface {
public:
// Methods for accessing the nth value element.
virtual bool AsBool(int index) = 0;
virtual int8_t AsInt8(int index) = 0;
virtual uint8_t AsUint8(int index) = 0;
virtual int16_t AsInt16(int index) = 0;
virtual uint16_t AsUint16(int) = 0;
virtual int32_t AsInt32(int index) = 0;
virtual uint32_t AsUint32(int index) = 0;
virtual int64_t AsInt64(int index) = 0;
virtual uint64_t AsUint64(int index) = 0;
// Return a pointer to the object instance that implements the state in
// question (or nullptr) if no such object "makes sense". This is used if
// the object requires additional manipulation - such as a fifo that needs
// to be pop'ed. If no such manipulation is required, nullptr should be
// returned.
virtual std::any GetObject() const = 0;
// Return the shape of the operand (the number of elements in each dimension).
// For instance {1} indicates a scalar quantity, whereas {128} indicates an
// 128 element vector quantity.
virtual std::vector<int> shape() const = 0;
// Return a string representation of the operand suitable for display in
// disassembly.
virtual std::string AsString() const = 0;
virtual ~SourceOperandInterface() = default;
};
目标操作数接口提供了用于分配和处理的方法
DataBuffer
实例(用于存储寄存器值的内部数据类型)。答
目标运算数也有与之相关的延迟时间,即
等待由指令分配的数据缓冲区实例所需的周期数
语义函数用于更新目标寄存器的值。对于
例如,add
指令的延迟时间可以为 1,而 mpy
指令的延迟时间可以为 1
则可能是 4。有关详情,请参阅
语义函数教程。
// The destination operand interface is used by instruction semantic functions
// to get a writable DataBuffer associated with a piece of simulated state to
// which the new value can be written, and then used to update the value of
// the piece of state with a given latency.
class DestinationOperandInterface {
public:
virtual ~DestinationOperandInterface() = default;
// Allocates a data buffer with ownership, latency and delay line set up.
virtual DataBuffer *AllocateDataBuffer() = 0;
// Takes an existing data buffer, and initializes it for the destination
// as if AllocateDataBuffer had been called.
virtual void InitializeDataBuffer(DataBuffer *db) = 0;
// Allocates and initializes data buffer as if AllocateDataBuffer had been
// called, but also copies in the value from the current value of the
// destination.
virtual DataBuffer *CopyDataBuffer() = 0;
// Returns the latency associated with the destination operand.
virtual int latency() const = 0;
// Return a pointer to the object instance that implmements the state in
// question (or nullptr if no such object "makes sense").
virtual std::any GetObject() const = 0;
// Returns the order of the destination operand (size in each dimension).
virtual std::vector<int> shape() const = 0;
// Return a string representation of the operand suitable for display in
// disassembly.
virtual std::string AsString() const = 0;
};
ISA 说明
处理器的 ISA(指令集架构)定义了抽象模型 是软件与硬件进行交互的途径。它定义了 可用的指令、数据类型、寄存器以及其他机器状态 指令及其行为(语义)。对于 因此 ISA 不包含指令的实际编码。 请单独处理。
处理器 ISA 在描述 在抽象的编码无关级别上的指令集。说明文件 枚举可用指令集。对于每条指令 列出其名称、操作数的编号和名称,以及 绑定到实现其语义的 C++ 函数/Callable。此外, 用户可以指定一个反汇编格式字符串,以及该指令对 硬件资源名称前者对于生成文本 用于调试、跟踪或交互用途的说明的表示形式。通过 后者可用于在模拟中提高周期准确度。
ISA 描述文件由 isa-解析器解析,后者会生成 不限定表示法的指令解码器。该解码器负责 来填充说明对象的字段。比如 目标寄存器编号,通过特定于格式的指令获取 解码器。其中一个解码器是二进制解码器, 下一个教程。
本教程介绍了如何为简单标量编写 ISA 描述文件 架构。我们将使用 RiscV RV32I 指令集的子集来 与其他教程一起,构建一个支持 是模拟“Hello World”的计划。如需详细了解 RiscV ISA,请参阅 Risc-V 规范。
首先打开文件:
riscv_isa_decoder/riscv32i.isa
该文件的内容分为多个部分。首先是 ISA 声明:
isa RiscV32I {
namespace mpact::sim::codelab;
slots { riscv32; }
}
这会将 RiscV32I
声明为 ISA 的名称,并且代码生成器将
创建一个名为 RiscV32IEncodingBase
的类,用于定义
生成的解码器将用于获取运算码和运算数信息。名称
这个类是通过将 ISA 名称转换为 Pascal-Case 名称生成的
并将其与 EncodingBase
串联起来。声明 slots { riscv32; }
指定 RiscV32I 中只有一个指令槽 riscv32
ISA(而不是 VLIW 指令中的多个槽位),并且
有效指令是指在 riscv32
中定义的要执行的指令。
// First disasm fragment is 15 char wide and left justified.
disasm widths = {-15};
它指定任何拆解代码段的第一个反汇编片段 将显示为左对齐,格式为 15 个字符 宽字段。任何后续片段都将附加到此字段 任何其他间距。
下面有三个 slot 声明:riscv32i
、zicsr
和 riscv32
。
根据上面的 isa
定义,只有为 riscv32
定义的指令
槽位将是 RiscV32I
ISA 的一部分。另外两个空档有什么用?
槽位可用于将说明分解为多个单独的组,然后可将这些组
合并为一个广告位请注意表示法 : riscv32i, zicsr
。riscv32
此属性指定广告位 riscv32
继承
槽位 zicsr
和 riscv32i
中定义的所有指令。RiscV 32 位 ISA
由名为 RV32I 的基本 ISA 组成,其中可能有一组可选的扩展
资源。槽机制允许
然后根据需要进行组合,以定义
整体 ISA。在本示例中,RiscV“I”群组
与“zicsr”中的代码不同。可以定义其他群组
搜索“M”(乘/除)、“F”(单精度浮点数)、“D”
(双精度浮点数)、“C”(紧凑的 16 位指令)等命令
所需的最终 RiscV ISA 所需的资源。
// The RiscV 'I' instructions.
slot riscv32i {
...
}
// RiscV32 CSR manipulation instructions.
slot zicsr {
...
}
// The final instruction set combines riscv32i and zicsr.
slot riscv32 : riscv32i, zicsr {
...
}
无需更改 zicsr
和 riscv32
槽定义。不过,
本教程的重点是向 riscv32i
添加必要的定义
。我们来详细了解一下此广告位中当前定义了什么:
// The RiscV 'I' instructions.
slot riscv32i {
// Include file that contains the declarations of the semantic functions for
// the 'I' instructions.
includes {
#include "learning/brain/research/mpact/sim/codelab/riscv_semantic_functions/solution/rv32i_instructions.h"
}
// These are all 32 bit instructions, so set default size to 4.
default size = 4;
// Model these with 0 latency to avoid buffering the result. Since RiscV
// instructions have sequential semantics this is fine.
default latency = 0;
// The opcodes.
opcodes {
fence{: imm12 : },
semfunc: "&RV32IFence"c
disasm: "fence";
ebreak{},
semfunc: "&RV32IEbreak",
disasm: "ebreak";
}
}
首先是 includes {}
部分,其中列出了需要
该广告位在被直接引用时添加到生成的代码中
在最终的 ISA 中包含文件也可列在
限定了 includes {}
部分,在这种情况下,始终包含这些部分。这可以
如果需要向每个广告位添加相同的 include 文件,会很方便
定义。
default size
和 default latency
声明定义了这一点,除非
则指令的大小为 4,而
目标操作数写入为 0 个周期。请注意,指令的大小
是计算
在模拟的
处理器。该值可能与
指令表示。
槽定义的核心是操作码部分。可以看到,其中只有两个
操作码(指令)fence
和 ebreak
目前已在
riscv32i
。fence
操作码通过指定名称 (fence
) 进行定义,
操作数规范 ({: imm12 : }
),后跟可选反汇编代码
格式 ("fence"
) 以及要作为语义内容绑定的 Callable 函数
函数 ("&RV32IFence"
)。
指令运算数指定为三元组,每个组成部分
predicate ':'源操作数列表 ':'
目标操作数列表:源操作数列表和目标操作数列表以英文逗号分隔
操作数名称的分隔列表。可以看到,
fence
指令包含(不含谓词运算数),只有一个源
操作数名称为“imm12
”,并且没有目标操作数。RiscV RV32I 子集可以
不支持谓词执行,因此谓词操作数将始终为空
示例。
语义函数被指定为指定 C++
函数或 Callable 来调用语义函数。该
语义函数/Callable 为 void(Instruction *)
。
反汇编规范由英文逗号分隔的字符串列表组成。
通常只使用两个字符串,一个用于操作码,一个用于
操作数。设置格式后(使用指令中的 AsString()
调用),每个
字段内的字符串根据 disasm widths
设置格式
。
以下练习可帮助您向 riscv32i.isa
文件添加说明
足以模拟“Hello World”计划。对于赶时间的人来说
解决方案,请参见
riscv32i.isa
和
rv32i_instructions.h。
执行初始构建
如果您尚未将目录更改为 riscv_isa_decoder
,请立即执行此操作。然后
按如下所示构建项目 - 此构建应该会成功。
$ cd riscv_isa_decoder
$ bazel build :all
现在将您的目录更改回代码库的根目录,
生成的源代码为此,请将目录更改为
bazel-out/k8-fastbuild/bin/riscv_isa_decoder
(假设您使用的是 x86)
host - 对于其他主机,k8-Fastbuild 将是另一个字符串)。
$ cd ..
$ cd bazel-out/k8-fastbuild/bin/riscv_isa_decoder
在该目录中,除其他文件外,还有以下文件: 生成的 C++ 文件:
riscv32i_decoder.h
riscv32i_decoder.cc
riscv32i_enums.h
riscv32i_enums.cc
我们在浏览器中点击 riscv32i_enums.h
来查看它。您应该
您会看到它包含如下内容:
#ifndef RISCV32I_ENUMS_H
#define RISCV32I_ENUMS_H
namespace mpact {
namespace sim {
namespace codelab {
enum class SlotEnum {
kNone = 0,
kRiscv32,
};
enum class PredOpEnum {
kNone = 0,
kPastMaxValue = 1,
};
enum class SourceOpEnum {
kNone = 0,
kCsr = 1,
kImm12 = 2,
kRs1 = 3,
kPastMaxValue = 4,
};
enum class DestOpEnum {
kNone = 0,
kCsr = 1,
kRd = 2,
kPastMaxValue = 3,
};
enum class OpcodeEnum {
kNone = 0,
kCsrs = 1,
kCsrsNw = 2,
kCsrwNr = 3,
kEbreak = 4,
kFence = 5,
kPastMaxValue = 6
};
constexpr char kNoneName[] = "none";
constexpr char kCsrsName[] = "Csrs";
constexpr char kCsrsNwName[] = "CsrsNw";
constexpr char kCsrwNrName[] = "CsrwNr";
constexpr char kEbreakName[] = "Ebreak";
constexpr char kFenceName[] = "Fence";
extern const char *kOpcodeNames[static_cast<int>(
OpcodeEnum::kPastMaxValue)];
enum class SimpleResourceEnum {
kNone = 0,
kPastMaxValue = 1
};
enum class ComplexResourceEnum {
kNone = 0,
kPastMaxValue = 1
};
enum class AttributeEnum {
kPastMaxValue = 0
};
} // namespace codelab
} // namespace sim
} // namespace mpact
#endif // RISCV32I_ENUMS_H
您可以看到,在
riscv32i.isa
文件在某个枚举类型中定义。此外,
OpcodeNames
数组用于存储操作码的所有名称(
在 riscv32i_enums.cc
中定义)。其他文件包含生成的解码器,
我们将在另一个教程中对此进行详细介绍
Bazel 构建规则
Bazel 中的 ISA 解码器目标使用名为
mpact_isa_decoder
,从 mpact/sim/decoder/mpact_sim_isa.bzl
加载
在 mpact-sim
代码库中。在本教程中,
riscv_isa_decoder/BUILD
为:
mpact_isa_decoder(
name = "riscv32i_isa",
src = "riscv32i.isa",
includes = [],
isa_name = "RiscV32I",
deps = [
"//riscv_semantic_functions:riscv32i",
],
)
此规则调用 ISA 解析器工具和生成器来生成 C++ 代码,
然后将生成的 编译到其他规则可以依赖的库中。
标签 //riscv_isa_decoder:riscv32i_isa
。includes
部分用于
用于指定源文件可能包含的其他 .isa
文件。通过
isa_name
用于指定具体的 ISA,如果有多个 ID,则需要
在要为其生成解码器的源文件中。
添加寄存器-寄存器 ALU 指令
现在,可以向 riscv32i.isa
文件中添加一些新指令了。第一个
这组指令是寄存器-注册 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 | 操作码 |
虽然 .isa
文件用于生成与格式无关的解码器,但它仍然
有助于考虑二进制格式及其布局来指导条目。您
可以看到,有三个字段与填充
指令对象:rs2
、rs1
和 rd
。此时,我们将选择使用
以相同方式编码的整数寄存器的名称(位序列),
所有指令中的相同指令字段中,
我们要添加的说明如下:
add
- 整数加法。and
- 按位和。or
- 按位或。sll
- 左移逻辑。sltu
- 设置小于号、无符号。sub
- 整数相减。xor
- 按位异或。
所有这些说明都会添加到opcodes
riscv32i
槽定义。回想一下,我们必须指定名称、操作码
以及每条指令的反汇编和语义函数。这个名字很简单
我们只使用上面的运算码名称。此外,它们都使用相同的操作数
我们可以使用 { : rs1, rs2 : rd}
作为运算数规范。这意味着
rs1 指定的寄存器源运算数在源中的索引为 0
指令对象中的运算数矢量,指定的寄存器源运算数
由 rs2 表示的寄存器将具有索引 1,由 rd 指定的寄存器目标操作数
将是目标操作数向量(位于索引 0 处)中的唯一元素。
接下来是语义函数规范。为此,您可以使用关键字
semfunc
和一个 C++ 字符串,用于指定可用于分配
转换为 std::function
。在本教程中,我们将使用函数,因此 Callable 函数
字符串为 "&MyFunctionName"
。使用
fence
指令,这些指令应为 "&RV32IAdd"
、"&RV32IAnd"
等。
最后是反汇编规范。它以关键字“disasm
”开头,
后跟逗号分隔的字符串列表,指定
指令应输出为字符串。在%
操作数名称指示使用以下字符串表示进行字符串替换:
操作数。对于 add
指令,应为:disasm: "add", "%rd,
%rs1,%rs2"
。也就是说,add
指令的条目应如下所示:
例如:
add{ : rs1, rs2 : rd},
semfunc: "&RV32IAdd",
disasm: "add", "%rd, %rs1, %rs2";
接下来修改 riscv32i.isa
文件,然后将所有这些说明添加到
.isa
说明。如果您需要帮助(或想查看您的工作成果),
说明文件
此处。
将说明添加到 riscv32i.isa
文件后,必须
为每个新语义函数添加函数声明,
引用的文件 rv32i_instructions.h
../semantic_functions/.再次提醒,如果你需要帮助(或想查看你的工作成果)
答案是
此处。
完成上述所有操作后,请切换回 riscv_isa_decoder
然后重新构建您随时可以检查生成的源文件。
使用即时指令添加 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 | 操作码 |
如您所见,运算数名称 rs1
和 rd
引用与
用于表示整数寄存器,因此这些名称可以
。立即值字段具有不同的长度和位置,
两个(uimm5
和 uimm20
)是无符号的,而 imm12
是有符号的。每个
它们将使用自己的名称
因此,I 型指令的操作数应为 { : rs1, imm12 :rd
}
。对于专用 I 型指令,它应为 { : rs1, uimm5 : rd}
。
U 类型指令运算数规范应为 { : uimm20 : rd }
。
我们需要添加的 I-Type 说明如下:
addi
- 立即添加。andi
- 按位立即,ori
- 按位或立即。xori
- 具有立即数的按位异或。
我们需要添加的专用 I-Type 指令如下:
slli
- 按逻辑顺序左移。srai
- 将算术数右移。srli
- 按立即数右移逻辑右移。
我们需要添加的 U-Type 说明如下:
auipc
- 向 PC 添加上部直接位置。lui
- 加载顶部立即。
用于操作码的名称自然地遵循指令名称
(无需构思新模板,这些都是独一无二的)。对于
指定语义函数,回想一下指令对象编码的是
与底层运算数无关的源运算数的接口
类型。这意味着,对于具有相同运算的指令,
可能因操作数类型而异,可以共用同一个语义函数。例如,
如果存在以下情况,addi
指令会执行与 add
指令相同的运算:
one 会忽略运算数类型,因此它们可以使用相同的语义函数
规范 "&RV32IAdd"
。对 andi
、ori
、xori
和 slli
也是如此。
其他指令使用了新的语义函数,但应将其命名
基于运算,而非运算数,因此对于 srai
,请使用 "&RV32ISra"
。通过
U 类型指令 auipc
和 lui
没有等效的寄存器,因此可以
以使用 "&RV32IAuipc"
和 "&RV32ILui"
。
反汇编字符串与上一个练习中的反汇编字符串非常相似,但
如您所料,对 %rs2
的引用被替换为 %imm12
、%uimm5
、
或 %uimm20
。
继续进行更改并构建。检查生成的输出。就像 以前,你可以对照 riscv32i.isa 和 rv32i_instructions.h。
添加分支和跳转和链接说明
我们需要添加的分支和跳转-链接指令都使用了目的地
仅隐含在指令本身中的操作数,即下一个 pc
值。在这一阶段,我们将这视为具有名称
next_pc
。我们将在后续教程中对此进行进一步定义。
分支说明
我们要添加的分支都使用 B 型编码。
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 | 操作码 |
不同的立即数字段会串联成 12 位有符号立即数
值。由于这种形式并非真正具有相关性,因此我们将其称为“立即”
bimm12
,表示立即启动 12 位分支。我们将在
下一个教程介绍了如何创建二进制解码器。所有
如果分支指令会比较 rs1 和 rs2 指定的整数寄存器
如果条件为 true,则立即值将与当前 pc 值相加,
生成要执行的下一个指令的地址。此
因此,分支指令应为 { : rs1, rs2, bimm12 : next_pc }
。
我们需要添加的分支指令包括:
beq
- 如果相等,则分支。bge
- 如果大于或等于,则分支。bgeu
- 大于或等于无符号的分支。blt
- 如果小于,则分支。bltu
- 无符号数小于的分支。bne
- 如果不相等,则分支。
这些操作码名称都是唯一的,因此可以在 .isa
中重复使用
说明。当然,必须添加新的语义函数名称,例如
"&RV32IBeq"
等
现在,反汇编规范有点复杂一些,因为
指令用于计算目的地,但实际上
指令运算数。不过,它是存储在
说明对象,因此该对象可用。解决方法是使用
反汇编字符串中的表达式语法。不再使用“%”后跟
运算数名称,您可以输入 %(expression: print format)。只是非常简单
支持表达式,但地址加偏移是其中之一,并且 @
符号。打印格式类似于
C 样式的 printf 格式,但没有前导 %
。以下代码的反汇编格式
beq
指令随后会变为:
disasm: "beq", "%rs1, %rs2, %(@+bimm12:08x)"
跳转和链接说明
只需添加两个跳转和链接指令,即 jal
(跳转-链接)和
jalr
(间接跳转和链接)。
jal
指令使用 J 类型编码:
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 | 操作码 |
与分支指令一样,20 位立即被分散在
所以我们将其命名为 jimm20
。分片并不重要
,但会在今后
创建二进制解码器的教程。操作数
规范之后会变为 { : jimm20 : next_pc, rd }
。请注意
目标操作数、下一个 pc 值和
指令。
与上面的分支说明类似,反汇编格式变为:
disasm: "jal", "%rd, %(@+jimm20:08x)"
间接跳转和链接使用具有 12 位立即数的 I-Type 格式。它
将符号扩展的立即值添加到由
rs1
用于生成目标指令地址。链接寄存器是
由 rd
指定的整数寄存器。
2020 年 31 月 20 日 | 19 月 15 日 | 12 月 14 日 | 11.7 | 6.0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | RD | 操作码 |
如果您看到过这个模式,现在可以推导出操作数规范
jalr
应为 { : rs1, imm12 : next_pc, rd }
,并且反汇编代码
规范:
disasm: "jalr", "%rd, %rs1, %imm12"
继续进行更改,然后进行构建。检查生成的输出。只是 与之前一样 riscv32i.isa 和 rv32i_instructions.h。
添加商店说明
商店说明非常简单。它们都使用 S-Type 格式:
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 | 操作码 |
如您所见,这是另一种分散的 12 位立即数,
将其命名为 simm12
。存储指令都存储整数的值
通过将 rs2 指定的寄存器添加到内存中的有效地址,
将 rs1 指定的整数寄存器的值转换为
12 位立即数。对于以下参数,操作数格式应为 { : rs1, simm12, rs2 }
:
所有商店说明
需要实现的商店说明如下:
sb
- 存储字节。sh
- 存储半个字词。sw
- 存储字词。
sb
的反汇编规范符合您的预期:
disasm: "sb", "%rs2, %simm12(%rs1)"
语义函数规范也符合预期:"&RV32ISb"
,
等等
继续进行更改,然后进行构建。检查生成的输出。只是 与之前一样 riscv32i.isa 和 rv32i_instructions.h。
添加加载说明
加载指令的建模方式与 模拟器。以便能够对加载延迟时间 加载指令分为两个独立的操作:1) 有效 以及 2) 结果回写。在 模拟器这是通过将加载的语义操作拆分为两部分来实现的, 主指令和子指令。此外, 当我们指定操作数时,需要同时为主操作数和 child 指令。为此,您可以将操作数规范视为 三元组列表。相关语法如下:
{(predicate : sources : destinations),
(predicate : sources : destinations), ... }
加载指令全部使用 I-Type 格式,与前面许多 操作说明:
2020 年 31 月 20 日 | 19 月 15 日 | 12 月 14 日 | 11.7 | 6.0 |
---|---|---|---|---|
12 | 5 | 3 | 5 | 7 |
imm12 | rs1 | func3 | RD | 操作码 |
运算数规范拆分计算地址所需的运算数
并从寄存器目标启动内存访问,以便加载数据:
{( : rs1, imm12 : ), ( : : rd) }
。
由于语义操作被拆分成两条指令,因此语义函数
同样需要指定两个 Callable 函数对于 lw
(加载字词),应为
写道:
semfunc: "&RV32ILw", "&RV32ILwChild"
反汇编规范更为传统。没有提及
子指令。对于 lw
,它应该是:
disasm: "lw", "%rd, %imm12(%rs1)"
需要实现的加载指令包括:
lb
- 加载字节。lbu
- 加载无符号字节。lh
- 加载半字。lhu
- 加载半字未签名。lw
- 加载字词。
继续进行更改,然后进行构建。检查生成的输出。只是 与之前一样 riscv32i.isa 和 rv32i_instructions.h。
感谢您取得今天的成就。希望以上内容对您有所帮助。