RISC-V 基本指令入门:先看懂 ISA,再看懂指令

如果你第一次接触 RISC-V,最容易犯的错是把它当成“一张指令表”来背。这样学很快会卡住,因为 RISC-V 真正重要的不是零散指令,而是它背后的组织方式:这是一套模块化的 ISA 家族,先有基础整数架构,再往上叠加各种标准扩展。

这篇文章是这个系列的第一篇。我不想一上来就把编码格式和助记符堆满,而是先把坐标系放正:什么是 RV32I、什么是 RV64IhartEEI 到底在说什么,寄存器和指令格式为什么长这样,以及初学者最该先掌握哪些基础指令组。

先记住一句话

RISC-V 不是“只有一套固定 CPU 指令”,而是“一个可以拼装的 ISA 组合”。

最核心的部分叫基础整数 ISA,也就是 I。围绕它,RISC-V 再增加乘除法、原子操作、浮点、压缩指令等扩展,于是形成了 RV32IMAFDCRV64GC 这样的字符串。你看到的不是产品型号,而是这颗 CPU 支持哪些 ISA 能力。

这也是 RISC-V 学习顺序的关键:先理解基础整数架构,再看扩展,不要反过来。

hartEEIXLEN

官方文档里有几个词,初学者很容易混。

hart 是 hardware thread 的缩写。你可以把它理解成“一个能独立执行指令流的硬件执行实体”。它不等同于整颗芯片,也不等同于一个操作系统线程。

EEI 是 execution environment interface,意思是执行环境接口。它描述的是:这颗 hart 运行在什么样的环境里,异常、地址、特权、I/O 等行为由谁来提供和约束。对初学者来说,最重要的是知道:ISA 只规定“指令应该做什么”,EEI 才把它放进真实平台里。

XLEN 则是整数寄存器和通用整数运算的位宽。RV32XLEN=32RV64XLEN=64。这会直接影响寄存器大小、地址宽度和很多指令的行为。

所以,RV32IRV64I 的关系可以简单理解为:后者保留前者的基础语义,但运行在更宽的整数世界里,并增加了一些更适合 64 位环境的指令形式。

先看寄存器,而不是先背指令

RISC-V 基础整数架构里有 32 个通用整数寄存器,名字是 x0x31。其中最重要的几个规则是:

  • x0 永远是 0,写进去也没用
  • pc 是程序计数器,指向当前或下一条要执行的指令
  • x1 通常叫 ra,返回地址寄存器
  • x2 通常叫 sp,栈指针

这套设计非常“工程化”。你不需要一开始就记住每个 ABI 名字,但至少要知道:x0 代表常量零,x1x2 在函数调用里特别重要。很多看起来复杂的指令,其实都是在围绕这 32 个寄存器搬数据。

指令格式为什么看起来有点绕

RISC-V 常见的基础指令格式可以先认识这六种:RISBUJ

  • R 型:寄存器到寄存器运算
  • I 型:带立即数,或者加载类指令常用
  • S 型:存储类指令常用
  • B 型:条件分支
  • U 型:高位立即数
  • J 型:无条件跳转

初学者不用急着去背每个 bit 位的分布,先记住“这几种格式分别负责什么”。你会发现,RISC-V 的指令设计很强调分工:算术、访存、分支、跳转,各有自己的模板。

基础指令组,先掌握这几类

如果只看 RV32I 的基础整数指令,我建议先把下面几组当成学习主线。

1. 算术、逻辑和移位

这一组是“让寄存器里的值发生变化”的核心。

典型指令包括:

  • addi
  • addsub
  • andiorixori
  • sllisrlisrai

它们的作用很直观:做加减、位运算和移位。对于初学者来说,最重要的是理解 addi 很常用,因为它既能做加法,也常被拿来装载小立即数。

2. 取数和存数

RISC-V 是典型的 load/store 架构。也就是说,真正做运算的是寄存器,内存只通过专门的访存指令进入和退出。

典型指令包括:

  • lblhlw
  • sbshsw

