2. RISC-V指令集架构基础
在把处理器从 RTL 一路带到 Linux 启动的过程中,指令集架构(ISA)是软硬件之间的第一份“契约”。RISC-V 的 ISA 采用模块化设计:基础整数指令集 RV32I 与 RV64I 定义了最核心的整数运算、访存、跳转和控制流;而特权架构则定义了机器态、监管态、用户态以及 CSR、异常和中断。理解这一层,才能看懂后续流水线译码、CSR 访问、trap 入口和 Linux 启动时的模式切换。
一、RV32I/RV64I 的指令格式
RV32I 的指令长度固定为 32 位,低 7 位是 opcode,用于区分指令大类。常见格式有 R、I、S、B、U、J 六种。它们并不是随意划分,而是围绕“源寄存器、目的寄存器、立即数、功能码”这几个字段做复用,从而在固定 32 位宽度内表达不同语义。
- R 型:寄存器—寄存器运算,如
add x1, x2, x3,包含 funct7、rs2、rs1、funct3、rd、opcode。 - I 型:立即数运算与加载,如
addi x1, x2, 10、lw x1, 0(x2),包含 12 位立即数。 - S 型:存储指令,如
sw x1, 8(x2),立即数被拆成两段,分别放在指令的高位和 rs2 位置。 - B 型:条件分支,如
beq x1, x2, label,立即数以 2 字节为单位编码,支持 ±4KB 范围。 - U 型:高位立即数,如
lui、auipc,提供 20 位立即数并放到高 20 位。 - J 型:跳转并链接,如
jal x1, label,立即数同样按 2 字节对齐编码。
RV64I 在 RV32I 基础上扩展了 64 位整数运算,例如 addw、sllw、ld、sd 等,寄存器宽度变为 64 位,地址空间和算术范围也随之扩大。但指令格式本身仍然保持 32 位定长,这为译码器设计带来很大便利。
二、寄存器组织与 ABI 约定
RV32I/RV64I 定义了 32 个通用整数寄存器 x0~x31,其中 x0 恒为 0,写入被忽略。其余寄存器在汇编层面通常使用 ABI 名称,例如 ra、sp、a0、t0、s0 等。这些名称不是硬件强制,而是软件约定,决定了函数调用、参数传递和栈帧布局。
| 寄存器 | ABI 名 | 用途 | 调用约定 |
|---|---|---|---|
| x0 | zero | 常量 0 | 只读 |
| x1 | ra | 返回地址 | 调用者保存 |
| x2 | sp | 栈指针 | 被调用者保存 |
| x5-x7 | t0-t2 | 临时寄存器 | 调用者保存 |
| x10-x11 | a0-a1 | 参数/返回值 | 调用者保存 |
| x8-x9, x18-x27 | s0-s11 | 保存寄存器 | 被调用者保存 |
此外还有程序计数器 pc,它不通过通用寄存器暴露,而是由硬件维护。RISC-V 没有延迟槽,分支和跳转的目标地址在指令执行时直接决定,这使流水线控制比某些传统架构更清晰。
三、特权模式与 CSR 寄存器
RISC-V 特权架构定义了三个层级:机器模式(M-mode)、监管模式(S-mode)和用户模式(U-mode)。M-mode 是最高权限,负责启动、时钟中断、异常入口和物理内存保护;S-mode 通常运行操作系统内核;U-mode 运行用户进程。Linux 启动时,固件先运行在 M-mode,随后通过 mret 切换到 S-mode,把控制权交给内核。
CSR(Control and Status Register)是特权模式的核心接口,通过 csrrw、csrrs、csrrc 等指令访问。常见 CSR 包括:
mstatus:全局中断使能、特权模式、浮点状态等。mtvec:机器模式 trap 入口地址和模式。mepc:异常发生时的返回地址。mcause:异常或中断的原因编码。satp:S-mode 地址翻译与分页配置。sstatus、stvec、sepc、scause:S-mode 对应副本。
关键点:CSR 访问必须满足当前特权级。U-mode 访问 M-mode CSR 会触发非法指令异常;S-mode 访问某些 M-mode CSR 也会被拦截。这种分层是操作系统隔离和安全启动的基础。
四、异常与中断机制
异常是当前指令执行过程中产生的同步事件,例如非法指令、加载地址不对齐、断点、系统调用;中断是外部异步事件,例如定时器中断、外部中断。RISC-V 把它们统一称为 trap,并共用一套入口机制。
当 trap 发生时,硬件会做几件事:把当前 pc 保存到 mepc 或 sepc;把原因写入 mcause 或 scause;把 trap 类型和中断标志写入 mtval 或 stval;然后跳转到 mtvec 或 stvec 指定的入口。入口可以配置为直接模式或向量模式,向量模式下不同中断有不同偏移。
# 典型的 M-mode trap 入口伪代码
trap_entry:
csrrw t0, mscratch, t0 # 交换临时寄存器
sw t1, 0(t0) # 保存现场
csrr t1, mcause # 读取异常原因
csrr t2, mepc # 读取返回地址
...
csrrw t0, mscratch, t0
mret # 返回并恢复特权级
调试提示:如果 Linux 启动时卡在早期 trap,优先检查 mtvec 是否指向有效入口、mepc 是否保存了正确返回地址,以及 mcause 的低位编码是否与预期异常一致。
五、常见误区与复盘
- 误区一:把 ABI 名当成硬件寄存器名。硬件只认
x0~x31,ABI 名是汇编器和调用约定层面的映射。 - 误区二:忽略
x0的只读特性。向x0写入不会改变其值,但可能影响指令编码和译码逻辑。 - 误区三:认为所有 CSR 都能在任意模式访问。CSR 有特权级限制,越权访问会触发异常。
- 误区四:把异常和中断混为一谈。异常是同步的,中断是异步的,但二者共用 trap 入口和 CSR 保存机制。
复盘时可以用一个最小场景验证:在 M-mode 下设置 mtvec,执行一条非法指令,观察 mcause 是否变为非法指令编码,mepc 是否指向该指令地址。这个实验能同时验证指令译码、CSR 访问和 trap 入口三条路径,是后续 RTL 调试和 Linux 启动分析的重要基础。