RISC-V 指令格式和6种基本整数指令
指令格式是指令使用二进制编码表示的结构形式,一般一条指令分为操作码和地址码两部分:
一、操作码
操作码表示指令的属性功能和执行的指令类型。操作数对应的二进制位数决定了计算机能够实现的最大指令数目。例如,操作码是 7 位的二进制码,计算机能够实现的最大指令数目是 128(2^7)。操作码根据二进制位数是否具有可变性将其细分为固定长度和可变长度两种。
-
固定长度操作码就是保持二进制位数不变,所有指令的操作码长度都等于某一固定值,并且它们必须在一个字段中。它的优点主要有:规整的指令格式,简化的底层硬件设计,非常短的指令译码时间,提高了计算机系统的整体性能。但是指令的编码效率低,具有很大的信息的冗余度,使代码的总长度大大增加,可扩展性差。RISC- V 采用的方案就是固定长度操作码。
-
可变长度操作码,也被称作扩展操作码,它是对相关操作码的扩展,可以对操作码的二进制长度进行改变和重新设置,并且可同时存在于多个字段中。可变长度的操作码通常会将特定的字段作为对应的操作码,而剩余的指令部分,会使用扩展的方式对应到地址码。
二、地址码
地址码的功能是不同于操作码的,地址码的主要功能是用来表明指令操作的对象,获得被操作对象的地址、操作数的存储地址以及寄存设备地址等。地址码的结构比操作码要更复杂的多,主要包括存储源操作数的地址、存储结果的目的操作数地址、以及将要跳转链接的下一条指令的地址。下一条指令的地址一般都是隐式存在的,必须通过计算分析才能够得到。根据一条指令中操作数地址数量,将地址码划分成以下几种类型。
1、零地址码
零地址码的指令,仅仅只有一个操作码而没有地址码,其指令格式如下图所示,op代表操作码。零地址码指令无须先设置对应的操作数,这有可能是事先已经设置好了操作数。例如空操作、停机等指令。
2、一地址码
一地址码的指令,只有一个存储操作数的地址,也是最终处理结果的保存地址, 它也被称作单操作数指令。其指令格式如下图所示,op 代表操作码,rs1 代表操作数地址。例如自增运算,在处理完该地址的操作数之后,将最终处理结果存回原来的地址中。
3、二地址码
二地址码的指令,也被称作双操作数指令,是当前阶段计算机系统中应用最广泛的。其指令格式如下图所示,op 是操作码,rs1 表示源操作数地址和最终处理结果的存储目的地址,rs2 表示另外一个源操作数地址。
4、三地址码
三地址码的指令,具有三个地址存放操作数。指令格式如下图所示,op 代表操作码,rs1 代表源操作数地址,rs2 代表另外一个源操作数地址,rd 代表目的操作数地址即存放处理结果的地址。
三、RISC-V六种基本指令格式
RISC-V 指令格式是一个典型的三操作数,7 位操作码的指令格式。RISC-V 指令集具有六种基本指令格式(R/I/S/B/U/J),如下图所示:
- 其中 opcode 表示 7 位指令操作码,其作用是区分不同的指令;
- funct3 表示 3 位 的功能码,funct7 表示 7 位的功能码,它们可以辅助区分不同种类的指令;
- rs1 和 rs2表示两个 5 位的源寄存器;
- rd 是 5 位的目的寄存器,指令运算的结果就存储 rd 中;
- imm 代表不同长度的立即数,可直接作为操作数使用。
6 种基本指令格式具体介绍如下:
1、R-typed
R-typed 指令是最常用的运算指令,具有三个寄存器地址,每个都用 5bit 的数表示。指令的操作由 7 位的 opcode、7 位的 funct7 以及 3 位的 funct3 共同决定的。R-typed 是不包含立即数的所有整数计算指令,一般表示寄存器-寄存器操作的指令。
2、I-typed
I-typed 具有两个寄存器地址和一个立即数,其中一个是源寄存器 rs1,一个是目的寄存器 rd,指令的高 12 位是立即数。指令的操作仅由 7 位的 opcode 和 3 位的funct3两者决定。值得注意的是,在执行运算时需要先把 12 位立即数扩展到 32 位之后再进行运算。I-typed 指令相当于将 R-typed 指令格式中的一个操作数改为立即数。一般表示短立即数和访存 load 操作的指令。
3、S-typed
S-typed 的指令功能由 7 位 opcode 和 3 位 funct3 决定,指令中包含两个源寄存器和指令的imm[31:25]和 imm[11:7]
构成的一个12位的立即数,在执行指令运算时需要把12 位立即数扩展到 32 位,然后再进行运算,S-typed 一般表示访存 store 操作指令,如存储字(sw)、半字(sh)、字节(sb)等指令。
4、B-typed
B-typed 的指令操作由 7 位 opcode 和 3 位 funct3 决定,指令中具有两个源寄存器和一个 12 位的立即数,该立即数构成是指令的第32位是 imm[12]、第7位是imm[11]、25 到 30 是 imm[10:5]、8 到 11 位是 imm[4:1],同样的,在执行运算时需要把12 位立即数扩展到 32 位,然后再进行运算。B-typed 一般表示条件跳转操作指令,如相等(beq)、不相等(bne)、大于等于(bge)以及小于(blt)等跳转指令。
5、U-typed
U-typed 的指令操作仅由 7 位 opcode 决定,指令中包括一个目的寄存器 rd 和高20 位表示的 20 位立即数。U-typed 一般表示长立即数操作指令,例如 lui 指令,将立即数左移 12 位,并将低 12 位置零,结果写回目的寄存器中。
6、J-typed
J-typed 的指令操作由 7 位 opcode 决定,与 U-typed 一样只有一个目的寄存器 rd和一个 20 位的立即数,但是 20 位的立即数组成不同,即指令的 31 位是 imm[20]、 12 到 19 位是 imm[19:12]、20 位是 imm[11]、21 到 30 位是 imm[10:1],J-typed 一般表示无条件跳转指令,如 jal 指令。
从基本指令格式可知,RISC-V 是具有高性能低功耗的更简洁的指令集架构设计。
- 首先,RISC-V 指令仅有以上 6 种基本指令格式,并且每个指令长度都是 32 位的,不像 X86-32 和 ARM-32 那样具有很多指令格式,这大大缩短了指令的解码时间。
- 第二,RISC-V 指令格式具有三个寄存器地址,不像 X86 那样使源操作数和目的操作数共享一个地址,它无须多使用一条 move 指令来完成存储目的寄存器的值。
- 第三,对于所有的 RISC-V 指令,其读写的寄存器标识符需要存放在同一位置,这使得指令在执行解码操作前,就能提前访问寄存器的值。
- 第四,指令格式中的立即数总是符号扩展的,并且指令的最高位是符号位,因此可以在解码前执行立即数的符号扩展操作。
这说明RISC-V 是优秀的、简洁的指令集架构,因此,RISC-V 指令集架构将会不断地发展,逐渐成为最主流的指令集架构。
四、6种基本整数指令集
RISC-V 基本整数指令集是每个专用指令集所必须具有的,其主要是为了保证基本的功能要求和所有的程序正常运行,以 32 位为例具体介绍如下:
1. 算数运算指令
RISC-V 的 32 位基础整数指令集(RV32I)具有 7 条算数运算指令,分别是 ADDI、 SLTI、SLTIU、ADD、SUB、SLT 和 SLTU
。它们的指令格式如下图所示:
算数运算指令使用两种类型的指令格式,一种是寄存器-立即数操作的 I-typed 指令格式,一种是寄存器-寄存器操作的 R-typed 指令格式。对于算数运算指令的两种指令格式,都具有目标寄存器 rd。R-typed 是 add、sub、slt 和 sltu,I-typed 是 addi、slti和 sltiu。
addi
的功能是将立即数和 rs1 相加并写入 rd 中;add
是将 rs1 和 rs2 相 加并写入 rd 中;sub
是 rs1 减去 rs2 并写入 rd 中;slt 和 sltu
是有符号和无符号的比较指令,即 rs1 小于 rs2 则置 1,否者置 0;slti 和 sltiu
的功能是 rs1 小于立即数则置 1,否者置 0。
2. 移位指令
RV32I 具有 6 条移位指令,分别是 SLLI、SRLI、SRAI、SLL、SRL 和 SRA
,其指令格式如下图所示,其中 shamt 代表偏移量也就是移位量。
移位指令也是使用 R-typed 和 I-typed 两种指令格式,R-typed 的是 sll、srl 和 sra, I-typed 的是 slli、srli 和 srai。
slli
的功能是立即数逻辑左移,rs1 左移 shamt 位,空位填 0 并写入 rd 中;srli
是立即数逻辑右移,rs1 右移 shamt 位,空位填 0 并写入rd 中;srai
立即数算数右移,rs1 右移 shamt 位,空位填 rs1 的最高位并写入 rd 中;sll
是逻辑左移,rs1 左移 rs2 位,空位填 0 并写入 rd 中;srl
是逻辑右移,rs1 右移 rs2位,空位填 0 并写入 rd 中;sra
是算数右移,rs1 右移 rs2 位,空位填 rs1 的最高位并写入 rd 中。
3. 逻辑操作指令
RV32I 具有 6 条逻辑操作指令,分别是 XORI、ORI、ANDI、XOR、OR 和 AND
,其指令格式如下图所示:
逻辑操作指令也是使用 R-typed 和 I-typed 指令格式,R-typed 为 xor、or 和 and, I-typed 为 xori、ori 和 andi。
xori
是立即数异或,rs1 和立即数按位异或并写入 rd中;ori
是 rs1 和立即数按位取或并写入 rd 中;andi
是 rs1 和立即数位与并写入 rd 中;xor
异或,rs1 和 rs2 按位异或并写入 rd 中;or
是 rs1 和 rs2 按位取或并写入 rd 中;and
是 rs1 和 rs2 位与并写入 rd 中。
4. 加载和存储指令
RV32I 具有 8 条加载和存储指令,分别是 LB、LH、LW、LBU、LHU、SB、SH 和 SW
,其指令格式如下图所示:
RV32I 是一个加载-存储结构,只有加载-存储类指令可以访问存储器,在寄存器
和存储器之间进行数据传输。加载类指令使用的是 I-typed 指令格式,存储类指令使
用的是 S-typed 指令格式。
lb
是字节加载,读取一个字节写入 rd 中;lh
是半字加载,读取两个字节写入 rd 中;lw
是字加载,读取四个字节写入 rd 中;lbu
是无符号字节加载,读取一个字节写入 rd 中;lhu
是无符号半字加载,读取两个字节写入 rd中;sb
是存字节,把 rs2 的低位一字节存入地址 rs1+立即数中;sh
是存半字,把 rs2的低位两字节存入地址 rs1+立即数中;sw
是存字,把 rs2 的低位四字节存入地址 rs1+立即数中。
5. 控制转移指令
RV32I 具有 8 条控制转移指令,分别是 BEQ、BNE、BLT、BGE、BLTU、BGEU、 JAL 和 JALR
,其指令格式如下图所示:
控制转移指令分为条件分支跳转和无条件跳转链接两类指令,条件分支跳转使用
的是 B-typed 格式,无条件跳转中 jalr 和 jal 使用的分别是 I-typed 格式和 J-typed 格
式。
beq
是相等条件分支,rs1 和 rs2 的值相等时,把 pc 的值设置成当前值+偏移值;bne
是不等条件分支,rs1 和 rs2 的值不等时,把 pc 的值设置成当前值+偏移值;blt
是小于条件分支,rs1 小于 rs2 的值时,把 pc 的值设置成当前值+偏移值;bge
是大于等于条件分支,rs1 大于等于 rs2 的值时,把 pc 的值设置成当前值+偏移值;bltu
是无符号小于条件分支;bgeu
是无符号大于等于条件分支;jal
是跳转并链接,把 pc 设置成当前值+偏移值,然后将 pc+4 当做下一条指令的地址存入 rd 中;jalr
跳转并链接,把 pc 设置成 rs1+偏移值,然后将 pc+4 写入 rd 中。
6. CSR 操作指令
RV32I 具有 6 条 CSR 操作指令,分别是 CSRRW、CSRRWI、CSRRS、CSRRSI、 CSRRC 和 CSRRCI
。CSR 操作指令的指令格式如下图所示,其中 csr 表示 CSR 寄存器的地址,zimm 表示零扩展立即数。
CSR 操作指令都是使用 I-typed 指令格式。
csrrw
是读后写控制状态寄存器,先将 csr 的值记为 t,把 rs1 的值写入 csr,再将 t 写入 rd 中;csrrwi
是立即数读后写控制状态寄存器,将 csr 的值写入 rd 中,再将立即数写入 csr 中;csrrs
是读后置位控制状态寄存器,先将 csr 的值记为 t,让 t 和 rs1 取或并写入 csr,再将 t 写入 rd 中;csrrsi
是立即数读后置位控制状态寄存器,先将 csr 的值记为 t,把 t 和立即数 zimm 取或并写入 csr,再将 t 写入 rd 中;csrrc
是读后清除控制状态寄存器,先将 csr 的值记 为 t,把 t 和 rs1 位与并写入 csr,再将 t 写入 rd 中;csrrci
是立即数读后清除控制状态寄存器,csr 的值记为 t,把 t 和立即数 zimm 位与并写入 csr,再将 t 写入 rd 中。