4. RV32I基础整数指令集(上):算术运算、逻辑运算与移位指令
各位同学,今天我们正式进入RV32I指令集的核心地带。说实话,这部分内容就像CPU的“呼吸”和“心跳”——没有它们,处理器就是个死物。我个人习惯把RV32I的指令分成几大类来理解,今天我们先啃下最基础的三块:算术运算、逻辑运算和移位指令。
4.1 算术运算指令:ADD、SUB、ADDI
先看加法。RISC-V的加法指令非常纯粹,就是ADD rd, rs1, rs2,把rs1和rs2的值相加,结果存到rd。没有标志位,没有条件码。嗯,这里要注意:x86和ARM的加法会改状态寄存器,但RISC-V不干这事。为什么?为了简化硬件设计,减少指令间的隐式依赖。
我在项目中遇到过一个问题:有同事从ARM转过来做RISC-V,习惯性地想检查加法溢出标志位,结果发现根本没有。他当时就懵了。其实RISC-V的哲学是——溢出检测你自己用软件做,别让硬件背锅。
核心要点:ADD和SUB都是寄存器-寄存器操作,不涉及内存。操作数宽度固定为32位,结果也只保留低32位。
减法指令SUB rd, rs1, rs2,其实就是rs1 - rs2。硬件实现上,它和ADD共享一套加法器,只是把rs2取反再加1。说白了,减法就是加法的“变装”。
再来看立即数加法ADDI rd, rs1, immediate。这个指令太常用了,加载地址、调整栈指针、循环计数……几乎每个程序里都有它。立即数是12位有符号数,范围-2048到2047。你想想看,如果立即数不够用怎么办?RISC-V的解决方案是:用LUI加载高20位,再用ADDI补低12位。这是RISC-V的经典组合拳。
| 指令 | 格式 | 功能 | 我的一点经验 |
|---|---|---|---|
| ADD | ADD rd, rs1, rs2 | rd = rs1 + rs2 | 最常用的寄存器加法 |
| SUB | SUB rd, rs1, rs2 | rd = rs1 - rs2 | 注意操作数顺序,别搞反了 |
| ADDI | ADDI rd, rs1, imm | rd = rs1 + 符号扩展(imm) | 立即数范围有限,大常数需分两步 |
避坑指南:我曾经在写启动代码时,用ADDI给栈指针赋值,结果立即数不够大,栈指针指向了错误地址。后来我养成了习惯:大常数一定用LUI+ADDI组合,或者直接用LI伪指令。
4.2 逻辑运算指令:AND、OR、XOR
逻辑运算指令,说白了就是按位操作。AND用于清零某些位,OR用于置位,XOR用于翻转。这三个指令都有对应的立即数版本:ANDI、ORI、XORI。
我记得有一次调试外设驱动,需要修改某个控制寄存器的特定位。用ANDI清零,再用ORI置位,两行代码搞定。如果换成其他架构,可能还要读-改-写三步走。RISC-V的简洁在这里体现得很充分。
# 示例:将x5寄存器的bit3清零,bit1置位
andi x5, x5, ~(1 << 3) # 清零bit3
ori x5, x5, (1 << 1) # 置位bit1
这里有个细节:ANDI、ORI、XORI的立即数也是12位有符号数。但逻辑运算通常只需要低12位,所以符号扩展不会影响结果——除非你操作的是最高位。嗯,这种情况很少见,但如果你遇到了,记得用LUI先加载高20位。
个人技巧:我习惯用XORI来快速翻转寄存器的某些位,比如切换GPIO的电平状态。一条指令,干净利落。
4.3 移位指令:SLL、SRL、SRA
移位指令分三种:逻辑左移(SLL)、逻辑右移(SRL)、算术右移(SRA)。移位量由rs2的低5位决定,范围0-31。为什么是5位?因为32位寄存器只需要5位就能表示所有移位量。
你想想看,如果移位量超过31会怎样?RISC-V的规定是:只取低5位,高位忽略。也就是说,SLL x1, x2, x3等价于左移x3[4:0]位。这和x86的SHL指令行为不同,x86会检查移位量是否超过操作数宽度。我曾经在移植代码时踩过这个坑——x86上移位32位会清零,但RISC-V上移位32位等于不移位(因为32的低5位是0)。
关键区别:
- SLL:左移,低位补0。相当于乘以2的n次方。
- SRL:逻辑右移,高位补0。相当于无符号数除以2的n次方。
- SRA:算术右移,高位补符号位。相当于有符号数除以2的n次方(向负无穷取整)。
# 示例:假设x5 = 0x80000001(最高位为1)
slli x6, x5, 1 # x6 = 0x00000002,左移1位,低位补0
srli x7, x5, 1 # x7 = 0x40000000,逻辑右移,高位补0
srai x8, x5, 1 # x8 = 0xC0000000,算术右移,高位补符号位1
这里要注意:SLLI、SRLI、SRAI是立即数版本的移位指令,移位量直接编码在指令中,范围0-31。我个人习惯在编译优化时,把乘除法替换成移位指令——如果除数是2的幂,用SRAI代替除法,性能提升非常明显。
避坑指南:我曾经在写除法算法时,用SRLI处理无符号数,用SRAI处理有符号数。结果有一次把有符号负数当无符号数右移了,高位补0,导致结果完全错误。记住:有符号数右移一定要用SRAI,无符号数用SRLI。
4.4 知识体系总览
为了让你更直观地理解这三类指令的关系,我画了一张图。你可以看到,算术运算、逻辑运算、移位指令构成了RV32I指令集的“三驾马车”,它们各自独立又相互配合。
这张图把今天讲的内容串起来了。你可以看到,算术运算、逻辑运算、移位指令各自独立,但都遵循RISC-V的简洁设计哲学——没有冗余的标志位,没有隐式的副作用。每条指令只做一件事,而且做得干净利落。
好了,今天的内容就到这里。记住:ADD/SUB/ADDI是算术运算的基石,AND/OR/XOR是位操作的利器,SLL/SRL/SRA是移位和乘除法的加速器。这三类指令加起来不到20条,但你已经能写出大部分基础程序了。下次我们继续聊比较指令和分支指令,那才是真正让程序“活”起来的东西。
公众号:蓝海资料掘金营,微信deep3321