Riscv 学习笔记 - 指令格式
1395字6分钟
在上一篇文章中,我们介绍了RISC-V指令集架构的基本概念和指令集体系结构。有没有想过,这些指令是怎么编码成二进制的呢?
本文以32位指令集为例,将介绍RISC-V指令集架构的六种核心指令格式。
目录
RISC-V 基本指令格式
在RISC-V中,我们将每条指令译为32位二进制编码,而基本 ISA 中有五种指令格式:
- 类型指令:用于算术运算、逻辑运算、移位运算、比较运算等。
- 类型指令:用于加载立即数等。
- 类型指令:用于存储指令,用于将数据从存储器中读出或写入到存储器中。
- 类型指令:用于有条件跳转指令。
- 类型指令:用于无条件跳转指令。
可以直观地理解为跟据汇编指令的格式,将指令分为不同的类型,每种类型都有自己的编码格式。
不同类型的指令译码后的格式不完全相同,但基本的思想是一致的:
- 尽可能保持寄存器的编码位置不变,然后插入立即数。
- 尽可能节省空间放更多的东西。
下面我们将介绍这五种指令格式。
“R” 类型指令
R 类型指令用于算术运算、逻辑运算、移位运算、比较运算等 寄存器-寄存器操作 ,算是最简单的类型。
add rd, rs1, rs2
在小小的一个字(32位)内,我们放入了五个 字段field :
字段 | 含义 |
---|---|
funct7 | 7位的立即数 |
rs2 | 2位的源寄存器 |
rs1 | 2位的源寄存器 |
funct3 | 3位的功能码 |
rd | 2位的目的寄存器 |
opcode | 6位的操作码 |
可以看到,rd
,rs1
,rs2
这三个字段分别占用了5位,刚好对应32个寄存器。
opcode
这里硬编码为0110011
,表示这是一个 R
类型指令。funct3
是功能码,用于区分不同指令,比如000
对应add
指令。
funct7
则用于表示一些比较特殊的操作,比如sub
指令的funct7
为0100000
,这个1
就是指要扩展符号位,后面的srli
和srai
就只有这里不同。
“I” 类型指令
I 类型指令用于加载等操作。
可以看到,I
类型指令的编码格式跟 R
类型指令的格式非常相似,只是把 funct7
和 rs2
字段换成了 imm
字段,用于表示立即数。(imm
一定是有符号的,不用考虑符号扩展的问题。)
以及这里的opcode
是0010011
。
需要注意的是,imm
字段的位宽为12位,也就是说,我们只能使用(-2048, 2047)的立即数,然后这个数会被扩展到32位。
lwu
指令,因为没有位需要扩展。“U” 类型指令
U 类型指令用于长立即数。
通过lui
和auipc
指令,我们可以将高20位放入指定寄存器/pc
中,然后再设置低位即可获得完整的32位立即数。
lui x10, 0xFEDC # x10 = 0xFEDC0000
addi x10, x10, 0x1234 # x10 = 0xFEDC1234
当addi
指令中imm
最高位为1时,addi
符号位扩展后与lui
中imm
相加会导致后者减少1(想想为什么)。
lui x10, 0xDEADB # x10 = 0xDEADB000
addi x10, x10, 0xEFF # x10 = 0xDEADAEFF
所以这时要让lui
中imm
加1。(很可惜没有addiu
指令,不过我们可以使用li
伪指令来直接设置长立即数。)
“S” 类型指令
S 类型指令用于存储操作,例如将寄存器中的数据存储到内存中。
这里很有意思的一点是,imm
被分成了两部分,高位为 imm[11:5]
,低位为
imm[4:0]
。还记得一开始我们说过,尽可能保持寄存器的编码位置不变,然后放入立即数。 就是这个意思。
“B” 类型指令
B 类型指令用于有条件跳转指令。
这里的 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]
的原因。
jalr
需要储存两个寄存器,所以使用的是R
format,而imm
字段从0开始储存。- 使用
auipc x1, <hi20bits>
jalr x2, x1, <lo12bits>
来实现32位范围的跳转。
“J” 类型指令
J 类型指令用于无条件跳转指令。这个简单。