一起学RISC-V汇编第4讲之指令格式
一起学RISC-V汇编第4讲之指令格式
RISC-V指令集的一个特点就是指令是定长的,对于RV32I ISA,一共有47条指令,有6种指令指令格式,分别为R/I/S/B/U/J类型,其中:
- R型用于寄存器-寄存器间的操作(10条)
- I型用于短立即数和访存(Load)操作(25条)
- S型用于访存Store操作(3条)
- B型用于条件跳转(6条)
- U型用于长立即数(2条)
- J型用于无条件跳转(1条)
格式如下图所示:
从图中可以看出RV32I指令集:
- 指令的长度固定为32位,以4字节边界对齐;
- 源寄存器rs1、rs2,以及目标寄存器rd的位置保持不变;
- RISC-V的指令编码风格是操作码(opcode)放在低位,操作码占7bit;
- 立即数的符号位放置在最高位(31位),方便进行符号扩展;
- RISC-V 指令格式为三操作数或者两操作数,操作数只能为立即数或寄存器;
- B类型分支跳转指令是在S类型基础上将立即数进行了旋转,J类型跳转指令是在U类型基础上将立即数进行了旋转,所以也可以认为RISC-V仅有4种指令类型;
- B型分支指令和J型跳转指令的地址需要左移1位(相当于乘以2),以获得更大的跳转范围。意味着:B型分支指令和J型跳转指令实际上只能跳转到偶数地址。
下面分别进行讲解:
1 RISC-V指令格式
1.1 R-type
R-type是最常用的指令类型,表示寄存器-寄存器操作,指令的操作由7bit的opcode、3位的func3、7位的func7 共同决定的(func3字段正好3位,func7字段正好7位);R-type包含有三个寄存器(两个源寄存器rs1与rs2,一个目的寄存器rd),寄存器位宽为5bit,可以寻址32个寄存器(指令集定义的通用寄存器个数2^5),由这些小细节可见,RISC-V指令集经过了巧妙的设计。
RV32I一共有10条R-type指令:
其详细编码如下:
以上指令的使用形式为:
# 加(算术指令)
ADD rd, rs1, rs2
# 减(算术指令)
SUB rd, rs1, rs2
# 逻辑左移(移位指令)
SLL rd, rs1, rs2
# 小于则置位(比较-置位)
SLT rd, rs1, rs2
# 无符号小于则置位(比较-置位)
SLTU rd, rs1, rs2
# 异或(逻辑指令)
XOR rd, rs1, rs2
# 逻辑右移(移位指令)
SRL rd, rs1, rs2
# 算术右移 (移位指令)
SRA rd, rs1, rs2
# 或(逻辑指令)
OR rd, rs1, rs2
# 与(逻辑指令)
AND rd, rs1, rs2
1.2 I-type
I-type指令有两个寄存器和一个立即数,相当于将R-type指令格式中的一个寄存器改为立即数,立即数一共12位,也即支持有符号数[-2028, 2017],如果是无符号数范围为[0-4096];指令的操作由7bit的opcode、3位的func3来决定。
RV32I一共有25条I-type指令,这里列出15条,分为9条短立即数操作,5条load取数操作,以及1条跳转操作。其它10条I-type指令为CSR读写指令以及环境指令等,后续会讲到。
可见:
- 短立即数操作为1.1中 R-type中的指令中的rs2寄存器换成立即数,表示寄存器与立即数进行计算,结果存到目的寄存器里;
- load取数操作中,rs1为寄存器,立即数为偏移地址,表示寄存器相对寻址。
9条短立即数操作:
以上指令的使用形式为:
# 加立即数(算术指令)
ADDI rd, rs1, imm
# 小于立即数则置位(比较-置位)
SLTI rd, rs1, imm
# 无符号小于立即数则置位(比较-置位)
SLTIU rd, rs1, imm
# 异或立即数(逻辑指令)
XORI rd, rs1, imm
# 或立即数(逻辑指令)
ORI rd, rs1, imm
# 与立即数(逻辑指令)
ANDI rd, rs1, imm
# 逻辑左移立即数(移位指令)
SLLI rd, rs1, shamt
# 逻辑右移立即数(移位指令)
SRLI rd, rs1, shamt
# 算术右移立即数 (移位指令)
SRAI rd, rs1, shamt
注意:riscv中没有SUBI指令,SUBI可以由ADDI来实现(减一个数等于加一个负数)。
5条load取数操作:
以上指令的使用形式为:
# 取字节(取指令)
LB rd, imm(rs1)
# 取半字(取指令)
LH rd, imm(rs1)
# 取字(取数令)
LW rd, imm(rs1)
# 取无符号字节(取指令)
LBU rd, imm(rs1)
# 取无符号半字(取指令)
LHU rd, imm(rs1)
RV32I为什么没有LWU指令?实际上LWU属于RV64I的指令,LB,LH 都是load数到32位寄存器里,由于寄存器比要取的数宽,所以存在符号扩展,在RV64I架构,寄存器为64bit的,LW才存在符号扩展,所以LWU属于RV64I的指令。
1条跳转操作:
这条指令的使用形式为:
# 寄存器跳转并链接(跳转指令)
JALR rd, imm(rs1)
1.3 S-type
S-type指令操作由7bit的opcode、3位的func3来决定,S-type指令没有目的寄存器,一般表示访存的store操作,如sw,sd等。指令中包含两个源寄存器(rs1,rs2)与一个12位立即数(imm[31:25]与imm[11:7],这么样拆分目的在于尽量固定rs1,rs2 位域的位置,方便译码)
RV32I一共有3条S-type指令,实现的同样是寄存器相对寻址:
以上指令的使用形式为:
# 存字节(存指令)
SB rs2, imm(rs1)
# 存半数(存指令)
SH rs2, imm(rs1)
# 存字(存指令)
SW rs2, imm(rs1)
1.4 B-type
B-type指令操作由7bit的opcode、3位的func3来决定;指令中包含两个源寄存器(rs1,rs2)与一个12位立即数,B-typed 一般表示条件跳转操作指令(分支指令),如相等(beq)、不相等(bne)、大于等于(bge)以及小于(blt)等跳转指令。
RV32I一共有6条B-type指令:
以上指令的使用形式为:
# 相等时分支(分支指令)
BEQ rs1, rs2, imm
# 不等时分支(分支指令)
BNE rs1, rs2, imm
# 小于时分支(分支指令)
BLT rs1, rs2, imm
# 大于等于时分支(分支指令)
BGE rs1, rs2, imm
# 无符号小于时分支(分支指令)
BLTU rs1, rs2, imm
# 无符号大于等于时分支(分支指令)
BGET rs1, rs2, imm
为什么没有小于等于、大于这些分支指令?
实际上if (a <= b),可以转换为 if (b >= a) , 所以只需交换操作数即可,无需专门的指令,riscv中小于等于、大于等分支指令都是伪指令,后续伪指令章会讲到。
1.5 U-type
U-type指令操作仅由7位opcode决定;指令包括一个目的寄存器rd和20位的立即数,U-type一般表示长立即数操作,例如 lui 指令,将立即数左移 12 位,并将低 12 位置零,结果写回目的寄存器中。
RV32I一共有2条U-type指令:
以上指令的使用形式为:
# 装入高位立即数(算术指令)
LUI rd, imm
# PC加高位立即数(算术指令)
AUIPC rd, imm
看这个指令的描述,可以与I-type的短立即数(12bit)配合,得到一个完整的32bit立即数。
1.6 J-type
J-type指令操作仅由7位opcode决定,与U-type一样只有一个目的寄存器rd和20位的立即数,但是立即数的位域与U-type的组成不同,J-type一般用于无条件跳转,如jal指令,RV32I一共有1条J-type指令。
以上指令的使用形式为:
# 跳转(跳转指令)
JAL rd, imm
2 RISC-V是如何译码的?
通过opcode来得到指令类型,RISC-V 规范中给出了如下图所示的 opcode 表格(适用于 RV32G 和 RV64G):
opcode一共7位,低两bit固定位1(inst[1:0]=0b11),根据inst[6:5] 与 nst[4: 2]可查表。根据指令类型,可以判断得到是属于R/I/S/B/U/J中的某一类型,然后按照其格式进行拆分译码,例如:如果是R-type,进一步拆分func3和func7字段。
如表中对应关系为:
指令类型 | 对应opcode map中的类型 |
---|---|
R类型 | OP |
I类型 | OP-IMM、LOAD、JALR |
S类型 | STORE |
B类型 | BRANCH |
U类型 | AUIPC、LUI |
J类型 | JAL |
详情可以参考芯来开源的蜂鸟解码实现:
https://github.com/riscv-mcu/e203_hbirdv2/blob/master/rtl/e203/core/e203_exu_decode.v
参考: