第2章:RISC-V基础指令集(RV32I)——寄存器模型、整数运算、逻辑运算与移位指令
好,咱们正式开始啃指令集这块硬骨头。RV32I是RISC-V最基础的指令集,说白了就是地基。你后面学到的乘除法、原子操作、浮点,全得在这上面盖楼。我个人习惯,学任何架构先看寄存器模型——寄存器就是CPU的“工作台”,指令就是“操作手册”。
2.1 寄存器模型:CPU的32个“临时工”
RV32I定义了32个通用寄存器,每个都是32位宽。编号从x0到x31。嗯,这里要注意,x0是个“铁公鸡”——你读它永远得到0,写它等于白写。我在项目中遇到过新手把目标寄存器写成x0,调试了半天发现数据丢了,其实就是被x0吞了。
寄存器虽然编号简单,但RISC-V规范给它们起了“别名”,方便阅读和编译。比如x1叫ra(返回地址),x2叫sp(栈指针)。你想想看,写汇编时用sp比用x2直观多了吧?
| 寄存器编号 | 别名 | 用途说明 |
|---|---|---|
| x0 | zero | 硬连线为0,写入无效 |
| x1 | ra | 返回地址(函数调用用) |
| x2 | sp | 栈指针 |
| x3 | gp | 全局指针 |
| x4 | tp | 线程指针 |
| x5-x7 | t0-t2 | 临时寄存器(调用者保存) |
| x8 | s0/fp | 保存寄存器/帧指针 |
| x9 | s1 | 保存寄存器 |
| x10-x17 | a0-a7 | 函数参数/返回值 |
| x18-x27 | s2-s11 | 保存寄存器(被调用者保存) |
| x28-x31 | t3-t6 | 临时寄存器 |
2.2 整数运算指令:加减法,但不止加减法
整数运算指令分三类:加法、减法、以及“带立即数的加法”。指令格式很规整,都是opcode rd, rs1, rs2或者opcode rd, rs1, imm。
先看加法:ADD rd, rs1, rs2。把rs1和rs2的值相加,结果存到rd。减法类似:SUB rd, rs1, rs2。注意,RISC-V没有“减法立即数”指令,你想减一个常数,得用ADDI rd, rs1, -常数。嗯,这里要记住,立即数是有符号的12位,范围-2048到2047。
举个例子:
# 计算 a = b + c
ADD x10, x11, x12 # x10 = x11 + x12
# 计算 a = b - 5
ADDI x10, x11, -5 # x10 = x11 - 5
# 计算 a = b + 100
ADDI x10, x11, 100 # x10 = x11 + 100
还有一条特殊的加法指令:LUI rd, imm。它把20位立即数加载到rd的高20位,低12位补0。配合ADDI,就能构造任意32位常数。比如加载0x12345678:
LUI x10, 0x12345 # x10 = 0x12345000
ADDI x10, x10, 0x678 # x10 = 0x12345678
2.3 逻辑运算指令:与、或、异或、取反
逻辑运算在底层很常用,比如位掩码、清零、置位。RV32I提供了AND、OR、XOR,以及它们的立即数版本ANDI、ORI、XORI。
指令格式和算术指令完全一样:
AND rd, rs1, rs2 # 按位与
OR rd, rs1, rs2 # 按位或
XOR rd, rs1, rs2 # 按位异或
ANDI rd, rs1, imm # 与立即数
ORI rd, rs1, imm # 或立即数
XORI rd, rs1, imm # 异或立即数
你想想看,如果想清零某个寄存器的低8位,怎么做?用ANDI配合掩码0xFFFFFF00就行。如果想置位第3位,用ORI配合0x08。这些操作在控制GPIO、配置外设寄存器时天天用。
另外,RISC-V没有专门的“取反”指令。想取反,用XORI rd, rs1, -1。因为-1的二进制全是1,异或1就是取反。这个小技巧我在优化代码时经常用,比先加载常数再取反少一条指令。
2.4 移位指令:左移、右移(逻辑与算术)
移位指令分三种:逻辑左移(SLL)、逻辑右移(SRL)、算术右移(SRA)。每种都有立即数版本(SLLI、SRLI、SRAI)。
移位量由rs2的低5位(或立即数的低5位)决定,范围0-31。为什么是5位?因为32位寄存器只需要5位就能表示所有移位量。
SLL rd, rs1, rs2 # 逻辑左移:左移,低位补0
SRL rd, rs1, rs2 # 逻辑右移:右移,高位补0
SRA rd, rs1, rs2 # 算术右移:右移,高位补符号位
SLLI rd, rs1, shamt # 立即数左移
SRLI rd, rs1, shamt # 立即数逻辑右移
SRAI rd, rs1, shamt # 立即数算术右移
举个例子:
# 将x10左移4位(相当于乘以16)
SLLI x10, x10, 4
# 将x11逻辑右移8位(提取高24位)
SRLI x11, x11, 8
# 将x12算术右移2位(保持符号,相当于除以4并向下取整)
SRAI x12, x12, 2
移位指令在性能优化中很有用。比如你想快速乘以2的幂,用左移代替乘法;想提取位域,用左移+右移组合。我记得在优化一个CRC计算模块时,用移位指令代替了循环和分支,性能提升了3倍。
2.5 小结:RV32I运算指令的共性
总结一下,RV32I的整数运算、逻辑运算、移位指令有这些共同点:
- 三操作数格式: rd, rs1, rs2(或立即数),没有隐式操作数
- 立即数12位: 有符号,范围-2048到2047
- 移位量5位: 范围0-31,由rs2低5位或立即数指定
- 无状态位: RISC-V不设置标志位(不像x86的ZF、CF),条件判断靠比较指令
- 无乘法除法: 乘除在RV32M扩展中,基础集只有加减和移位
我个人觉得,RISC-V这种规整的设计让硬件实现变得简单,也让编译器优化更轻松。你写汇编时,每条指令都清晰明了,没有隐藏的副作用。这种“干净”的设计哲学,正是RISC-V的魅力所在。
下一章我们会讲加载存储指令和条件分支,到时候这些运算指令就会派上大用场了。