
这个系列写到第三十二篇的时候我一直在犹豫要不要专门开一篇 RISC-V。原因很简单——RISC-V 相关的开源项目太多了挑两个有代表性的出来写反倒容易得罪人。但既然系列走到这儿还是得把这俩补上PicoRV32和VexRiscv。这两个项目在 FPGA 圈子里属于绕不开级别的存在。一个是把精简做到极致的 Verilog 单文件软核另一个是用 SpinalHDL 写就的高度可配置五级流水线处理器。它们正好代表了 RISC-V 软核设计的两条截然不同的路线互补性非常强。这篇文章不打算做百科式介绍而是从实际使用的角度把这两个核的架构特点、资源占用、上板调试的关键步骤和坑都梳理一遍。无论你是刚接触 FPGA 软核的初学者还是准备在项目里选型 RISC-V IP 的工程师这篇文章应该都能给你一些参考。1. RISC-V 开源生态与 FPGA 的契合逻辑1.1 为什么 RISC-V 能在 FPGA 上快速流行早几年想在 FPGA 里塞一个软核处理器大多数人第一时间想到的都是 Xilinx 的 MicroBlaze 或者 Intel 的 Nios II。这两个 IP 确实成熟稳定但问题也很明显——闭源、授权费高、且被绑定在特定厂商的工具链里。你想换一家 FPGA 厂商基本等于把处理器相关的所有代码重写一遍。RISC-V 的出现把这个问题彻底解决了。指令集架构ISA开源、实现开源、工具链开源三者叠加就让在任意 FPGA 上跑一个软核变成了常规操作。不管是 10 美元的国产小芯片还是高端的 UltraScale只要综合工具能跑得动你就能把 RISC-V 核放进去。真正让 RISC-V 在 FPGA 上形成生态的还有另外几个因素。首先是工具链成熟度riscv-gnu-toolchain、riscv-openocd、GDB 这些工具配合起来调试体验已经不输商用软核。其次是可用外设和 SoC 方案越来越完善从单纯的 CPU 核到带 UART、SPI、中断控制器的完整 SoC开源社区都给出了参考设计。最后是软件生态的反哺RTOS 比如 Zephyr、FreeRTOS 都已经官方支持 RISC-VLinux 在部分核上也能跑起来这意味着你做原型验证时可以直接复用大量现成软件。1.2 从选核到选生态软核处理器的选型维度很多初学者选软核的时候只看两个指标跑多快、占多大。这个思路不能说错但在实际项目里远远不够。我自己的经验是选型至少要看四个维度。面积与性能包括 LUT/FF/BRAM 占用、最高频率、DMIPS 或者 CoreMark 跑分。这个决定了你能把核放进多大的芯片里以及能不能满足实时性要求。功能完整性支持哪些指令扩展M 乘除、F 单精度浮点、C 压缩指令、有没有中断控制器、MMU、调试模块。这些决定了软件能跑多复杂以及系统是否方便调试。可配置性与易用性指令扩展能不能裁剪、存储接口是 AXI 还是自定义时序、生成 Verilog 的方式是直接改参数还是需要构建环境。如果你只在 FPGA 里跑一个简单的状态机加协议转换那一个庞大又复杂的可配置核反而是负担。文档与社区活跃度有没有完整的示例工程、能不能方便地找到适配的外设和 SoC 参考设计、issue 响应速度如何。这一点在项目卡壳的时候尤其重要。把这两个项目放在这套维度里看区别就很清晰了。PicoRV32 面向的是麻雀虽小五脏俱全适合快速嵌入、资源紧张的场景VexRiscv 面向的是按需定制、面向研究和复杂 SoC适合做教学、做 Linux 原型、做自定义指令实验。下面分别展开。2. PicoRV32用极简换通用的经典单文件软核2.1 设计定位与核心特性PicoRV32 是 Clifford Wolf 在 2015 年左右发布的 RISC-V 软核整个处理器核心就集中在一个picorv32.v文件里不依赖任何厂商原语做到了真正的复制即用。这个设计理念在开源软核里非常罕见也是它这么多年经久不衰的根本原因。它支持 RV32I 基础指令集并通过参数可以选择开启 RV32M乘除法扩展、RV32C压缩指令扩展还提供了一个名为PICORV32_REGS的定制寄存器文件。接口方面也很有意思对外是一个简单的 mem 接口通过mem_valid/mem_ready握手协议访问指令和数据存储。因为时序非常简单无论是接片上 BRAM 还是接 SPI Flash都不需要复杂的总线桥。常有人问我PicoRV32 和市面上其他精简核相比到底强在哪。我的回答是它强在没有多余的东西。同样是 RV32I 指令集很多实现为了追求性能加了一堆流水线优化但 PicoRV32 采用的是 3 级简化的取指-译码-执行结构天然面积就小。在资源有限的 FPGA 上比如 ICE40 这种只有几百个 LUT 的芯片PicoRV32 依然能跑起来这是很多流水线核做不到的。2.2 架构细节与资源占用分析PicoRV32 的微架构并不复杂核心是一个有限状态机驱动的取指和执行单元。它在取指阶段发起mem_valid请求等待外设返回mem_ready和mem_rdata后进入译码与执行。正因为没有复杂的流水线冒险处理控制逻辑非常紧凑。关于资源占用我这里给一组典型综合结果。在 Artix-7 系列上开启 RV32IMC整数 乘除法 压缩指令的情况下LUT 占用大约在 800 到 1100 之间FF 在 500 上下如果把乘除法关闭只保留 RV32ILUT 可以压到 600 以内。如果是极致裁剪连压缩指令都不开配合优化的综合策略300 到 400 个 LUT 跑起来完全没问题。BRAM 不算在核内因为指令和数据的存储是由你在外边自行接的。配置组合大约 LUT大约 FF典型最高频率Artix-7 速度等级 -1RV32I550~650400~500150~200 MHzRV32IC650~750450~550150~200 MHzRV32IMC850~1100500~650120~180 MHzRV32IMC 中断1000~1200550~700120~180 MHz注意这个数据是我基于目前较新版本的综合结果不同工具版本、不同综合策略会有浮动关键是量级参考。PicoRV32 的最高频率并不算高但它本来就不追求极致性能而是追求在最小的面积里跑起来。对于协议转换、外设控制、状态机调度这类任务150MHz 性能绰绰有余。2.3 在 FPGA 上从零跑通 PicoRV32第一次接触 PicoRV32 的时候我推荐按下面这个路径走能少踩很多坑。第一步把仓库克隆下来在本地看一下 RTL。重点看两个文件picorv32.v和testbench目录下的仿真文件。仓库里自带的 testbench 会运行一个简单的程序你可以先用 Verilator 或者 Icarus Verilog 跑一遍仿真确认工具链和 RTL 都正常。我一般用 Verilator速度快而且能转出 C 模型做系统级验证。第二步写一个最小 SoC。刚开始不需要加太多外设就一块简单的 BRAM 当存储一个 UART 发打印信息就够了。BRAM 用 FPGA 厂商的 IP 生成或者直接用 Verilog 数组声明。关键是存储器的读写时序要和 PicoRV32 的 mem 接口对上。它的握手规则是处理器拉高mem_valid后存储器侧通过拉高mem_ready表示读写完成这个握手信号不能组合逻辑绕太久否则时序会很难收敛。第三步编写一个简单的 C 程序交叉编译出 hex 或者 bin 文件初始化到 BRAM 里。这一步有个小技巧用riscv-none-elf-gcc编译时链接脚本一定要把_start放到你设置的复位向量地址否则处理器一上电就跑飞。第四步综合布局布线上板验证。PicoRV32 的复位信号是低有效resetn上电后要有足够长的复位时间让片内存储器初始化完成。如果在调试的时候发现一上电就跑飞优先检查复位时序和存储器初始化文件路径。3. VexRiscv用 SpinalHDL 写就的可插拔处理器3.1 SpinalHDL 与 VexRiscv 的设计哲学如果你打开 VexRiscv 的源码会发现它的主要 RTL 文件并不是 Verilog而是一堆 Scala 代码。VexRiscv 使用 SpinalHDL 这个硬件描述语言来编写然后在构建时生成 Verilog。这跟直接用 Verilog/VHDL 写处理器的思路完全不同有点像是用高级语言写硬件逻辑然后再编译成 RTL。最初很多工程师对这种方式很排斥觉得多了一层工具链调试起来不够直观。但真正用过之后大部分人都能感受到它的优势。因为 SpinalHDL 本质上是一个嵌在 Scala 里的硬件描述框架可以用函数、类、继承、参数化这些软件工程的手段来组织硬件代码。VexRiscv 最核心的插件机制就是在这种设计哲学下自然形成的产物。所谓插件机制直观理解就是把处理器拆成一组可以热插拔的功能单元。你想有乘法指令就插一个 MulPlugin想有 JTAG 调试就插一个 DebugPlugin想有 MMU就插一个 MmuPlugin。处理器核心本身是一个骨架具体支持哪些功能完全由你通过 Scala 代码组合出来。这种灵活度在传统 Verilog 项目里几乎不可能实现。3.2 核心微架构与特色机制VexRiscv 是一个五级流水线设计取指IF、译码ID、执行EX、访存MEM、写回WB俱全同时支持按配置裁剪成更短的流水线。它的默认配置是单发射顺序执行通过硬件互锁来处理数据冒险也支持分支预测插件来降低流水线清空的开销。相比 PicoRV32 的简化结构VexRiscv 的设计明显更贴近现代处理器的概念。它在执行阶段集成了可选的移位器插件FullBarrelShifterPlugin、乘除法插件MulPlugin、DivPlugin、甚至浮点单元插件FpuPlugin。这些插件各自独立组合起来就能拼出一个支持 RV32IMFC 甚至带 MMU 的完整处理器。另一个值得说的特性是它的 CSR 插件和中断机制。VexRiscv 的CsrPlugin实现了 RISC-V 标准的状态寄存器访问指令和中断异常处理。通过配置可以开启机器态定时器中断、外部中断以及不同等级的异常处理模式。这对于跑 RTOS 或者 Linux 来说都是刚需也正是它比 PicoRV32 更适合做大系统的原因。3.3 通过配置参数生成 CPU 的方式要生成 VexRiscv 的 Verilog需要先准备好 Java 环境、sbt 构建工具然后把 VexRiscv 仓库克隆下来。仓库里自带了好几个预设的生成入口比如GenFull和GenSmall分别对应功能和面积优化的不同配置。也可以用 Scala 代码自定义一个配置组合然后调用生成器输出 Verilog 文件。Scalsa 生成配置的核心逻辑是往列表里插件。下面是我在自己项目里用过的一个最小配置片段简化后生成一个带总线接口和 CSR 的基础核val cpuConfig SpinalConfig( targetDirectory src/main/verilog ) cpuConfig.generateVerilog { val config VexRiscvConfig( plugins List( new IBusSimplePlugin( resetVector 0x80000000L, cmdForkOnSecondStage false, cmdForkPersistence true ), new DBusSimplePlugin( catchAddressMisaligned true, catchAccessFault true ), new CsrPlugin( config CsrPluginConfig( mtvecAccess CsrAccess.READ_WRITE, mtvecInit 0x80000000L ) ), new DecoderSimplePlugin( catchIllegalInstruction true ), new RegFilePlugin( regFileReadyKind plugin.SYNC, zeroBoot false ), new IntAluPlugin, new SrcPlugin, new FullBarrelShifterPlugin, new HazardSimplePlugin, new BranchPlugin( earlyBranch true, catchAddressMisaligned true ) ) ) new VexRiscv(config) }在命令行里跑sbt runMain vexriscv.GenFull就能在src/main/verilog目录下生成对应的 Verilog。生成的代码通常很大不适合手动修改这也是使用 SpinalHDL 的一个共识——要改配置回 Scala 源码改然后重新生成。生成的 Verilog 在接入 FPGA 工程时还需要处理总线和时钟复位。VexRiscv 的 IBus 和 DBus 都支持 AXI4 或者简单的 wishbone 风格接口具体接口取决于你选择的插件。初学者建议先用它自带的仿真环境跑一遍示例程序确认整个流程走通后再上板。4. 实操场景两个核分别适合怎样的项目4.1 轻量级控制与协议转换PicoRV32 的主场如果你在 FPGA 里只需要实现一个不太复杂的控制逻辑比如根据串口命令去操作几个外设、做一组 LED 的点亮时序、处理一些传感器数据那 PicoRV32 就是非常合适的选择。它面积小、上手快、不需要搭建复杂的 SoC 环境一个 Verilog 文件加一块 BRAM 就能构成最小系统。我自己的一个实际项目是把 PicoRV32 用在一个小型的变频器通信板上主控 FPGA 是国产的小容量芯片资源非常紧张。当时用 PicoRV32 跑 Modbus 从站协议解析顺便做几个 IO 的实时控制整体资源占用非常低还能省出一个软核的授权费。这种场景里性能不是瓶颈灵活性反而是核心需求——毕竟协议要改、寄存器要变用 Verilog 硬写状态机改起来太痛苦软核跑 C 代码就舒服得多。4.2 SoC 搭建、教学研究与 Linux 原型VexRiscv 的用武之地VexRiscv 的场景明显要更重一些。如果你想在 FPGA 上搭建一个相对完整的 SoC比如带 UART、SPI、I2C、GPIO、中断控制器还想跑一个 RTOS 甚至 Linux那 VexRiscv 的插件式配置能力和生态会让你省心很多。教学与科研场景里VexRiscv 的价值尤其突出。你可以通过插拔插件让学生直观地看到乘法器、分支预测、MMU 这些模块对处理器性能和面积的影响。也可以拿它做自定义指令的实验在译码和执行阶段插入自己的逻辑这在传统商用软核 IP 里几乎不可能实现。还有一个我很看重的场景是毕业设计和开源硬件竞赛VexRiscv 因为可配置性强能折腾出很多有意思的扩展功能。4.3 性能与面积对照实测为了让大家有更直观的感受我把两个核在相同工艺和测试条件下的表现整理成一个对照表。这里的实测数据来自 Artix-7 速度等级 -1综合工具为 Vivado 2023.1仅供参考。指标PicoRV32RV32IMCVexRiscv最小配置VexRiscv完整配置带MMU逻辑资源约 1K LUT约 1.2K~1.8K LUT约 4K~7K LUT存储资源外接 BRAM外接 BRAM 可能有 TLB 项外接 BRAM TLB 项较多最高频率120~180 MHz150~250 MHz150~250 MHz指令集支持RV32IMC 自定义RV32IMC 等按需RV32IMAFC MMU 等典型配置复杂度低单文件中需要 Scala 构建高需要链接脚本和 Bootloader虽然 VexRiscv 的最小配置在面积上并不比 PicoRV32 有绝对优势但它的流水线更深、分支处理更完善所以同频率下性能通常会更好。如果你需要的只是跑起来PicoRV32 是性价比最高的选择如果后续有升级扩展的计划VexRiscv 的纵深会更足。5. 常见问题与排查技巧实录5.1 PicoRV32 的握手时序与复位问题在我带过的项目里新手最容易摔跤的地方是 PicoRV32 的 mem 接口时序。它的握手协议要求外设收到mem_valid后必须在下一拍或者更晚给出mem_ready。有些同学直接用组合逻辑把mem_ready拉高看起来没问题但一旦接入真实 BRAM因为 BRAM 的读延迟数据返回的时序就跟不上。解决方法是所有存储器访问统一走寄存器打拍。如果你要接 AXI 接口的 IP最好写一个简单的 mem-to-AXI 桥接逻辑把握手信号先转换成 AXI 的 AW/AR/W 通道信号。另外复位信号也值得单独说resetn至少要保证足够宽的复位脉冲否则上电瞬间片内 BRAM 初始化没完成取指就会拿到垃圾数据。还有一个很容易被忽略的点PicoRV32 不支持未对齐的访存。如果你的 C 代码里有指针强转或者结构体跨地址边界访问程序就会直接卡死。遇到莫名其妙的跑飞先检查是不是未对齐访问。5.2 VexRiscv 的构建环境与生成代码维护VexRiscv 用 sbt 构建最大的坑在国内网络环境下经常出现——sbt 下载依赖库非常容易卡住。我的经验是用一个已配置好的镜像仓库或者把依赖缓存整包拷贝到本地能省去很多等待时间。另外每次修改 Scala 配置后生成的 Verilog 文件名和模块接口都可能会变所以不要在生成后的 Verilog 上做任何手动修改否则一旦重新生成就全部被覆盖。如果你在 FPGA 工程里同时用了多个版本的 VexRiscv一定要留意resetVector的设置。它决定了处理器复位后从哪里取指如果和你的 Bootloader 的链接地址不一致系统就会跑飞。使用YamlPlugin可以输出当前配置的详细信息包括寄存器地址、CSR 地址映射调试时非常有用。5.3 通用软核调试方法仿真和在线调试不管是哪个核上板前先在仿真里把软件逻辑调通永远是最省时间的做法。对于 PicoRV32可以直接读取 hex 初始化 BRAM 的存储模型在 testbench 里监测串口输出。对于 VexRiscv仓库里有配套的仿真环境可以结合 Verilator 跑 C 程序。上板调试阶段我强烈建议给软核接上调试接口。PicoRV32 虽然没有官方的 JTAG但可以外挂一个调试模块VexRiscv 则可以直接配置 DebugPlugin配合 OpenOCD 和 GDB 进行在线断点调试。这一套组合拳比靠 LED 灯和串口打印来定位问题高效得多。不过装 OpenOCD 的时候要注意版本太老或者太新都可能和 VexRiscv 的调试端口有兼容性问题。结语这两个核其实覆盖了我个人实际项目中绝大部分的软核需求。手头资源紧、只想快速跑一个控制逻辑我会毫不犹豫选 PicoRV32如果是要搭一个正经的 SoC 平台认真研究中断、MMU、跑 Linux那 VexRiscv 的插件式架构和生态会让人越用越顺手。从学习角度说PicoRV32 的源码非常适合通读一遍它代码量小、逻辑清晰能帮你建立 RISC-V 处理器的整体概念。读完 PicoRV32 再去看 VexRiscv你就能理解那些插件到底在做什么为什么一个核可以被拆成这么多模块。这种从简到繁、从固定到可配置的学习路径是我觉得最不容易走弯路的方式。最后再分享一个小经验不管选哪个核都要先花时间把它的自带的 testbench 跑通。这不只是为了验证工具链更是为了熟悉这个核的接口约定和时序习惯。很多问题在写仿真用例的过程中就会暴露出来比等到烧进板子再抓头发强得多。