RISC-V 指令格式

先贴几个图

  • RV32I 指令图

D35C054C-DCDE-4C29-BDC1-6637C915952B

  • 下面是具体的指令格式

83904B69-0871-4150-98DE-1F0EA0877D17

  • 下面是指令格式展开的格式

1B9BA357-6712-4A2D-B287-FE774DF41B4F

上面几张图可以在包云岗老师翻译的 RISC-V book 里面找到。

图 2.2 显示了六种基本指令格式,分别是:用于寄存器-寄存器操作的 R 类型指令,用于短立即数和访存 load 操作的 I 型指令,用于访存 store 操作的 S 型指令,用于条件跳转操作的 B 类型指令,用于长立即数的 U 型指令和用于无条件跳转的 J 型指令。图 2.3 使用图 2.2 的指令格式列出了图 2.1 中出现的所有 RV32I 指令的操作码.

为了帮助程序员,所有位全部是 0 是非法的 RV32I 指令。因此, 试图跳转到被清零的 内存区域的错误跳转将会立即触发异常,这可以帮助调试。类似地,所有位全部是 1 的指 令也是非法指令,它将捕获其他常见的错误,诸如未编程的非易失性内存设备、断开连接 的内存总线或者坏掉的内存芯片。

R-Format Instruction

R-Format 用于寄存器-寄存器操作

98C5AAD5-6084-4061-9F60-2C4713515ACE

I-Format Instruction

用于短立即数和访存 load 操作的 I 型指令

28CB5742-AD12-4C86-8AA8-7E8DCA8EF12E

很重要的是,RISC-V 所有的立即数都是 signed 的。例如:

6415DC2D-0A51-49C5-B1AE-606145CEB81F

除了这种 add imm. load 操作也是 I 型的:

02D8BD3A-6757-4E0F-B976-A0B66ED9A93B

Note: if instruction has immediate, then uses at most 2 registers (one source, one destination)

load 的模式通常是 load rd, rs 或者 load rd, imm(rs).

S-Format

需要保存2个 Register 和一个小的 imm:

5890A79A-BC93-4D19-AC79-447D0273E059

RISC-V design decision is move low 5 bits of immediate to where rd field was in other instructions – keep rs1/rs2 fields in same place.

因为 store 只是把寄存器的存到内存中,所以没有“目标寄存器”。


以上指令中,你会发现,RISC-V 希望 rs1 rs2 rd 三个寄存器 存在的话即有相同的位置

  • By always placing the read sources in the same place, the register file can read without hesitation. If the data ends up being unnecessary (e.g. I-Type), it can be ignored

B-Format 和 Label

1
BEQx1,x2,Label

上面是一个很正常的跳转指令,但是本身 .label 如何在机器码上存在呢?

在 RISC-V 中,一切都是有内存位置的,包括指令,那么跳转到对应指令的内存即可。在这里,因为 .code 端本身占用内存的大小有限,所以这里使用 PC-Relative Addressing:

  • 使用 imm 字段,表示 PC 的二进制补码偏移量。(Could specify ± 211 addresses offset from the PC)
  • To improve the reach of a single branch instruction, in principle, could multiply the offset by four bytes before adding to PC (instructions are 4 bytes and word aligned). 因为我们的 RV32I 都是 32bits 的,而且是 4bytes align 的,所以可以把偏移量 * 32
  • This would allow one branch instruction to reach ± 211 × 32-bit instructions either side of PC

但是,考虑压缩指令,即扩展的 16-bit instructions,RISC-V 需要支持的还有 16bits 的扩展指令。

  • To enable this, RISC-V always scales the branch offset by 2 bytes - even when there are no 16-bit instructions

所以需要处理 size, 不能 *32,

在 RISC-V 中,conditional branch 逻辑如下:

  • 如果跳转成功了,PC = PC + imm, imm 是二进制补码,是 signed 的
  • 否则,PC = PC + 4

最后,指令的支持形式如下

8E9E3C64254443AF66A352BF0DD83DB4

7ACB61F8-C66F-4612-9DBD-1F7095D2CE9E

下面给了一个例子:

36F645F0-136D-449D-BCCF-97C8ED834226

以上是 16bytes,而我们有 imm[12], 偏移量相当于乘了 byte,同时预留了一个虚拟 0 bit, 相当于 *2byte, 实在不够,会使用 branch + jal 的方式支持。

U-Format

用于长立即数的 U 型指令

04E9659D-BA95-4219-ACF8-A031BD9F7472

  • AUIPC – Add Upper Immediate to PC
  • LUI – Load Upper Immediate

注意这俩对应的都是高位。可以用 lui + addi 构造 32位的数(毕竟立即数不够大)。

需要注意的是,addi 指令中,写入 imm[11:0], 看上去和 lui 没有冲突,但是如果构造 0xDEADBEEF这种,因为这辆都是二进制补码,需要处理低12位中,imm[11] 是 1的问题,这会导致整个结果成一个负数。

这里我们要回到二进制补码的特点:加一个负数的时候加法仍然是按位加的,但是进位是要从高位 -1 的 。这里需要高位再 +1 再处理。

80146EE9-9531-455F-86F9-1C7BBB41A9A8

J-Format

下面是 JAL 和 JALR

BECEBBA9-B72D-4E25-A767-DD54718C7AA5

jal 根据 imm 来跳跃,rd 存储。这个 jal 中遵循和 branch 中一样的测试,即 *2byte

96F9098A-5CC8-4041-8448-92AAFAA92718

jalr 中,把结果存到寄存器中, 注意,这回儿它就不 *2byte