RISC-V 经常被称为"开源指令集架构",但如果真正开始做 RISC-V BSP、Bootloader 或 Zephyr 驱动,仅仅知道 ADD、LW、SW 并不够。
本文从 ISA 的基本概念出发,理解 RISC-V 的寄存器、指令编码、Load/Store、CSR 和扩展机制,并最终结合 Zephyr,看看这些 ISA 能力是如何真正被软件使用的。
一、ISA 到底是什么?
ISA,即:
Instruction Set Architecture
中文通常叫指令集架构。
它实际上定义的是:
软件和 CPU 硬件之间的接口。
例如:
add a0, a1, a2
软件看到的含义就是:
a0 = a1 + a2
至于 CPU 内部究竟采用几级流水线、有没有乱序执行、ALU 有几个,这些都不属于 ISA,而属于微架构。
可以简单理解为:
软件
│
▼
ISA
│
▼
CPU Microarchitecture
│
▼
硬件
所以同一个 RISC-V ISA,可以由不同厂商实现出完全不同的 CPU。
二、RISC-V 为什么叫 RISC?
RISC 是:
Reduced Instruction Set Computer
即精简指令集计算机。
RISC-V 的一个重要设计思想就是:
让指令格式尽可能规则,让硬件解码和执行更加简单。
例如:
add a0, a1, a2
主要完成寄存器之间的加法。
而访问内存则使用:
lw a0, 0(a1)
或者:
sw a0, 0(a1)
这体现了 RISC-V 非常重要的 Load/Store Architecture:
CPU Register
▲ │
│ │
Load Store
│ │
▼ ▼
Memory
也就是说,大多数算术运算发生在寄存器之间,而内存访问由专门的 Load/Store 指令完成。
三、RV32、RV64 到底是什么意思?
RISC-V 经常看到:
RV32I
RV64I
RV64IMAC
RV64GC
这里最前面的:
RV32
RV64
代表 CPU 的 XLEN。
例如:
RV32 → XLEN = 32
RV64 → XLEN = 64
因此 RV64 CPU 的通用整数寄存器宽度通常为 64 bit。
RISC-V 一共有 32 个整数通用寄存器:
x0 ~ x31
其中最特殊的是:
x0 = 0
无论向 x0 写入什么,读出来永远是 0。
四、x10 为什么又叫 a0?
做 Zephyr 或 Linux RISC-V 开发时,经常看到:
a0
a1
a2
sp
ra
t0
s0
而不是:
x10
x11
x12
x2
x1
x5
x8
这是因为 RISC-V ABI 给寄存器定义了更加方便的软件名称。
例如:
x0 → zero
x1 → ra
x2 → sp
x5~x7 → t0~t2
x10~x17 → a0~a7
x8~x9 → s0~s1
其中:
ra = Return Address
sp = Stack Pointer
a0~a7 = Function Arguments
所以:
int add(int a, int b)
{
return a + b;
}
在 RISC-V ABI 下,可以很自然地对应:
a0 = a
a1 = b
执行 add
a0 = return value
这也是为什么你在 Zephyr 汇编代码里经常看到 a0。
五、RISC-V 指令到底长什么样?
程序员看到的是:
add a0, a1, a2
但 CPU 真正看到的是二进制编码。
最典型的 R-type 指令可以抽象成:
31 25 24 20 19 15 14 12 11 7 6 0
┌────────────┬────────┬────────┬───────┬────────┬─────────┐
│ funct7 │ rs2 │ rs1 │funct3 │ rd │ opcode │
└────────────┴────────┴────────┴───────┴────────┴─────────┘
其中:
rs1 / rs2 → 输入寄存器
rd → 目标寄存器
opcode → 指令大类
funct3
funct7 → 进一步确定具体操作
例如:
add a0, a1, a2
实际上就是告诉 CPU:
rs1 = a1
rs2 = a2
rd = a0
operation = ADD
RISC-V 的一个重要特点就是指令格式比较规则,这对 CPU Decode 非常重要。
六、RISC-V 的模块化扩展
RISC-V 和传统的一些 ISA 一个很大的区别是:
它不是一个固定不变的指令集合,而是采用模块化扩展。
最基础的是:
I = Integer
然后可以增加:
M = Multiply / Divide
A = Atomic
F = Single Precision Floating Point
D = Double Precision Floating Point
C = Compressed
V = Vector
例如:
RV64IMAC
可以理解成:
RV64
+ I
+ M
+ A
+ C
而:
RV64GC
中的 G 是一组常用扩展的组合。
这种设计非常适合 SoC:
简单 MCU
↓
RV32I
需要乘除法
↓
RV32IM
需要 RTOS / SMP / 原子操作
↓
RV64IMA...
需要向量计算
↓
+ V
CPU 可以根据应用场景选择不同的 ISA 能力。
七、为什么 -march 对 RISC-V 特别重要?
编译 RISC-V 程序时,经常会看到:
-march=rv64imac
它告诉编译器:
目标 CPU 支持哪些 ISA 扩展。
例如 CPU 实际只有:
RV64IMAC
但编译器却生成了:
V Extension
中的向量指令,那么 CPU 执行时就可能产生:
Illegal Instruction
整个过程就是:
Compiler
│
│ -march
▼
生成目标 ISA
│
▼
RISC-V CPU Decode
│
├── 支持 → Execute
│
└── 不支持 → Illegal Instruction
这也是 BSP 开发中遇到"非法指令"时,一个非常值得首先检查的地方。
八、CSR:RISC-V 不只有 x0~x31
如果开始研究 Zephyr、异常和中断,会遇到大量:
mstatus
mie
mip
mtvec
mcause
mepc
mtval
satp
这些并不是普通通用寄存器,而是:
CSR
Control and Status Register
CSR 是 RISC-V 特权架构非常重要的一部分。
例如:
mstatus
保存 Machine Mode 下的重要状态。
mie
控制哪些 Machine Interrupt 被允许。
mip
表示哪些 Interrupt 当前处于 pending 状态。
而:
mtvec
则决定发生 Trap 后 CPU 应该跳到哪里。
所以发生一个异常时,可以粗略理解成:
CPU
│
│ Exception / Interrupt
▼
mtvec
│
▼
Trap Handler
│
├── mcause
├── mepc
└── mtval
其中:
mcause → 为什么进入 Trap
mepc → Trap 发生时执行到哪里
mtval → 与异常相关的附加信息
这几个 CSR 后面分析 Zephyr Trap 和 Interrupt 时会反复出现。
九、RISC-V ISA 和 Zephyr Driver 是什么关系?
到这里,就可以把 ISA 和我们实际写的 Zephyr Driver 联系起来了。
例如一个非常普通的 Zephyr Driver:
static int foo_enable(const struct device *dev)
{
const struct foo_config *cfg = dev->config;
sys_write32(FOO_ENABLE,
cfg->base + FOO_CTRL);
return 0;
}
表面上我们只是调用:
sys_write32()
但往下追:
Zephyr Driver
│
▼
sys_write32()
│
▼
MMIO
│
▼
RISC-V Store Instruction
│
▼
Bus
│
▼
Peripheral Register
最终 CPU 执行的其实就是类似:
sw t0, 0(t1)
也就是:
RISC-V Store Word。
所以一个看似普通的:
sys_write32(value, addr);
实际上已经直接落到了 ISA。
十、为什么理解 Load/Store 对 BSP 很重要?
假设 DTS 中定义:
uart0: uart@10000000 {
reg = <0x10000000 0x1000>;
};
那么 Driver 得到:
base = 0x10000000
然后:
sys_write32(value, base + UART_CTRL);
最终的数据路径就是:
DTS
│
▼
Zephyr Driver
│
▼
MMIO Address
│
▼
RISC-V sw
│
▼
SoC Bus
│
▼
UART Register
这也是为什么做 BSP 时,下面几个东西不能割裂开来看:
DTS
Driver
MMIO
ISA
Hardware
它们实际上是一条完整的数据通路。
十一、A 扩展:Zephyr 为什么需要 Atomic?
RISC-V 的:
A Extension
提供原子操作,例如:
LR/SC
AMOADD
AMOSWAP
AMOAND
AMOOR
这和 RTOS 中的:
atomic
spinlock
SMP synchronization
关系非常密切。
例如:
atomic_add(&counter, 1);
在支持 A 扩展的 RISC-V CPU 上,底层就可能映射到:
amoadd.w
于是:
Zephyr API
│
▼
Architecture Layer
│
▼
RISC-V A Extension
│
▼
Atomic Instruction
这就是一个非常典型的:
RTOS API → ISA
映射。
十二、把 RISC-V ISA 和 Zephyr 串起来
现在可以重新看最开始的问题:
RISC-V ISA 到底和 Zephyr 有什么关系?
实际上可以总结成:
Zephyr
│
┌───────────┼───────────┐
▼ ▼ ▼
Driver Kernel Scheduler
│ │ │
MMIO Atomic Context Switch
│ │ │
▼ ▼ ▼
Load/Store A Extension Registers
│ │ │
└───────────┼───────────┘
▼
RISC-V ISA
│
┌──────┼──────┐
▼ ▼ ▼
CSR Trap Privilege
│ │
└──┬───┘
▼
CPU
所以:
Zephyr Driver
↓
Architecture Layer
↓
RISC-V ISA
↓
CPU
↓
SoC Hardware
这其实就是 RISC-V BSP 最核心的一条链路。
十三、最后:应该怎么看 RISC-V?
如果只是学习 RISC-V,可以从:
ADD
SUB
LW
SW
开始。
但是如果你的目标是:
Zephyr
BSP
Bootloader
CPU
SoC
那么更值得重点理解的是:
ISA
│
├── Register / ABI
│
├── Instruction Encoding
│
├── Load / Store
│
├── ISA Extensions
│
├── CSR
│
├── Trap
│
└── Privilege
然后再进入:
PLIC
CLINT
CLIC
Timer
Interrupt
MMU / MPU
Context Switch