3.2 RISC-V指令编码格式
RISC-V每条指令的宽度为32位(不考虑压缩扩展指令),包括RV32指令集以及RV64指令集。指令编码格式大致可分成如下6类。
● R类型:寄存器与寄存器算术指令。
● I类型:寄存器与立即数算术指令或者加载指令。
● S类型:存储指令。
● B类型:条件跳转指令。
● U类型:长立即数操作指令。
● J类型:无条件跳转指令。
RISC-V指令集编码格式如图3.1所示。
图3.1 RISC-V指令集编码格式
指令编码可以分成如下几个部分。
● opcode(操作码)字段:位于指令编码Bit[6:0],用于指令的分类。
● funct3和funct7(功能码)字段:常常与opcode字段结合在一起使用,用来定义指令的操作功能。
● rd字段:表示目标寄存器的编号,位于指令编码的Bit[11:7]。
● rs1字段:表示第一源操作寄存器的编号,位于指令编码的Bit[19:15]。
● rs2字段:表示第二源操作寄存器的编号,位于指令编码的Bit[24:20]。
● imm:表示立即数。RISC-V中使用的立即数大部分是符号扩展(sign-extended)的立即数。
RV64指令集支持64位宽的数据和地址寻址,为什么指令的编码宽度却只有32位[1]?
[1]RISC-V通常使用32位定长指令,不过为了减少代码量,也支持16位的压缩扩展指令。
因为RV64指令集是基于寄存器加载和存储的体系结构设计,所以所有的数据加载、存储以及处理都是在通用寄存器中完成的。RISC-V一共有32个通用寄存器(通用寄存器都是64位宽的,可以处理64位宽的数据),即x0~x31,其中,x0寄存器的编号为0,以此类推。因此,指令编码使用5位宽(25 = 32),即索引32个通用寄存器。
对于精简指令集来说,大部分运算指令最多采用3个寄存器来表示,例如加法指令add rd, rs1, rs2,那么在指令编码中只需要15个位宽就可以索引3个通用寄存器,剩余的位宽可用于指令分类等。因此32位宽的指令编码已经绰绰有余,使用32位宽指令编码比使用64位宽指令编码更有利于提高指令密度,减小代码尺寸,从而提高指令高速缓存的命中率,提升程序执行效率。
加载和存储指令通常采用12位立即数加上2个通用寄存器的方式来表示,下面以lw加载指令为例说明其指令编码的布局,如图3.2所示。
图3.2 lw加载指令的编码
● 第0~6位为opcode字段,用于指令的分类。
● 第7~11位为rd字段,用来描述目标寄存器rd的编号,可以从x0~x31通用寄存器中选择。
● 第12~14位为功能码字段,在加载指令中表示加载数据的位宽。
● 第15~19位为基地址rs1,可以从x0~x31通用寄存器中选择。
● 第20~31位为offset字段,表示偏移量。
RV64指令集中常用的符号说明如下。
● rd:表示目标寄存器的编号,可以从x0~x31通用寄存器中选择。
● rs1:表示源寄存器1的编号,可以从x0~x31通用寄存器中选择。
● rs2:表示源寄存器2的编号,可以从x0~x31通用寄存器中选择。
● ():通常用来表示寻址模式,例如,(a0)表示以a0寄存器的值为基地址进行寻址。这个前面还可以加offset,表示偏移量,可以是正数或负数。例如,8(a0)表示以a0寄存器的值为基地址,偏移8字节进行寻址。
● { }:表示可选项。
● imm:表示有符号立即数。