如何10分钟上手rv:从零编译运行你的第一个RISC-V CPU模拟器(零基础教程)
【免费下载链接】rv32-bit RISC-V CPU in ~800 lines of C89项目地址: https://gitcode.com/gh_mirrors/rv/rv
rv是一个仅用约 800 行 C89 代码实现的 32 位 RISC-V CPU 模拟器(Emulator),完整支持RV32IMAC_Zicsr指令集,并成功启动了完整的 Linux 系统。对于想入门 RISC-V 架构、CPU 仿真或系统编程的零基础读者来说,它是目前"代码量最小、最容易通读"的 RISC-V CPU 模拟器之一,本教程将带你 10 分钟内从零完成编译与运行。
为什么选择 rv:极简 RISC-V CPU 模拟器的核心亮点 💡
在动手之前,先了解这个 RISC-V CPU 模拟器有哪些过人之处:
| 特性 | 说明 |
|---|---|
| 📏 代码量极小 | 整个 CPU 核心仅约 800 行 C89,一个下午就能通读 |
| 🎯 指令集完整 | 实现RV32IMAC_Zicsr,支持 M 模式与 S 模式 |
| ✅ 经过验证 | 通过riscv-tests中所有受支持的测试用例 |
| 🐧 真实能力 | 可启动 RISC-V 32 位 Linux 系统 |
| 🧩 API 极简 | 仅需 2 个必选函数 + 1 个内存回调,无任何内存分配 |
核心思想:rv 把 CPU 建模为"寄存器状态 + 程序计数器 + 内存回调",你只需提供一块内存,调用 rv_init 初始化、循环调用 rv_step 单步执行即可,就像亲手"转动 CPU 的曲柄"。
第一步:准备环境并获取 rv 源码(1分钟)
环境要求:Linux 或 macOS,安装任意 C 编译器(GCC/Clang)与make即可,无需任何 RISC-V 工具链。
# 克隆 rv 源码仓库 git clone https://gitcode.com/gh_mirrors/rv/rv cd rv项目结构一览(后文都会用到):
rv/ ├── rv.c / rv.h # 核心:800 行 CPU 模拟器(本项目的灵魂) ├── tools/ │ ├── example/ # 最小示例:2 条指令的 RISC-V 程序 │ ├── test/ # riscv-tests 测试运行器 │ ├── linux/ # 完整的 Linux 机器模拟器(CPU+CLINT+PLIC+UART) │ └── baremetal/ # 裸机程序链接脚本 link.ld └── LICENSE.txt第二步:最快编译运行步骤——跑通你的第一条 RISC-V 指令 ⚡
进入示例目录,一条命令即可完成编译并运行:
cd tools/example make ./example你会看到如下输出:
Environment call @ 80000004: 42🎉恭喜!你的第一个 RISC-V CPU 模拟器已经跑起来了!
这段输出意味着什么?rv 在0x80000000处加载了仅 2 条指令的程序(见 example.c):
add a7, a7, 42—— 把寄存器 a7 的值变成 42;ecall—— 触发环境调用异常。
CPU 模拟单步执行到ecall时,mepc(异常程序计数器)指向0x80000004,a7(x17)中的值正是 42。这正是 RISC-V 异常机制最直观的演示。
第三步:读懂 rv 的极简 API(可选进阶)
整个模拟器对外的接口只有 3 个(定义于 rv.h):
void rv_init(rv *cpu, void *user, rv_bus_cb bus_cb); /* 初始化/复位 CPU */ rv_u32 rv_step(rv *cpu); /* 单步执行 1 条指令 */ /* bus_cb:内存访问回调,返回 RV_BAD 表示访问失败 */- rv_init:将 PC 复位到
0x80000000,设置misa支持 I/M/A/C/S/U 扩展; - rv_step:取指、译码、执行,遇到异常时返回异常码(如
RV_EMECALL); - bus_cb:由你自己实现的内存读写回调,rv 不关心内存从哪来。
得益于"零内存分配 + 固定状态结构"的设计,你可以把rv结构体直接嵌进任何嵌入式系统或 Fuzz 框架中使用。
第四步:运行官方 riscv-tests 测试集(可选,需要 RISC-V 工具链)
如果想像 CI 一样验证模拟器的正确性,可用 tools/test/ 中的运行器:
- 安装
riscv64-unknown-elf工具链与riscv-tests,并设置环境变量RISCV; - 执行
make -C tools/test,它会自动拉取rv32ui/rv32si/rv32mi/rv32uc/rv32um/rv32ua全套指令测试向量; - 测试脚本 test.py 会逐条运行并打印
OK/FAIL。
这也是理解"指令对齐异常、非法指令异常、环境调用"等 RISC-V 异常体系的最佳实践材料(见 run_test.c 中的 CPU 状态 dump 函数)。
终极挑战:在这个 800 行 CPU 上启动 Linux 🐧
tools/linux/目录包含一台完整的机器模拟器 mach.c,硬件配置包括:
- 🧠 rv CPU 核心(128 MiB RAM)
- ⏱ CLINT 核间/定时器中断控制器(rv_clint.c)
- 🔔 PLIC 平台级中断控制器(rv_plic.c)
- 🔤 SiFive UART 串口 ×2(rv_uart.c,基于 ncurses 显示终端)
按照 tools/linux/README.md 的步骤,用 buildroot 交叉编译 OpenSBI + Linux 内核 + 设备树(rv.dts),然后运行:
make mach ./mach buildroot/output/images/fw_payload.bin buildroot/output/images/rv.dtb屏幕上将打印 Linux 启动日志——一台"800 行 CPU"跑起了真实的操作系统。
常见问题 FAQ 📚
Q1:为什么代码里看不到一个 switch 语句?A:作者有意为之——RISC-V 指令编码高度正交,用分支表达式直接计算"操作数来源、移位量、目标寄存器",比冗长的 switch + break 更短更清晰。
Q2:它真的是纯 C89 吗?A:基本是。全程未使用超过 32 位的整数类型(乘法也是 32 位实现),对类型宽度有少量假设,详见 README.md 的 Caveats 章节。
Q3:rv 可以用于学习和教学吗?A:非常适合。协议为 MIT 许可证(LICENSE.txt),你可以自由修改、重写(比如用 switch 重译指令分发),非常适合课程实验。
Q4:性能如何?A:rv 的设计目标是简洁可读而非速度,官方 FAQ 对"Slow!"的回答是 😄 完全够用。
总结:下一步学什么 🚀
| 你的目标 | 推荐路径 |
|---|---|
| 理解 CPU 模拟器原理 | 精读 rv.c 的rv_step,配合misa扩展位定义 |
| 学习 RISC-V 异常/中断 | 用tools/test/复现 riscv-tests 中的异常用例 |
| 动手改硬件 | 在tools/linux/mach.c中新增一个外设地址段 |
| 交叉编译裸机程序 | 参考 tools/baremetal/link.ld 与 README 中的 GCC 命令 |
从 800 行代码读懂一颗 RISC-V CPU,再到亲手启动 Linux——rv 为零基础读者提供了一条最短、最完整的学习路径。现在,去运行你的第一条指令吧!
【免费下载链接】rv32-bit RISC-V CPU in ~800 lines of C89项目地址: https://gitcode.com/gh_mirrors/rv/rv
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考