RISC-V 指令系统

指令系统

指令集

指令集从本质上可以分为复杂指令集(Complex Instruction Set Computing,CISC)和精简指令集(Reduced Instruction Set Computing,RISC)两种。复杂指令集的特点是能够在一条指令内完成很多事情。

指令架构(Instruction Set Architecture, 缩写为ISA),是软件和硬件的接口,不同的应用需求,会有不同的指令架构。

RISC-V指令集使用模块化的方式进行组织,每一个模块使用一个英文字母来表示。最基本也是唯一强制要求实现的指令集部分是I字母代表的基本整数指令集。

RISC-V指令格式

常见的RISC-V指令集如下表所示

基本指令集含义
RV32I32位整数指令集
RV32ERV32I的子集,用于小型嵌入式场景
RV64I64位整数指令集,兼容RV32I
RV128I128位整数指令集,兼容RV64I和RV32I

RISC-V有六种基本指令格式:

指令类型操作
R-type用于寄存器-寄存器操作
I-type用于短立即数和访存 load 操作
S-type用于访存 store 操作
B-type用于条件跳转操作
U-type用于长立即数
J-type用于无条件跳转

在RISC-V中对于所有指令,要读写的寄存器的标识符总是在同一位置,意味着在解码指令之前,就可以先开始访问寄存器,这些格式的立即数字段总是符号扩展,符号位总是在指令中最高位,各种类型的指令构成如下图所示:

在这里插入图片描述

字段含义
opcode(操作码)指令的基本操作,这个缩写是它惯用名称
rd目的操作寄存器,用来存放操作结果
funct3一个另外的操作码字段
rs1第一个源操作数寄存器
rs2第二个源操作数寄存器
funct7一个另外的操作码字段
imm立即数

R型指令

R型指令构成如下:

在这里插入图片描述

R型的全部指令(RV32I)

在这里插入图片描述

R型指令包括加法、减法、逻辑运算、移位运算。

ADD 执行 rs1 和 rs2 的相加。SUB 执行从 rs1 中减去 rs2。忽略结果的溢出,并把结果的低 XLEN 位写入目的寄存器 rd。

SLT 和 SLTU 分别执行有符号和无符号的比较,如果 rs1 < rs2,向 rd 写入 1,否则写入 0。

AND、OR 和 XOR 执行按位逻辑操作。

SLL、SLR 和 SRA 对寄存器 rs1 中的值执行逻辑左移、逻辑右移、和算数右移,移位的数目 保持在寄存器 rs2 的低 5 位中。

XLEN:处理器架构支持的字长(位宽)

示例:
add a0, a1, a2  //a0 = a1 + a2
sub a0, a1, a2  //a0 = a1 - a2
sll a0, a1, a2  //a0 = a1 << a2(低位补0)
srl a0, a1, a2  //a0 = a1 >> a2(高位补0)
sra a0, a1, a2  //a0 = a1 >> a2 (算术右移,高位补原来的符号位)
slt a0, a1, a2  //a1 < a2 ? a0 = 1 : a0 = 0
xor a0, a1, a2  //a0 = a1 ^ a2
or  a0, a1, a2  //a0 = a1 | a2
and a0, a1, a2  //a0 = a1 & a2

I型指令

I型指令构成如下:

在这里插入图片描述

I型的全部指令(RV32I)

I型指令包括立即数的运算和load指令。

在这里插入图片描述

ADDI 将符号扩展的 12 位立即数加到寄存器 rs1 上。简单地将结果的低 XLEN 位当作结果,而忽略了算数溢出。

如果寄存器 rs1 小于符号扩展的立即数(当二者都被视为有符号数时),SLTI(小于立即数时 置 1)指令把值 1 放到寄存器 rd 中;否则,该指令把 0 写入 rd 中。SLTIU 与之相似,但是将两 个值作为无符号数比较(也就是说,前者会把立即数按符号扩展到 XLEN 位,而后者会将其视为 无符号数)。

