跳到主要内容

寄存器模型

寄存器是处理器中最快速的存储单元,RISC-V 架构定义了一套完整的寄存器模型。理解寄存器模型是学习 RISC-V 的基础。

通用寄存器

RISC-V 基础整数指令集定义了 32 个通用寄存器,编号为 x0 到 x31。这些寄存器在 RV32I 中是 32 位宽,在 RV64I 中是 64 位宽。寄存器宽度用 XLEN 表示,即 RV32I 中 XLEN=32,RV64I 中 XLEN=64。

寄存器列表

寄存器ABI 名称用途调用约定
x0zero硬件零寄存器-
x1ra返回地址调用者保存
x2sp栈指针被调用者保存
x3gp全局指针-
x4tp线程指针-
x5t0临时寄存器 0调用者保存
x6t1临时寄存器 1调用者保存
x7t2临时寄存器 2调用者保存
x8s0/fp保存寄存器 0 / 帧指针被调用者保存
x9s1保存寄存器 1被调用者保存
x10a0参数/返回值 0调用者保存
x11a1参数/返回值 1调用者保存
x12a2参数 2调用者保存
x13a3参数 3调用者保存
x14a4参数 4调用者保存
x15a5参数 5调用者保存
x16a6参数 6调用者保存
x17a7参数 7调用者保存
x18s2保存寄存器 2被调用者保存
x19s3保存寄存器 3被调用者保存
x20s4保存寄存器 4被调用者保存
x21s5保存寄存器 5被调用者保存
x22s6保存寄存器 6被调用者保存
x23s7保存寄存器 7被调用者保存
x24s8保存寄存器 8被调用者保存
x25s9保存寄存器 9被调用者保存
x26s10保存寄存器 10被调用者保存
x27s11保存寄存器 11被调用者保存
x28t3临时寄存器 3调用者保存
x29t4临时寄存器 4调用者保存
x30t5临时寄存器 5调用者保存
x31t6临时寄存器 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-f7ft0-ft7浮点临时寄存器
f8-f9fs0-fs1浮点保存寄存器
f10-f11fa0-fa1浮点参数/返回值
f12-f17fa2-fa7浮点参数
f18-f27fs2-fs11浮点保存寄存器
f28-f31ft8-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 调用约定可以确保函数间的正确协作:

  1. 调用者保存寄存器(t0-t6、a0-a7)在调用函数前保存
  2. 被调用者保存寄存器(s0-s11)在函数入口保存、出口恢复
  3. 栈指针 sp 必须在函数返回前恢复

寄存器分配策略

编译器在分配寄存器时通常遵循以下策略:

  1. 优先使用临时寄存器存放中间结果
  2. 需要跨函数调用保持的值使用保存寄存器
  3. 循环变量优先分配寄存器
  4. 活跃范围不重叠的变量可以共享寄存器

减少内存访问

充分利用 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 程序的基础。下一章我们将学习基础整数指令集。