RISC-V 基本指令入门:先看懂 ISA,再看懂指令
如果你第一次接触 RISC-V,最容易犯的错是把它当成“一张指令表”来背。这样学很快会卡住,因为 RISC-V 真正重要的不是零散指令,而是它背后的组织方式:这是一套模块化的 ISA 家族,先有基础整数架构,再往上叠加各种标准扩展。
这篇文章是这个系列的第一篇。我不想一上来就把编码格式和助记符堆满,而是先把坐标系放正:什么是 RV32I、什么是 RV64I,hart 和 EEI 到底在说什么,寄存器和指令格式为什么长这样,以及初学者最该先掌握哪些基础指令组。
先记住一句话
RISC-V 不是“只有一套固定 CPU 指令”,而是“一个可以拼装的 ISA 组合”。
最核心的部分叫基础整数 ISA,也就是 I。围绕它,RISC-V 再增加乘除法、原子操作、浮点、压缩指令等扩展,于是形成了 RV32IMAFDC、RV64GC 这样的字符串。你看到的不是产品型号,而是这颗 CPU 支持哪些 ISA 能力。
这也是 RISC-V 学习顺序的关键:先理解基础整数架构,再看扩展,不要反过来。
hart、EEI 和 XLEN
官方文档里有几个词,初学者很容易混。
hart 是 hardware thread 的缩写。你可以把它理解成“一个能独立执行指令流的硬件执行实体”。它不等同于整颗芯片,也不等同于一个操作系统线程。
EEI 是 execution environment interface,意思是执行环境接口。它描述的是:这颗 hart 运行在什么样的环境里,异常、地址、特权、I/O 等行为由谁来提供和约束。对初学者来说,最重要的是知道:ISA 只规定“指令应该做什么”,EEI 才把它放进真实平台里。
XLEN 则是整数寄存器和通用整数运算的位宽。RV32 的 XLEN=32,RV64 的 XLEN=64。这会直接影响寄存器大小、地址宽度和很多指令的行为。
所以,RV32I 和 RV64I 的关系可以简单理解为:后者保留前者的基础语义,但运行在更宽的整数世界里,并增加了一些更适合 64 位环境的指令形式。
先看寄存器,而不是先背指令
RISC-V 基础整数架构里有 32 个通用整数寄存器,名字是 x0 到 x31。其中最重要的几个规则是:
x0永远是 0,写进去也没用pc是程序计数器,指向当前或下一条要执行的指令x1通常叫ra,返回地址寄存器x2通常叫sp,栈指针
这套设计非常“工程化”。你不需要一开始就记住每个 ABI 名字,但至少要知道:x0 代表常量零,x1 和 x2 在函数调用里特别重要。很多看起来复杂的指令,其实都是在围绕这 32 个寄存器搬数据。
指令格式为什么看起来有点绕
RISC-V 常见的基础指令格式可以先认识这六种:R、I、S、B、U、J。
R型:寄存器到寄存器运算I型:带立即数,或者加载类指令常用S型:存储类指令常用B型:条件分支U型:高位立即数J型:无条件跳转
初学者不用急着去背每个 bit 位的分布,先记住“这几种格式分别负责什么”。你会发现,RISC-V 的指令设计很强调分工:算术、访存、分支、跳转,各有自己的模板。
基础指令组,先掌握这几类
如果只看 RV32I 的基础整数指令,我建议先把下面几组当成学习主线。
1. 算术、逻辑和移位
这一组是“让寄存器里的值发生变化”的核心。
典型指令包括:
addiadd、subandi、ori、xorislli、srli、srai
它们的作用很直观:做加减、位运算和移位。对于初学者来说,最重要的是理解 addi 很常用,因为它既能做加法,也常被拿来装载小立即数。
2. 取数和存数
RISC-V 是典型的 load/store 架构。也就是说,真正做运算的是寄存器,内存只通过专门的访存指令进入和退出。
典型指令包括:
lb、lh、lwsb、sh、sw
读内存时是 load,写内存时是 store。这条规则非常重要,因为它决定了你不能拿任何算术指令直接去操作内存地址中的内容。
3. 分支和跳转
程序之所以能“跑起来”,靠的不是算术,而是控制流。
典型指令包括:
beq、bneblt、bgejaljalr
条件分支负责“如果满足条件就跳过去”,jal 和 jalr 则负责函数调用、返回和间接跳转。函数调用为什么和 ra 有关系,往往就是从这里开始理解的。
4. 上高位立即数和地址构造
单条指令里的立即数位数有限,所以 RISC-V 提供了专门的“装高位”思路。
典型指令包括:
luiauipc
lui 用来放一个高位常量,auipc 则更像“把当前 pc 和高位立即数结合起来”,常用于构造位置无关的地址。这一组在理解函数跳转、全局地址和链接器配合时会越来越重要。
5. ecall 和 ebreak
这两个指令不属于日常算术,但它们是系统交互和调试里的入口。
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就是x0a0是 ABI 名字,对应x10sp是x2- 运算先在寄存器里完成,最后才写回内存
这就是 RISC-V 最基础的执行模型:先搬到寄存器,再运算,再按需要读写内存。
RV32I 和 RV64I 的差别
RV64I 不是“换了一套完全不同的指令集”,而是在 RV32I 的思路上,适配了更宽的整数世界。
对初学者来说,最值得记住的是:
RV32的基本整数宽度是 32 位RV64的基本整数宽度是 64 位RV64I里有一批以W结尾的指令,专门处理 32 位结果并做符号扩展
这些 *W 指令的存在非常实用,因为真实软件里并不是所有数据都天然是 64 位。很多时候,你还是要明确地保留 32 位语义。
看到 ISA 字符串时怎么读
RISC-V 的 ISA 命名规则本身就是文档的一部分。看到 RV64IMAFDC 这种字符串时,可以拆开来理解:
RV64表示XLEN=64I是基础整数 ISAM是乘除法扩展A是原子扩展F和D是单精度、双精度浮点扩展C是压缩指令扩展
你还会经常看到 G。它是个很有用的简写,表示一组常见的通用能力集合,初学者可以把它理解成“很多通用系统软件默认会期待的那组基础扩展组合”。
这里顺手提醒一句:CSR 和 FENCE 相关能力在 RISC-V 里有标准扩展边界,不要把它们随口当成“最小基础整数 ISA 一定自带”的内容。学文档时要注意这个分界。
这篇文章该怎么收尾
如果你刚开始学 RISC-V,最好的路径不是先背完全部助记符,而是先把这几个问题想清楚:
- 1. 这套 ISA 是怎么分层的
- 2. 寄存器、
pc和内存之间是什么关系 - 3. 哪些指令负责算术,哪些负责访存,哪些负责控制流
- 4.
RV32和RV64为什么看起来相似又不完全一样
只要这几层理解稳了,后面再看原子操作、特权态、CSR、异常和中断,你会轻松很多。
下一篇我准备继续写 RISC-V 系列里更容易和实际代码联系起来的一部分,比如函数调用约定、栈帧和一条简单函数在汇编里是怎么走的。那一篇会更接近“读得懂代码”的阶段。
京公网安备11011202100605号