
最近在给一块入门级FPGA板子做系统控制器选型翻来翻去发现一个有意思的现象以前大家第一个想到的是软核CPU要么是Xilinx家的MicroBlaze要么是Intel家的Nios II都是各自的封闭生态License和工具链都绑得死死的。这几年风向彻底变了社区里新开的Verilog/FPGA开源项目十个里有三四个都选择挂一颗RISC-V内核从几百LUT的微型核到能跑Linux的应用级核都有覆盖了原本需要付费IP才能覆盖的区间。这篇是这个系列的第三十二期按惯例把最近值得关注的开源项目挑出来拆一遍。这期补两个RISC-V方向的新面孔一个是已经在开源社区流传极广、以极致小巧闻名的PicoRV32另一个是用Scala生态里的SpinalHDL描述语言写出来的VexRiscv。两个项目都很能打但设计哲学和适用场景完全是两条路线我们把这俩放在一起看正好能把RISC-V软核在FPGA上到底能干什么、怎么选这件事聊透。如果你正准备在FPGA里塞一颗CPU或者想在软核和硬核之间做个更灵活的选择这篇文章会给你一些可以直接落地的参考。我尽量不绕弯子把技术点、选型逻辑和实操里的坑一次讲清楚。1. RISC-V与FPGA的化学反应为什么开源CPU第一时间都落在FPGA上先聊一个看似题外、其实很核心的问题RISC-V指令集和FPGA之间为什么这么投缘1.1 指令集开放带来的自由度RISC-V最大的特点不在性能而在开放。指令集架构ISA本身基于BSD License开放这意味着任何人——包括个人开发者、初创公司、高校实验室——都可以在不需要授权、不支付费用的情况下设计自己的处理器。对FPGA开发者来说这等于拿到了一个可以自由裁剪的CPU图纸想要支持乘法指令就加上M扩展想要精简面积就砍成RV32E甚至可以往里面塞私有指令来做协处理器加速。对比一下传统方案就清楚了。MicroBlaze和Nios II虽然是成熟的软核但它们的指令集、总线接口、调试方式都绑定在各自厂商的工具链里。你今天写好的MicroBlaze外设逻辑明天换一块Altera的板子基本就废了。RISC-V内核则不然PicoRV32或者VexRiscv生成的Verilog代码可以被任何支持标准综合流程的FPGA工具使用换个厂商也能继续用。这种代码跟着自己走的掌控感是FPGA工程师天然渴望的。1.2 FPGA的并行逻辑与CPU的串行执行互为补充还有一个更深层的原因FPGA擅长并行但怕复杂逻辑CPU擅长跑复杂流程但本质上是串行的这两者天然互补。举一个我实际做过的例子一个数据采集系统前端需要同时处理多个通道的高速采样、做数字滤波、触发判决这部分用FPGA的并行流水线实现非常顺手但系统上电后的配置流程、参数解析、通信协议状态机用纯Verilog硬写又会变得非常繁琐。这时候在FPGA里放一个RISC-V软核把慢速控制逻辑交给CPU把高速并行数据通路留在RTL整个系统的可维护性会高出一个数量级。这也是为什么现在的FPGA开源项目里带RISC-V的SoC越来越多。软核CPU不是一个替代逻辑的资源消耗品而是一个让FPGA从硬件加速器变成完整系统的关键粘合剂。1.3 免费工具链与生态的滚雪球效应相比那些需要专门IDE、特殊License的商用软核RISC-V的另一大杀手锏是工具链完全免费且成熟。GCC、Binutils、Newlib、OpenOCD这些基础工具对RISC-V的支持已经非常完善你在PC上交叉编译一个C程序生成二进制文件然后通过一串简单的脚本转换成FPGA可用的初始化文件整个过程行云流水。没有License服务器的限制没有厂商云平台的绑定这让大学生社团、个人极客也能轻松玩转社区贡献者自然越来越多。2. PicoRV32极致小巧的RISC-V内核一个下午就能跑通先说第一个新成员。PicoRV32是知名开源EDA开发者Clifford Wolf的作品这位老哥是Yosys逻辑综合工具、nextpnr布线工具的作者他出品的CPU天然带着浓厚的工具链极客气息。2.1 项目血统与设计哲学PicoRV32用纯Verilog-2001编写单文件核心代码量只有几千行。设计目标非常明确尽可能小的面积同时保持完整的可编程能力。它支持RV32I基础整数指令集并可选的MC扩展乘除法、压缩指令还自带一个可配置的定时器和一个用自定义方式实现的中断控制器。在我测过的实际参数里PicoRV32在Artix-7系列FPGA上大约只需要750~800个LUT和几百个触发器加上BRAM跑一个完整的MCU小系统总资源占用经常不到FPGA总容量的5%。这意味着一颗原本快要被逻辑塞满的FPGA仍然可以轻松塞一个软核进去做配置管理而不用为CPU付出太多面积代价。2.2 用PicoRV32搭建最小系统的核心逻辑要把PicoRV32当作一个MCU来用最关键的是理解它的内存接口。它本质上是一个典型的Harvard结构精简核心通过一组简单的内存读写信号与外部逻辑交互。在标准接线方式里核心通过mem_valid和mem_ready握手信号访问内存地址空间你可以用Verilog写一个简单的地址译码器把程序存储器通常用BRAM实现、数据存储器和外设寄存器映射到不同地址段。我习惯的做法是例化PicoRV32核连接时钟和复位信号。写一个处理模块监听mem_valid信号对mem_addr进行译码。低地址段映射到BRAM作为程序区高地址段映射到外设寄存器比如UART、GPIO、LED。预先把编译好的固件转换成.hex或.mem文件在初始化BRAM时加载进去。这套流程里最需要注意的是地址映射的一致性。PicoRV32默认的复位入口地址是0x00000000所以BRAM里的程序起始地址也必须放在这个位置否则上电后CPU取到的第一条指令就是乱的。这个问题我遇到不下三次每次都是编译器生成的起始地址和链接脚本没对齐。2.3 编译链路的快速上手工具链部分并不复杂。这里以RISC-V GNU工具链为例安装好riscv-none-elf-gcc后编译一段简单的C程序只需要几步riscv-none-elf-gcc -marchrv32imc -mabiilp32 -Os -nostdlib -T link.ld -o firmware.elf firmware.c riscv-none-elf-objcopy -O binary firmware.elf firmware.bin然后用一个小脚本把二进制文件转成Verilog的$readmemh需要的十六进制文本格式。这里的-marchrv32imc一定要跟PicoRV32例化时的配置对应上。如果你例化时开了ENABLE_COMPRESSED但没有开乘法扩展那编译参数就应该写rv32ic而不是rv32imc否则程序一跑到乘法指令就会触发非法指令异常。2.4 PicoRV32的局限PicoRV32虽然好用但不是万能的。它没有MMU也没有完整的特权级实现跑不了Linux这类的重量级操作系统中断控制器是自成一派的自定义实现跟官方标准定义的CLINT/PLIC并不完全一致。所以如果你打算用标准RISC-V生态里的RTOS或者裸机BSP需要自己适配一些软件层的东西。用一句话概括PicoRV32是优秀的轻量级协控核心不是应用处理器。3. VexRiscv用Scala写CPU配置化流水线的另一种思路如果说PicoRV32代表的是极简主义那VexRiscv代表的则是高度可配置的工程化路线。这个项目的口碑在FPGA社区相当好它的特别之处在于核心不是用Verilog写的而是用SpinalHDL写出来的。3.1 SpinalHDL与生成Verilog的颠覆感先解释一下SpinalHDL是什么。它是一种基于Scala语言、面向硬件描述的开源框架。看到用Scala写CPU很多人的第一反应是这一套能靠谱吗。我的实际体验是这种思路解决了一个传统Verilog长期存在的痛点参数化和代码复用太难。在Verilog里想做一个可选支持乘法器、可选分支预测、可选MMU的CPU通常要靠大量generate和宏定义代码写到最后往往比主线逻辑还复杂。但在SpinalHDL里这些能力被封装成库函数和组件你可以用写软件的方式组合出硬件。VexRiscv把CPU的功能单元拆成了一个个插件Plugin分支预测是一个插件调试模块是一个插件乘除法器是一个插件连指令译码中的各个步骤也都是插件。要什么就加什么插件不需要就去掉生成的代码完全契合配置。3.2 VexRiscv的能力覆盖范围VexRiscv的能力弹性非常大。在最精简配置下它可以只占用不到1000个LUT性能比PicoRV32还要高一些在高配版本下它可以加入五级流水线、动态分支预测、指令和数据缓存、MMU、硬件调试模块性能足以跑Linux。这一点很关键同一个CPU核心通过配置插件的增减覆盖了从微控制器到应用处理器的整个光谱这在传统的Verilog软核项目里几乎见不到。很多FPGA项目直接用VexRiscv替代MicroBlaze通过标准AXI4接口连接DDR控制器、Ethernet MAC、UART和GPIO形成的SoC与外设生态非常整洁。3.3 典型用法把VexRiscv当IP生成器对于大部分FPGA工程师来说使用VexRiscv不一定要深入学习Scala语言。把它当做一个IP生成工具就够了在配置文件中设置好你想要的功能窗口然后运行构建命令它会生成一个纯Verilog文件以及对应的仿真测试环境。这个Verilog文件可以像任何其他RTL模块一样被例化到工程里。一个让我印象深刻的细节是VexRiscv生成的Verilog并不是那种一眼看到底的玩具级代码而是结构清晰、带时钟门控和流水线寄存器的工程级代码。这就给了团队里做时序收敛的人一个友好的界面去分析关键路径。3.4 使用VexRiscv时最容易踩的坑VexRiscv最大的门槛其实是构建环境。它需要Java运行环境、sbt构建工具并且第一次构建时要从仓库拉取大量依赖在国内网络环境下很容易翻车。这里的建议是提前配好sbt镜像和Maven镜像不要裸着跑sbt。构建时不要一上来就搞完整高配先跑默认配置确认环境没有问题再改参数。生成的Verilog文件最好固定版本管理不要每次都在板上临时从源代码重新生成。版本不一致会导致BSP里的设备树或链接脚本对不上排查起来非常痛苦。另外一个坑是总线位宽和外设地址对齐。VexRiscv默认的总线接口是AXI4-Lite或者Avalon-MM类数据总线宽度可能与AXI总线的地址对齐方式不完全相同直接使用网上示例代码时要注意指令总线和数据总线的跨时钟域处理。4. 大象与猫鼬PicoRV32和VexRiscv的关键参数横评前面分别说了两个项目的特点这里放到一张表里硬碰硬地对比一下方便按需选型。对比维度PicoRV32VexRiscv硬件描述语言Verilog-2001SpinalHDL构建后生成Verilog最小规模约750 LUT约900~1200 LUT精简流水线配置典型最高性能约0.45 DMIPS/MHz最高约1.0~1.2 DMIPS/MHz五级流水线配置指令集支持RV32I/E/M/CRV32I/M/A/C可选F扩展特权级与MMU无MMU简单中断可选配置高配支持MMU可跑Linux调试支持自定义中断无标准调试模块可选JTAG调试模块支持OpenOCD外设接口自定义简易内存接口AXI4/AXI4-Lite/Avalon等标准接口集成复杂度极低适合快速上手中高需要花时间理解配置与总线适用场景简单控制、教学、极小面积需求较高性能SoC、跑RTOS/Linux、复杂外围继承社区热度很高文档和示例多高BSP和Linux支持更完整从这张表能看出两个项目并不是谁取代谁的关系而是不同的工具做不同的事。如果你的需求只是在一片资源极度紧张的FPGA里放一颗能做状态判断、能跑控制算法的小CPUPicoRV32完完全全够用而且它极低的LUT占用让你几乎感觉不到它的存在。如果你要做的是一个正经的SoC用户可能会跑RTOS甚至Linux外设要接DDR控制器和网卡还希望有一个能下断点调试代码的JTAG口那VexRiscv的高配路线明显更合适。选型时我还有一个很实际的建议先确认团队里谁负责后续维护。如果维护者主要写Verilog对Scala比较陌生选PicoRV32会更顺畅因为所有代码都是标准的Verilog任何一个FPGA工程师都能看懂如果团队熟悉嵌入式软件和Linux愿意花一周时间熟悉SpinalHDL的构建流程那VexRiscv后续带来的收益会更大。5. 在FPGA上跑通RISC-V的完整链路从编译到上板的避坑清单最后把实际操作中的几条链路和常见的坑梳理一遍。这些经验来自我自己的折腾过程希望能帮你省掉几顿晚饭的时间。5.1 工具链安装与版本匹配无论是PicoRV32还是VexRiscv第一步都是准备好RISC-V GCC交叉编译器。最稳妥的方式是直接从官方工具链仓库拉取预编译版本注意三点目标三元组要和你的CPU位数一致。PicoRV32通常是32位VexRiscv的Linux配置通常也是32位起步。多版本GCC并存时一定要检查PATH里的默认版本。我见过有人编译出来的固件在板上运行时随机崩溃最后发现是用了64位工具链却选择了32位软浮点ABI链接出的代码指令集行为不一致。交叉编译器路径建议用绝对路径写进Makefile避免不同终端环境变量差异导致同类问题。5.2 启动地址、链接脚本与BRAM初始化启动地址是第一个真正的分水岭。PicoRV32默认从0x00000000启动VexRiscv的启动地址取决于配置参数。如果你在配置时改了复位地址链接脚本里的.text段起始地址必须同步修改否则CPU上电取指就会跑飞。BRAM初始化也是一个容易出现看起来对了其实数据错位的环节。用$readmemh加载十六进制文件时Verilog读取的地址从0开始而BRAM实际深度可能远大于固件大小。如果BRAM输出寄存器在复位释放时还没有完成初始化CPU第一次取指就会读到全X态。我的处理习惯是在顶层模块里设计一个延迟复位的逻辑——先让BRAM初始化稳定至少延迟几十个时钟周期后再释放CPU的复位信号。这个小技巧成本极低但能消除一大部分上电后状态不可知的问题。5.3 验证程序不要一上来就写复杂功能第一次上板不要直接跑你精心写的UART收发测试或者LCD驱动先写一个最简单的循环点亮LED程序确认CPU执行流是通的volatile unsigned int *led_reg (volatile unsigned int *)0x40000000; volatile int counter 0; while (1) { *led_reg (counter 8) 0xFF; }这个程序只涉及最基础的内存写操作没有中断、没有外设复杂时序。如果LED能够均匀闪烁说明取指、跳转、写数据通路都正常。接下来再逐步加入UART、定时器中断、DMA等外设每加一个功能就在板上验证一次不要一次性把整个软件栈都烧进去再调试。5.4 中断与异常软件工程师最容易忽略的上下文开销RISC-V的中断处理有一个鲜明的特点不像ARM有硬件自动压栈一堆寄存器RISC-V的通用寄存器到底保存哪些很大程度取决于软件。在使用PicoRV32或者VexRiscv提供的BSP时会看到一段汇编代码在进入中断服务例程时先把各种寄存器压栈、退出时再恢复。如果你在写C语言Handler时开启了优化但上下文保存部分的汇编没有正确保存某些寄存器就会在中断返回之后出现变量突然被清零这类诡异问题。这里最实用的排查手段是先关闭编译优化确认基本逻辑正确再逐步提高优化级别每次提高后跑一轮压力测试。不要把优化级的bug和中断的bug混在一起排查难度会成倍上升。5.5 用片上逻辑分析仪解决程序根本没跑的玄学问题上板后如果LED不亮、UART没有任何输出大部分情况不是代码逻辑错了而是信号在物理层就没有达到预期。这类问题的排查建议直接使用FPGA工具自带的集成逻辑分析仪比如Xilinx ILA或者Intel SignalTap在上板前先用综合工具把关键信号引出来观察。需要观察的信号优先级依次是复位释放信号、CPU时钟、握手信号mem_valid、BRAM使能信号。只要这些信号是正常跳变的CPU大概率在运行如果mem_valid一直拉不高要么复位一直被拉低要么CPU本来就停在某个等待状态。千万不要一上来就去怀疑GCC生成的汇编代码有问题在FPGA上绝大部分黑屏问题出在硬件连接和复位释放上。5.6 性能与面积之间的调优体会最后分享一个关于调优的实际体会。PicoRV32虽然面积小但它属于单发射顺序执行设计分支跳转的代价较大在控制密集的任务里性能会比同频率的商用软核差一些。VexRiscv通过配置选择不同的流水线级数可以对性能和面积做更精细的权衡但这个权衡本身需要你对运行时的负载有清楚的估计。有一回我用VexRiscv跑一个轻量级RTOS加TCP/IP协议栈开始用了五级流水线高配性能很宽裕但资源占用接近7000 LUT。后来把配置降到三级流水线、关掉部分缓存优化功能保持不变LUT占用降了接近四成网络吞吐测试只下降了百分之十几。这件事让我彻底认同VexRiscv按需配置的设计哲学——默认做法永远是先选一套合理配置跑起来再根据实测数据一点一点剪裁这比一开始就纠结最优参数要高效得多。最后一个实用小技巧如果你打算长期使用RISC-V软核做产品原型建议从一开始就把固件编译、内存初始化文件生成、FPGA比特流打包做成一条自动化脚本。我早期手工敲命令经常因为忘记重新生成内存文件导致板上运行的还是旧固件浪费了大量时间排查。后来写了一个简单的Makefile每次改完C代码执行一条命令就能跑完编译、格式转换并把新的初始化文件拷到FPGA工程的指定目录。这套流程省下来的时间在项目后期每天都能派上大用场。嵌入式开发里真正值钱的往往不是某个惊艳的代码片段而是那套让你能快速迭代、快速复现问题的工程习惯。