Riscv 学习笔记 - 指令格式

Riscv 学习笔记 - 指令格式

1395字6分钟


在上一篇文章中,我们介绍了RISC-V指令集架构的基本概念和指令集体系结构。有没有想过,这些指令是怎么编码成二进制的呢?

本文以32位指令集为例,将介绍RISC-V指令集架构的六种核心指令格式𝑅/𝐼/𝑈/𝑆/𝐵/𝐽

目录

RISC-V 基本指令格式

在RISC-V中,我们将每条指令译为32位二进制编码,而基本 ISA 中有五种指令格式:

可以直观地理解为跟据汇编指令的格式,将指令分为不同的类型,每种类型都有自己的编码格式。

不同类型的指令译码后的格式不完全相同,但基本的思想是一致的:

Note
  • 尽可能保持寄存器的编码位置不变,然后插入立即数。
  • 尽可能节省空间放更多的东西。

下面我们将介绍这五种指令格式。

“R” 类型指令

R 类型指令用于算术运算、逻辑运算、移位运算、比较运算等 寄存器-寄存器操作 ,算是最简单的类型。

add rd, rs1, rs2

funct731257rs224205rs119155funct314123rd1175opcode607

在小小的一个字(32位)内,我们放入了五个 字段field

字段含义
funct77位的立即数
rs22位的源寄存器
rs12位的源寄存器
funct33位的功能码
rd2位的目的寄存器
opcode6位的操作码

可以看到,rdrs1rs2 这三个字段分别占用了5位,刚好对应32个寄存器。

opcode 这里硬编码为0110011,表示这是一个 R 类型指令。funct3是功能码,用于区分不同指令,比如000对应add指令。

funct7 则用于表示一些比较特殊的操作,比如sub指令的funct70100000,这个1就是指要扩展符号位,后面的srlisrai就只有这里不同。

“I” 类型指令

I 类型指令用于加载等操作。

imm[11:0]312012rs119155funct314123rd1175opcode607

可以看到,I 类型指令的编码格式跟 R 类型指令的格式非常相似,只是把 funct7rs2 字段换成了 imm 字段,用于表示立即数。(imm一定是有符号的,不用考虑符号扩展的问题。) 以及这里的opcode0010011

需要注意的是,imm 字段的位宽为12位,也就是说,我们只能使用(-2048, 2047)的立即数,然后这个数会被扩展到32位。

没有lwu指令,因为没有位需要扩展。

“U” 类型指令

U 类型指令用于长立即数。

imm[31:12]311220rd1175opcode607

通过luiauipc指令,我们可以将高20位放入指定寄存器/pc中,然后再设置低位即可获得完整的32位立即数。

lui x10, 0xFEDC # x10 = 0xFEDC0000
addi x10, x10, 0x1234 # x10 = 0xFEDC1234
Note

addi指令中imm最高位为1时,addi 符号位扩展后与luiimm相加会导致后者减少1(想想为什么)。

lui x10, 0xDEADB # x10 = 0xDEADB000
addi x10, x10, 0xEFF # x10 = 0xDEADAEFF

所以这时要让luiimm加1。(很可惜没有addiu指令,不过我们可以使用li伪指令来直接设置长立即数。)

“S” 类型指令

S 类型指令用于存储操作,例如将寄存器中的数据存储到内存中。

imm[11:5]31257rs224205rs119155funct314123imm[4:0]1175opcode607

这里很有意思的一点是,imm被分成了两部分,高位为 imm[11:5],低位为 imm[4:0]。还记得一开始我们说过,尽可能保持寄存器的编码位置不变,然后放入立即数。 就是这个意思。

“B” 类型指令

B 类型指令用于有条件跳转指令。

imm[12|10:5]31257rs224205rs119155funct314123imm[4:1|11]1175opcode607

这里的 imm 字段切得更碎了,高位为 imm[12|10:5],低位为 imm[4:1|11]。有没有发现什么东西消失了?

指令是如何用imm来表示跳转地址的?我们知道RISC-V使用pc来表示当前程序的位置,当正常执行时,完成一条指令,pc就会自动加4,即偏移量为一个字的长度,指向下一条指令的开头位置。

pc = pc + 4;

但当在分支跳转时,我们指定了rs2及偏移量,这个偏移量显然不能乱取,使pc指向指令中间的位置导致可怕的结果,故偏移量必须为4的倍数。同时我们又要尽可能节省空间放更多的东西,不如让偏移量为imm的4倍。

pc = pc + imm * 4;

但实际上,为了兼容16位的指令集,工程师们让偏移量为2的倍数,即:

pc = pc + imm * 2;

偏移量是2的倍数,即最低位一定是0,故我们可以直接把最低位丢掉,这也是上图中没有imm[0]的原因。

Note
  1. jalr 需要储存两个寄存器,所以使用的是R format,而imm字段从0开始储存。
  2. 使用
auipc x1, <hi20bits>
jalr x2, x1, <lo12bits>

来实现32位范围的跳转。

“J” 类型指令

J 类型指令用于无条件跳转指令。这个简单。

imm[20|10:1|11|19:12]311220rd1175opcode607

总结

313029282726252423222120191817161514131211109876543210imm[20|10:1|12|19:12]rdopcodeR-typeimm[31:12]rdopcodeU-typeimm[11:0]rs1funct3rdopcodeI-typeimm[11:5]rs2rs1funct3imm[4:0]opcodeS-typeimm[12|10:5]rs2rs1funct3imm[4:1|11]opcodeB-typeimm[20|10:1|11|19:12]rdopcodeJ-type