1. 课程导入

欢迎进入《RISC-V CPU从RTL仿真到FPGA原型验证项目》课程。本课程不是单纯讲解 RISC-V 指令集,也不是只教某个 EDA 工具的操作,而是围绕一个可运行、可验证、可上板的 RISC-V CPU 项目,把 RTL 设计、功能仿真、波形调试、综合实现、FPGA 原型验证 串成一条完整工程链路。学完本课程,你应当能够独立完成一个小型 RISC-V 处理器的仿真验证,并把它部署到 FPGA 开发板上运行测试程序。

一、学习目标:从“会写模块”到“能交付原型”

很多学习者能写 Verilog 模块,却无法回答“这个 CPU 到底能不能跑通程序”。本课程的目标是建立端到端的交付能力,具体分为四个层次:

  • 理解架构:掌握 RV32I 指令格式、寄存器堆、ALU、取指译码执行访存写回等基本数据通路。
  • 掌握仿真:能编写 testbench,使用 Verilator 或 Icarus Verilog 进行 RTL 仿真,并通过波形定位问题。
  • 完成实现:能编写约束文件,完成综合、布局布线,生成比特流并下载到 FPGA。
  • 原型验证:能通过 UART、LED、ILA 等片上资源观察 CPU 运行状态,验证指令执行结果。
核心判断标准:不是“代码编译通过”,而是“同一段测试程序在 RTL 仿真和 FPGA 上得到一致结果”。

二、核心概念:RTL、仿真与原型验证的关系

RTL(Register Transfer Level)是用硬件描述语言描述寄存器之间数据传输与逻辑运算的抽象层次。RTL 仿真是在软件环境中对设计施加激励并观察响应,它速度快、可观测性强,但不等同于真实硬件。FPGA 原型验证则是把 RTL 综合映射到可编程逻辑阵列中,以接近真实时序的方式运行。

三者关系可以概括为:RTL 是源,仿真是第一道筛子,FPGA 原型是最终试金石。仿真能发现绝大多数功能错误,但无法完全覆盖时序、复位、跨时钟域和板级电气问题;FPGA 原型能暴露真实问题,但调试可观测性弱于仿真。因此本课程强调“先仿真后上板、仿真与上板结果对齐”的方法论。

RISC-V CPU 项目路线图:从 RTL 到 FPGA 原型 RTL 设计 RV32I 数据通路 功能仿真 testbench + 波形 综合实现 约束 + 布局布线 FPGA 原型 下载 + 板级验证 仿真阶段关注 指令语义正确性 寄存器/存储器读写 上板阶段关注 时序收敛与复位 UART/LED 可观测性 一致性校验 同一测试程序 仿真结果 = 板级结果 项目主线:RTL 编码 → 单元仿真 → 系统仿真 → 综合约束 → 比特流下载 → 板级测试 → 问题回溯 每一阶段都有明确交付物与验收条件,避免“只写代码不验证”

三、整体框架:课程知识模块与工具链

课程按“设计—验证—实现—原型”四段推进,每段都配有可操作任务。下表给出模块划分与关键工具,便于你建立全局认知。

阶段 核心内容 关键工具/文件 交付物
RTL 设计 RV32I 取指、译码、执行、访存、写回 Verilog/SystemVerilog、编辑器 可综合 RTL 源码
功能仿真 指令级测试、波形调试、覆盖率 Verilator、Icarus、GTKWave 仿真日志与波形
综合实现 时序约束、资源映射、布局布线 Vivado/Quartus、XDC/SDC 比特流与资源报告
FPGA 原型 下载、UART 输出、ILA 抓取 开发板、串口助手、ILA 板级运行结果

四、方法流程:一个最小可运行闭环

为了避免“学完不知道做了什么”,本课程始终围绕一个最小闭环推进。你可以把它理解为项目的主循环:

  1. 编写或修改 RTL 模块,例如 ALU 或寄存器堆。
  2. 编写 testbench,施加激励并检查输出。
  3. 运行仿真,观察波形与日志,定位错误。
  4. 修正 RTL,重复仿真直到通过。
  5. 添加约束,综合实现并生成比特流。
  6. 下载到 FPGA,运行测试程序,比对结果。

下面是一个典型的仿真命令示例,后续章节会反复使用类似流程:

# 使用 Verilator 进行 RTL 仿真
verilator --cc --exe --build top.v tb_top.cpp -o sim_cpu
./obj_dir/sim_cpu

# 使用 Icarus Verilog + GTKWave 查看波形
iverilog -o cpu_sim top.v tb_top.v
vvp cpu_sim
gtkwave dump.vcd
建议每完成一个模块就立即仿真,不要等整个 CPU 写完再调试。越早发现错误,定位成本越低。

五、案例场景:从一条 add 指令看全链路

假设我们要验证 add x3, x1, x2 这条指令。在 RTL 仿真中,testbench 会初始化 x1=5、x2=7,执行后检查 x3 是否等于 12。若仿真通过,我们再把同样的测试程序烧录到 FPGA,通过 UART 打印 x3 的值。如果仿真得到 12,而板级得到 0 或随机值,就说明问题可能出在复位、时钟、存储器初始化或约束上,而不是指令语义本身。

这个案例说明:仿真和原型验证不是重复劳动,而是互相印证的两个视角。仿真帮你确认逻辑正确,原型帮你确认硬件可用。

六、常见误区与复盘

  • 误区一:只写 RTL 不做仿真。 结果上板后无法定位问题,只能盲目改代码。
  • 误区二:仿真通过就认为上板一定通过。 忽略时序、复位和跨时钟域问题。
  • 误区三:不保存波形和日志。 问题复现困难,无法回溯。
  • 误区四:测试程序过于简单。 只跑一条指令,无法覆盖分支、访存和流水线冲突。
复盘要点:每次上板失败,都要先问“仿真是否覆盖了该场景”。如果仿真没覆盖,先补仿真;如果仿真通过但上板失败,优先检查约束、复位和时钟。

本课程后续将沿着这条路线,从 RV32I 最小核开始,逐步加入仿真验证、综合约束和 FPGA 原型测试。你不需要一开始就拥有高端开发板,但需要保持“每写一段代码就验证一次”的工程习惯。掌握这条链路后,你不仅能完成一个 RISC-V CPU 原型,也能把同样的方法迁移到其他数字系统设计中。