ANDI、ORI、XORI 是在寄存器 rs1 和符号扩展的 12 位立即数上执行按位 AND、OR 和 XOR, 并把结果放入 rd 的逻辑操作。

示例:
addi a0, a1, 0x5  //a0 = a1 + 0x5
subi a0, a1, 0x05  //a0 = a1 - 0x05
slli a0, a1, 0x05  //a0 = a1 << 0x05(低位补0)
srli a0, a1, 0x05  //a0 = a1 >> 0x05(高位补0)
srai a0, a1, 0x05  //a0 = a1 >> 0x05 (算术右移,高位补原来的符号位)
slti a0, a1, 0x05  //a1 < 0x05 ? a0 = 1 : a0 = 0
xori a0, a1, 0x05  //a0 = a1 ^ 0x05
ori a0, a1, 0x05   //a0 = a1 | 0x05
andi a0, a1, 0x05  //a0 = a1 & 0x05

在这里插入图片描述

LW 指令从内存加载一个 32 位的值到 rd。

LH 先从内存加载一个 16 位的值,然后在存储到 rd 中之前,把它符号扩展到 32 位。

LHU 先从内存加载一个 16 位的值,然后,在存储到 rd 中之前, 把它用零扩展到 32 位。

LB 和 LBU 被类似地定义于 8 位的值。

示例:
lb x10,  0(x1)  //将x1的值加上0,将这个值作为地址, 取出这个地址所对应的内存中的值, 将这个值赋值给x10(取出的是8位数值)
lh x10,  0(x1)  //从内存中取出16位数值
lw x10, 0(x1)  //从内存中取出32位数值
lbu x10, 0(x1) //从内存中取出8位无符号数值
lhu x10, 0(x1) //从内存中取出16位无符号数值

S型指令

S型指令构成如下:

在这里插入图片描述

S型的全部指令(RV32I)

S型指令包括store指令。

在这里插入图片描述

SW、SH 和 SB 指令从寄存器 rs2 的低位将 32 位、16 位和 8 位的值存储到内存。

示例:
sb  x10, 0(x1)  //x1的值加上0,将这个值作为地址, 将x10的值存储到上述地址所对应的内存中去 (只会将x10的值的低8位写入)
sh  x10, 0(x1)  //只会将x10的值的低16位写入
sw  x10, 0(x1)  //只会将x10的值的低32位写入

B型指令

B型指令构成如下:

在这里插入图片描述

(注:imm[0]被丢弃,因为它始终为零)

///-----------------------------------------------------------------------------------------------------------

分支跳转指令的基址是分支指令所在地址,偏移量offset为0会导致指令卡死,形成无限循环。

bne指令会比较 x9 x0 的值。

如果不相等,程序将跳转到 PC + 0,即下一条指令。

bne x9, x0, 0 的行为:

  • x9x0 不相等时,程序计数器(PC)会被设置为 PC + (offset << 1),其中 offset 是一个相对偏移量。如果 offset0,则计算结果是 PC + 0,即程序计数器不会改变,指向当前指令地址,这会导致以下行为:
    1. 指令重新执行:程序计数器保持不变,指向当前指令,这意味着处理器将不断地重新执行这条 BEQ 指令。
    2. 无限循环:由于 rs1rs2 的值不变且相等,条件总是满足,导致程序卡在这条指令上,形成无限循环。
bne x9 x0 0
0000000,00000,01001,001,0000,0,1100011
imm[4:1]=0000

偏移量offset 是一个12位的值,在指令中存储时,它已经经过左移一位的处理,即存储的偏移量实际上是 offset << 1

当需要计算分支目标地址时,处理器会将 offset 左移一位,得到实际的字节偏移量。

bne x9 x0 4
0,000000,00000,01001,001,0010,0,1100011
imm[4:1]=0010
bne x9 x0 8
0000000,00000,01001,001,0100,0,1100011
imm[4:1]=0100