读内存时是 load,写内存时是 store。这条规则非常重要,因为它决定了你不能拿任何算术指令直接去操作内存地址中的内容。

3. 分支和跳转

程序之所以能“跑起来”,靠的不是算术,而是控制流。

典型指令包括:

  • beqbne
  • bltbge
  • jal
  • jalr

条件分支负责“如果满足条件就跳过去”,jaljalr 则负责函数调用、返回和间接跳转。函数调用为什么和 ra 有关系,往往就是从这里开始理解的。

4. 上高位立即数和地址构造

单条指令里的立即数位数有限,所以 RISC-V 提供了专门的“装高位”思路。

典型指令包括:

  • lui
  • auipc

lui 用来放一个高位常量,auipc 则更像“把当前 pc 和高位立即数结合起来”,常用于构造位置无关的地址。这一组在理解函数跳转、全局地址和链接器配合时会越来越重要。

5. ecallebreak

这两个指令不属于日常算术,但它们是系统交互和调试里的入口。

  • ecall 用于环境调用
  • ebreak 用于断点

它们提醒我们:ISA 不只是“算数机器”,还要给操作系统、运行时和调试器留接口。

一个最小例子,先看懂数据怎么走

下面这段代码不复杂,但很适合建立直觉:

addi a0, zero, 1     # a0 = 1
slli a0, a0, 2       # a0 = a0 << 2
sw   a0, 0(sp)       # 把结果写到栈顶
beq  a0, zero, done  # 如果结果为 0 就跳转
done:

这里能看到几件事:

  • zero 就是 x0
  • a0 是 ABI 名字,对应 x10
  • spx2
  • 运算先在寄存器里完成,最后才写回内存

这就是 RISC-V 最基础的执行模型:先搬到寄存器,再运算,再按需要读写内存。

RV32IRV64I 的差别

RV64I 不是“换了一套完全不同的指令集”,而是在 RV32I 的思路上,适配了更宽的整数世界。

对初学者来说,最值得记住的是:

  • RV32 的基本整数宽度是 32 位
  • RV64 的基本整数宽度是 64 位
  • RV64I 里有一批以 W 结尾的指令,专门处理 32 位结果并做符号扩展

这些 *W 指令的存在非常实用,因为真实软件里并不是所有数据都天然是 64 位。很多时候,你还是要明确地保留 32 位语义。

看到 ISA 字符串时怎么读

RISC-V 的 ISA 命名规则本身就是文档的一部分。看到 RV64IMAFDC 这种字符串时,可以拆开来理解:

  • RV64 表示 XLEN=64
  • I 是基础整数 ISA
  • M 是乘除法扩展
  • A 是原子扩展
  • FD 是单精度、双精度浮点扩展
  • C 是压缩指令扩展

你还会经常看到 G。它是个很有用的简写,表示一组常见的通用能力集合,初学者可以把它理解成“很多通用系统软件默认会期待的那组基础扩展组合”。

这里顺手提醒一句:CSRFENCE 相关能力在 RISC-V 里有标准扩展边界,不要把它们随口当成“最小基础整数 ISA 一定自带”的内容。学文档时要注意这个分界。

这篇文章该怎么收尾

如果你刚开始学 RISC-V,最好的路径不是先背完全部助记符,而是先把这几个问题想清楚:

  1. 1. 这套 ISA 是怎么分层的
  2. 2. 寄存器、pc 和内存之间是什么关系
  3. 3. 哪些指令负责算术,哪些负责访存,哪些负责控制流
  4. 4. RV32RV64 为什么看起来相似又不完全一样

只要这几层理解稳了,后面再看原子操作、特权态、CSR、异常和中断,你会轻松很多。

下一篇我准备继续写 RISC-V 系列里更容易和实际代码联系起来的一部分,比如函数调用约定、栈帧和一条简单函数在汇编里是怎么走的。那一篇会更接近“读得懂代码”的阶段。

吉ICP备2024014750号-1 备案图标 京公网安备11011202100605号