寄存器模型
寄存器是处理器中最快速的存储单元,RISC-V 架构定义了一套完整的寄存器模型。理解寄存器模型是学习 RISC-V 的基础。
通用寄存器
RISC-V 基础整数指令集定义了 32 个通用寄存器,编号为 x0 到 x31。这些寄存器在 RV32I 中是 32 位宽,在 RV64I 中是 64 位宽。寄存器宽度用 XLEN 表示,即 RV32I 中 XLEN=32,RV64I 中 XLEN=64。
寄存器列表
| 寄存器 | ABI 名称 | 用途 | 调用约定 |
|---|---|---|---|
| x0 | zero | 硬件零寄存器 | - |
| x1 | ra | 返回地址 | 调用者保存 |
| x2 | sp | 栈指针 | 被调用者保存 |
| x3 | gp | 全局指针 | - |
| x4 | tp | 线程指针 | - |
| x5 | t0 | 临时寄存器 0 | 调用者保存 |
| x6 | t1 | 临时寄存器 1 | 调用者保存 |
| x7 | t2 | 临时寄存器 2 | 调用者保存 |
| x8 | s0/fp | 保存寄存器 0 / 帧指针 | 被调用者保存 |
| x9 | s1 | 保存寄存器 1 | 被调用者保存 |
| x10 | a0 | 参数/返回值 0 | 调用者保存 |
| x11 | a1 | 参数/返回值 1 | 调用者保存 |
| x12 | a2 | 参数 2 | 调用者保存 |
| x13 | a3 | 参数 3 | 调用者保存 |
| x14 | a4 | 参数 4 | 调用者保存 |
| x15 | a5 | 参数 5 | 调用者保存 |
| x16 | a6 | 参数 6 | 调用者保存 |
| x17 | a7 | 参数 7 | 调用者保存 |
| x18 | s2 | 保存寄存器 2 | 被调用者保存 |
| x19 | s3 | 保存寄存器 3 | 被调用者保存 |
| x20 | s4 | 保存寄存器 4 | 被调用者保存 |
| x21 | s5 | 保存寄存器 5 | 被调用者保存 |
| x22 | s6 | 保存寄存器 6 | 被调用者保存 |
| x23 | s7 | 保存寄存器 7 | 被调用者保存 |
| x24 | s8 | 保存寄存器 8 | 被调用者保存 |
| x25 | s9 | 保存寄存器 9 | 被调用者保存 |
| x26 | s10 | 保存寄存器 10 | 被调用者保存 |
| x27 | s11 | 保存寄存器 11 | 被调用者保存 |
| x28 | t3 | 临时寄存器 3 | 调用者保存 |
| x29 | t4 | 临时寄存器 4 | 调用者保存 |
| x30 | t5 | 临时寄存器 5 | 调用者保存 |
| x31 | t6 | 临时寄存器 6 | 调用者保存 |
零寄存器 x0
x0 是一个特殊的寄存器,它始终读取为 0,写入操作会被忽略。这个设计非常巧妙,有以下用途:
提供常数 0
addi a0, x0, 0 # a0 = 0,将 a0 清零
由于 x0 始终为 0,这条指令实际上是将 a0 设置为 0。相比其他架构需要专门的清零指令,RISC-V 的设计更加简洁。
忽略结果
add x0, a0, a1 # 执行加法但丢弃结果,用于设置条件码
当不需要保存运算结果时,可以将目标寄存器设为 x0。
实现 NOP 指令
addi x0, x0, 0 # NOP(空操作)
RISC-V 没有专门的 NOP 指令,而是使用上述伪指令实现。
参数寄存器 a0-a7
a0-a7(x10-x17)用于函数调用时传递参数和返回值。根据 RISC-V 调用约定:
- a0 和 a1 既用于传递前两个参数,也用于返回函数值
- a2-a7 用于传递第 3 到第 8 个参数
- 超过 8 个参数时,多余的参数通过栈传递
# 函数调用:int result = add(1, 2)
li a0, 1 # 第一个参数
li a1, 2 # 第二个参数
call add # 调用函数
# 返回值在 a0 中
保存寄存器 s0-s11
s0-s11(x8、x9、x18-x27)用于保存需要在函数调用过程中保持不变的值。调用约定规定:
- 被调用函数如果使用这些寄存器,必须在返回前恢复其原始值
- 调用者可以安全地假设这些寄存器在函数调用后不变
# 被调用者保存示例
func:
addi sp, sp, -16 # 分配栈空间
sw s0, 12(sp) # 保存 s0
sw s1, 8(sp) # 保存 s1
# 函数体,可以使用 s0、s1
lw s1, 8(sp) # 恢复 s1
lw s0, 12(sp) # 恢复 s0
addi sp, sp, 16 # 释放栈空间
ret # 返回
临时寄存器 t0-t6
t0-t6(x5-x7、x28-x31)用于存放临时计算结果。调用约定规定:
- 调用者负责保存这些寄存器的值(如果需要)
- 被调用函数可以自由使用这些寄存器,无需恢复
# 调用者保存示例
addi sp, sp, -4
sw t0, 0(sp) # 保存 t0
call some_func # 调用函数
lw t0, 0(sp) # 恢复 t0
addi sp, sp, 4
特殊用途寄存器
返回地址寄存器 ra (x1)
ra 用于保存函数调用的返回地址。call 指令会自动将返回地址存入 ra。
call function # 等价于:jal ra, function
# 将下一条指令地址存入 ra,然后跳转
栈指针寄存器 sp (x2)
sp 指向当前栈顶,用于管理局部变量和函数调用栈帧。栈向低地址方向增长。
addi sp, sp, -32 # 分配 32 字节栈空间
sw a0, 0(sp) # 保存数据到栈
lw a0, 0(sp) # 从栈读取数据
addi sp, sp, 32 # 释放栈空间
全局指针寄存器 gp (x3)
gp 指向全局数据区的中间位置,用于高效访问全局变量和静态变量。通过 gp 加上偏移量可以在一条指令内访问 4KB 范围内的全局数据。
线程指针寄存器 tp (x4)
tp 用于指向线程本地存储(TLS),在多线程编程中用于访问线程私有数据。
帧指针寄存器 fp/s0 (x8)
fp 指向当前栈帧的基址,用于调试和实现动态栈分配。在简单函数中通常不需要使用 fp。
程序计数器 pc
程序计数器 pc 是一个特殊的寄存器,保存当前正在执行的指令地址。pc 不属于通用寄存器,不能直接读写,但可以通过某些指令间接访问。
auipc t0, 0 # 将 pc 的高 20 位加载到 t0
jal ra, label # 将 pc+4 存入 ra,然后跳转
pc 的值在以下情况下会改变:
- 正常执行时,pc 自动增加 4(32 位指令)或 2(16 位压缩指令)
- 执行跳转或分支指令时,pc 被设置为目标地址
- 发生中断或异常时,pc 被设置为处理程序入口地址
浮点寄存器
当处理器实现了 F 或 D 扩展时,会额外提供 32 个浮点寄存器 f0-f31。
| 寄存器 | ABI 名称 | 用途 |
|---|---|---|
| f0-f7 | ft0-ft7 | 浮点临时寄存器 |
| f8-f9 | fs0-fs1 | 浮点保存寄存器 |
| f10-f11 | fa0-fa1 | 浮点参数/返回值 |
| f12-f17 | fa2-fa7 | 浮点参数 |
| f18-f27 | fs2-fs11 | 浮点保存寄存器 |
| f28-f31 | ft8-ft10 | 浮点临时寄存器 |
浮点寄存器的宽度取决于扩展类型:
- F 扩展:32 位单精度浮点
- D 扩展:64 位双精度浮点
- Q 扩展:128 位四精度浮点
控制状态寄存器 CSR
控制和状态寄存器(CSR)用于配置处理器行为和查询处理器状态。CSR 与通用寄存器使用不同的地址空间,需要使用专门的 CSR 指令访问。
CSR 地址编码
CSR 地址为 12 位,按照功能分组:
| 地址范围 | 读写权限 | 用途 |
|---|---|---|
| 0x000-0x0FF | 只读 | 用户级只读信息 |
| 0x100-0x1FF | 读写 | 用户级陷阱处理 |
| 0x200-0x2FF | 读写 | 用户级计数器/定时器 |
| 0x300-0x3FF | 读写 | 机器模式配置 |
| 0x400-0x4FF | 读写 | 机器模式陷阱处理 |
| 0x500-0x5FF | 读写 | 机器模式内存保护 |
| 0x600-0x6FF | 读写 | 机器模式计数器/定时器 |
| 0x7C0-0x7FF | 读写 | 调试/性能监控 |
常用机器模式 CSR
mstatus - 机器状态寄存器
控制全局中断使能和处理器状态:
位域:
[12:11] MPP - 之前的特权模式
[7] MPIE - 之前的中断使能
[3] MIE - 机器模式中断使能
mtvec - 机器陷阱向量
保存异常和中断处理程序的入口地址。
[31:2] BASE - 处理程序基地址
[1:0] MODE - 向量模式(0=直接,1=向量)
mepc - 机器异常程序计数器
保存发生异常时的 pc 值,用于 mret 指令返回。
mcause - 机器异常原因
标识异常或中断的类型。
[31] Interrupt - 1 表示中断,0 表示异常
[30:0] Exception Code - 异常/中断编码
mtval - 机器陷阱值
保存异常的附加信息,如出错的地址或非法指令编码。
CSR 访问指令
csrrw rd, csr, rs1 # 读 CSR 到 rd,写 rs1 到 CSR
csrrs rd, csr, rs1 # 读 CSR 到 rd,设置 CSR 中 rs1 对应的位
csrrc rd, csr, rs1 # 读 CSR 到 rd,清除 CSR 中 rs1 对应的位
csrrwi rd, csr, imm # 读 CSR 到 rd,写立即数到 CSR
常用伪指令:
csrr rd, csr # 读 CSR
csrw csr, rs1 # 写 CSR
csrc csr, rs1 # 清除 CSR 位
csrs csr, rs1 # 设置 CSR 位
寄存器使用最佳实践
函数调用约定
遵循 RISC-V 调用约定可以确保函数间的正确协作:
- 调用者保存寄存器(t0-t6、a0-a7)在调用函数前保存
- 被调用者保存寄存器(s0-s11)在函数入口保存、出口恢复
- 栈指针 sp 必须在函数返回前恢复
寄存器分配策略
编译器在分配寄存器时通常遵循以下策略:
- 优先使用临时寄存器存放中间结果
- 需要跨函数调用保持的值使用保存寄存器
- 循环变量优先分配寄存器
- 活跃范围不重叠的变量可以共享寄存器
减少内存访问
充分利用 32 个通用寄存器可以显著减少内存访问:
# 不好的做法:频繁访问内存
lw t0, var1
add t0, t0, t1
sw t0, var1
lw t0, var1
add t0, t0, t2
sw t0, var1
# 好的做法:尽量使用寄存器
lw t0, var1
add t0, t0, t1
add t0, t0, t2
sw t0, var1
小结
本章介绍了 RISC-V 的寄存器模型,包括:
- 32 个通用寄存器的用途和调用约定
- 零寄存器 x0 的特殊用途
- 程序计数器 pc 的作用
- 浮点寄存器(F/D 扩展)
- 控制状态寄存器 CSR
理解寄存器模型是编写高效 RISC-V 程序的基础。下一章我们将学习基础整数指令集。