///-----------------------------------------------------------------------------------------------------------

B型的全部指令(RV32I)

B型指令包括条件跳转指令。

在这里插入图片描述

分支指令对两个寄存器进行比较。BEQ 和 BNE 分别在寄存器 rs1 和 rs2 相等或不等时采取分支。

BLT 和 BLTU 分别使用有符号和无符号的比较,如果 rs1 小于 rs2 则采取分支。

BGE 和 BGEU 分别使用有符号和无符号的比较,如果 rs1 大于或等于 rs2 则采取分支。

注意,BGT、BGTU、BLE 和 BLEU 可以分别通过反转 BLT、BLTU、BGE 和 BGEU 的操作数来合成。

示例:
beq a1,a2,Label   //if(a1==a2){goto Label;}
bne a1,a2,Label   //if(a1!=a2){goto Label;}
blt a1,a2,Label   //if(a1< a2){goto Label;}
bgt a1,a2,Label   //if(a1> a2){goto Label;}
bge a1,a2,Label   //if(a1<=a2){goto Label;}
ble a1,a2,Label   //if(a1>=a2){goto Label;}

U型指令

U型指令构成如下:

在这里插入图片描述

U型的全部指令(RV32I)

在这里插入图片描述

LUI(加载高位立即数)被用于构建 32 位常量,它使用 U 类型格式。LUI 把 32 位 U 立即数 值放在目的寄存器 rd 中,同时把最低的 12 位用零填充。

AUIPC(加高位立即数到 pc)被用于构建 pc 相对地址,它使用 U 类型格式。AUIPC 根据 U 立即数形成 32 位偏移量(最低 12 位填零),把这个偏移量加到 AUIPC 指令的地址,然后把结果放在寄存器 rd 中

示例:
lui  x10, 0x65432 //得到立即数的高20位,低位补0,立即数范围为:0x00~0xFFFFF

J型指令

J型指令构成如下:

在这里插入图片描述

J型的全部指令(RV32I)
在这里插入图片描述

跳转和链接(JAL)指令使用 J 类型格式。J 类型指令把 J 立即数以 2 字节的倍数编码一个 有符号的偏移量。偏移量是符号扩展的,加到当前跳转指令的地址上以形成跳转目标地址。跳转可 以因此到达的目标范围是 ±1 MiB。JAL 把跟在 JAL 之后的指令的地址(pc+4)存储到寄存器 rd 中。标准软件调用约定使用 x1 作为返回地址寄存器,使用 x5 作为备选的链接寄存器。

间接跳转指令 JALR(跳转和链接寄存器)使用 I 类型编码。通过把符号扩展的 12 位 I 立即 数加到寄存器 rs1 来获得目标地址,然后把结果的最低有效位设置为零。紧接着跳转的指令的地址 (pc+4)被写入寄存器 rd。如果不需要结果,寄存器 x0 也可以被用作目的寄存器。

示例:
jal ra, symbol    // 跳转到Symbol中去, 并把ra设置成返回地址 Symbol 可以是自定义的Label ,也可以是某个函数名
jal ra, 100       // 跳转到pc + 100 * 2的地方中去, 并把ra设置成返回地址  pc相对寻址,对应的是位置无关代码(PIC)
jalr ra, 40(x10)  // 跳转到x10+40 的地方中去, 并把ra设置成返回地址x10+40必须是绝对地址,指向内存中某个确定的地方(往往是函数的开头),非PIC

通用寄存器

RV32I有32个通用寄存器,以及一个PC寄存器。其中有一个通过硬件设置的值恒为 0 的 x0 寄存器

注:RISC-V的32个寄存器x0~x31是用0~31这些数字来表示。
————————————————

参考资料:

RISC-V 指令集介绍

RISC-V 指令集手册中文版 RISC-V ISA Manual CN

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值