news 2026/9/9 16:27:10

手写RISC-V处理器核:AXI接口与五级流水线设计详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手写RISC-V处理器核:AXI接口与五级流水线设计详解

简介:包含完整RTL源码的RISC-V与AXI4总线接口工程,源自SiFive U54MC多核处理器设计。它面向希望深入处理器微架构和片上系统互连的硬件开发者、FPGA工程师以及计算机体系结构方向学生,尤其适合作为从零搭建处理器数据通路与访存接口的设计参考。压缩包共1463个文件,约4.69MB,其中核心硬件代码包括414个SystemVerilog文件和399个Verilog文件,另配备C/C++测试用例、Makefile与configure构建脚本、设备树配置、许可证说明等辅助材料,便于在Linux环境中直接编译、仿真与调试。已有1017人学习此代码包。通过研读这份代码,可系统性理解RISC-V基础指令与I、M、A、C、F/D各扩展,学习AXI4协议的读写事务、突发模式、ID管理及响应机制;在RTL设计层面,能掌握Verilog模块划分、组合与时序逻辑描述、多核缓存一致性、中断控制和Testbench验证等工程化方法;同时还涵盖地址解码、命令生成、数据宽度与地址宽度设置等总线接口细节,对深入掌握处理器与总线互连、提升硬件调试能力具有直接参考价值,并有助于梳理SoC设计中的常见问题与调试思路。 整理这套RISC-V核的源码时,我给自己定了一个规矩:所有对外接口只保留AXI,内部一条五级流水线,底层全部用Verilog手写。之所以定这个规矩,是因为市面上标着“RISC-V CPU Verilog源码”的开源项目我几乎翻过一遍,要么是教学性质的迷你核,只支持二三十条指令,走的是自定义简单总线;要么是完整度很高的工业级core,模块复杂到不适合拿来当入门教材。我想要的是一份中间形态的代码:指令集实现够完整、AXI协议握手规范、代码量又能让人在一个月内读完。

这份代码主要面向三类人:正在做毕业设计或课程项目的学生,准备数字IC/FPGA方向面试的求职者,以及想在自己的FPGA工程里快速塞一个软核做SoC原型验证的工程师。如果你能看懂简单的Verilog模块,知道什么是时钟、复位、状态机,那么跟着后面几节内容把这个工程跑起来不会有太大障碍。

1. 三个关键词背后的分工:指令集、总线协议与硬件描述语言

1.1 一套RISC-V Core在SoC里到底扮演什么角色

一个SoC内部通常有CPU核、内存控制器、外设控制器、中断控制器,它们通过总线互联。RISC-V核负责取指、译码、执行指令,是控制中枢;AXI作为主流的总线协议,负责把CPU对存储和外设的读写请求高效地传出去;Verilog则把这些行为描述成可综合的硬件逻辑。这三者缺一不可:没有AXI,核只能孤立地跑内部寄存器;没有RISC-V,AXI上的事务缺少指令语义;没有Verilog风格的RTL描述,前面两者都只是文档和概念。

在这套源代码里,核与外部世界只有两类交互:一类是面向指令的取指请求,一类是面向数据读写的Load/Store请求,全部通过AXI主接口发出。这种设计最大的好处是:CPU核本身不关心外部接的是Block RAM、DDR控制器还是自定义外设,只要对方也是AXI从设备,就能直接对接。很多初学者会把CPU核和SoC混为一谈,实际上核只是SoC里的一个主设备,地址译码、仲裁、外设接入都属于核之外的System Bus部分。把这条边界划清楚,后面读代码会顺畅很多。

1.2 手写Verilog和Chisel生成RTL的差异,我为什么选前者

经常有人问我,Chisel生成RTL这么流行,Rocket和BOOM都是这么来的,为什么还要手写Verilog?我的看法是,两者解决的痛点完全不同。Chisel用Scala描述硬件,生成出的Verilog代码量通常很大,可读性差,跑仿真可以,做后端综合时一旦出现时序违例,定位逻辑路径要翻几十万行生成代码,非常痛苦。对中小规模的CPU核来说,手写Verilog能把每一级寄存器的边界、每一根握手信号的控制都把握在自己手里,综合和P&R阶段的优化空间也更大。

Chisel的优势在于大规模复杂项目的参数化生成,通过重构和高度抽象能减少重复编码,但代价是调试时需要同时理解Scala层和生成后的Verilog层。如果你是第一次做RISC-V CPU,我强烈建议先用Verilog把流水线和AXI协议吃透。Verilog是IEEE标准,所有EDA工具的支持都最成熟,任何仿真器、综合工具、第三方IP都能无缝对接,这个兼容性优势在团队协作和流片项目中尤其重要。

2. RTL整体框架设计:五级流水线如何长出一对AXI主接口

2.1 模块划分与流水线各级的职责边界

这套CPU采用经典五级结构,模块名基本都是常规命名:ifu(指令取指)、idu(指令译码)、exu(执行)、lsu(访存)、wbu(写回)。设计时有一个原则:每一级之间只通过valid/ready风格的握手信号交互,不允许跨级引用内部信号。很多初学者写CPU到最后代码调不通,十有八九是流水线寄存器之间又拉了一根绕来绕去的信号,看起来解决了眼前问题,实际上把整个流水线的组合逻辑时序完全搞乱。

各路模块的职责划分如下。IFU维护PC和指令侧AXI请求,送出的指令放进入口寄存器;IDU负责解码、立即数扩展、寄存器堆读口和冲突检测;EXU完成算术逻辑运算和分支判断;LSU执行Load/Store,把地址和写数据打包成数据侧AXI事务;WB把来自LSU的读回数据或ALU结果写回寄存器堆。寄存器堆是标准的双读一写端口,没有做任何旁路,数据前递在EXU和LSU之间显式处理,这样代码的逻辑更清晰,也方便你逐级理解数据的生命周期。

2.2 指令侧AXI和数据侧AXI为什么必须分开

指令存取和数据存取在流水线里是两张完全不同的访问模式:指令是顺序连续读,每次读4字节;数据则可能任意地址读单个字或者连续写。把两者合并到一个总线上虽然省端口,但会造成严重的总线竞争——取指miss时整个流水线空闲,Load miss时又取不了指令。这套代码里采用了哈佛结构,IFU引出独立的读通道AXI主接口,LSU引出读写都支持的数据侧AXI主接口。即便外部最终用总线矩阵把两边接向同一个内存控制器,接口层保持独立也能保证后续扩展ICache和DCache时不用改动流水线逻辑。

这里有个容易被忽略的细节:指令侧如果只用了AR通道,从协议上看没有任何问题,但不代表可以直接无视AW。以后如果要做调试模块,比如通过外部总线改写指令内存,指令侧没有写通道就会卡死。所以代码里即便指令侧只需要读,我也把写通道的握手信号留了接口并置为不激活状态,这是AXI协议使用中比较老到的一点。

2.3 地址映射与仲裁器:CPU之外的第一段代码

AXI主接口解决的是点对点传输。当外部接了多个从设备,比如一块RAM、一个串口、一组GPIO,就需要地址译码和仲裁。地址译码把CPU发出的地址区间映射到对应从设备,仲裁器在多个主设备请求同一个从设备时决定访问顺序。最常见的仲裁方式是轮询仲裁,也就是多个主设备轮流获得总线使用权。

写仲裁器最容易出问题的地方是:不能只看当前一拍有没有请求,还要考虑已经发送且尚未返回的事务不能被新一轮仲裁打断,否则响应会错乱。简单做法是在仲裁状态机里增加一个“总线忙”条件,只有所有未完成事务结束后才允许切换主设备。这样确实牺牲掉一些并发度,但换来的是仲裁模块可以做到无脑可靠。对于初学者来说,先把“完全串行化”的仲裁跑通,再考虑乱序和并发,是我强烈建议的顺序。

3. AXI接口RTL实现中,那些仿真测不出来的隐藏约束

3.1 AXI-Lite与AXI-Full的选择,直接影响代码复杂度

AXI协议有AXI-Lite和AXI-Full两个子集。AXI-Lite是简化版,单拍读写、所有burst长度固定为1、不要求ID,适合做寄存器配置类总线;AXI-Full支持burst、outstanding、乱序返回,适合做内存访问。CPU的数据侧如果用AXI-Lite,意味着每次访存最多传一笔,访问DDR时效率很低,做Cache miss回填更是灾难。

特性AXI-LiteAXI-Full
burst支持不支持,单拍传输支持,可配置burst长度
数据宽度固定可配置
每事务ID不需要需要,用于乱序返回标识
outstanding支持不支持支持多个未完成事务
适用场景配置寄存器、控制接口内存读写、高速数据通路

所以这套代码里数据侧用了AXI-Full,指令侧也实现了AR通道的固定长度burst读取。我见过很多项目图省事,把所有接口都做成AXI-Lite,跑仿真完全正常,一放到有真实DRAM的系统里,读一行数据要发几十个单拍事务,性能和协议规范都不达标。选择协议宽度时标准很简单:只要预期会出现连续地址的批量传输,就直接用AXI-Full,不要留到后面再改。

3.2 五通道握手:READY和VALID的依赖关系与死锁

AXI协议有五个通道:读地址AR、读数据R、写地址AW、写数据W、写响应B,每个通道都是VALID/READY双信号握手。最容易掉进去的坑是握手信号之间的依赖关系。协议规范明确说VALID不能依赖READY,意思是发送方在发起事务时只要自己的状态允许就必须拉高VALID,等待READY返回;READY则可以等待VALID。典型的死锁写法是:发送方为了减少无效传输,让VALID等到READY为高才拉高,而接收方又设计成没有VALID就不拉READY,两方互相等,整个总线卡死。

// 反例:awvalid等待awready,awready同时等待awvalid,直接死锁 always @(posedge clk) begin if (issue_req && awready) awvalid <= 1'b1; end

正确的套路是:主设备发出的VALID尽量由状态机控制,一旦状态机进入发送态即拉高,不需要等READY;从设备侧的READY可以在空闲时始终拉高,或者用“非满且信道可接收”的逻辑生成。对自己的核而言,CPU是主设备,控制权在自己手里,但从设备模型的配合方式也要在testbench里覆盖到“两者同时有效”和“先READY后VALID”的情况。更关键的是,READY建议寄存一拍输出,时序更好,但代价是增加一个周期的握手延迟,对CPU来说这个延迟会直接叠在访存延迟里,设计时需要接受它,而不是用组合逻辑去穿越。

3.3 outstanding事务与ID管理:简单可靠的策略

AXI-Full允许主设备连续发送多个outstanding事务,期间不需要等前一个返回,用ID来标识这些事务的返回顺序。对CPU核来说,支持多大深度的outstanding直接决定了总线和缓存模型的复杂度。最原始的思路是“一次只允许一个未完成事务”,也就是发出AR后,必须等R通道收到最后一拍数据才能发下一个AR。这个策略在协议上完全合法,实现简单,代价是访存延迟完全串行化。如果处理器是单发射、顺序提交的,多数场景下这个限制完全可以接受,这也是这套代码里数据侧实际的实现方式。

如果要做ICache预取或非阻塞Cache,就需要支持多个outstanding事务。此时ID字段不能全部填0,而要用一个小计数器区分请求。AXI允许乱序返回,也可以通过ID把乱序恢复成顺序:最简单的方式是只支持顺序返回,在从设备端把多路ID响应攒齐后按ID顺序提交。这套代码里预留了ID宽度方便扩展,但默认实现不做乱序处理。提这个是想说:不要一上来就追求更深的outstanding特性,先把单事务、顺序返回这种基础路径验证扎实,再谈性能优化。

3.4 写响应BRESP与读响应RRESP:别把错误直接吞掉

读数据通道的RRESP和写响应通道的BRESP里都有一个2位的resp字段,0表示OKAY,2表示SLVERR,3表示DECERR。很多CPU设计demo直接把resp丢到测试端就算完事,但一个正经核必须处理响应错误:当访问一个不存在的外设地址或从设备报错时,最稳妥的做法是把这次访存标记为异常,触发类似Load Access Fault的异常入口。实现起来不复杂,在LSU里增加一个“访存完成且resp非OKAY”的条件,把这个脉冲和访存完成的valid一起送进异常处理逻辑,同时禁止写回阶段把脏数据写入寄存器。

最容易漏的是写响应通道的时机。写请求在AW和W通道发出后,要等B通道返回才能释放该事务的跟踪条目;如果B通道迟迟不来,后续写请求就没有资源可用。这时候需要设置一个超时计数器,超时后按SLVERR处理并记录错误。虽然协议上从设备不应该无限期不返回,但作为主设备留一手总比死等强。

4. 指令集实现的取舍清单:从RV32I到CSR与异常入口

4.1 RV32I基础指令的完成标准

判断一套RISC-V核有没有真正实现RV32I,不是数指令条数,而是看整数通用寄存器、控制状态寄存器、异常入口和内存访问是否完整。RV32I包含约40条指令,分算术逻辑、Load/Store、分支跳转、CSR访问和内存屏障/环境调用。实际代码里常见的漏项有:fence.i在单核CPU里可以空转实现,但不能缺了指令解码;csrrw/csrrs/csrrc的写后读行为和三操作数关系要仔细看手册;SHIFT指令的立即数只使用最低5位;load指令的符号扩展和无符号扩展要区分清楚。这些细节在仿真回归中都能抓到,但在写代码阶段就要形成自查习惯,否则翻到后面改起来成本很高。

4.2 M扩展、CSR与异常入口的取舍

如果只是在自己设计的SoC里做控制用途,RV32I已经够用,但要做RISC-V指令集相关的求职项目或想跑更多软件,M扩展建议尽早加。实现乘法除法时需要在面积和延迟之间做取舍:组合乘法器一拍出结果,但布线压力大,时钟频率提不上去;迭代乘法器用状态机分多拍算完,面积小但流水线要支持阻塞周期。这套代码采用更稳妥的做法:默认RV32I,乘除法作为可选宏开启,用迭代方案,代价是乘除法期间暂停取指,用性能换代码简洁。

CSR这一块是很多人做CPU时最后才补又最容易出错的部分。至少要实现机器模式下这几个寄存器:mtvec(异常入口地址)、mepc(异常返回地址)、mcause(异常原因)、mstatus(全局中断使能位)。这套代码里有一个独立的CSR模块,译码阶段识别CSR地址,执行阶段根据指令类型做对应读写,异常发生时由控制逻辑自动写入mepc和mcause并跳转mtvec。这里有个非常实际的注意点:异常入口跳转会推翻原本的PC更新逻辑,所以这套代码里把异常请求放到IDU/EXU边界统一判断,避免分支和异常同时发生时入口被覆盖。

4.3 那些“看起来对但一综合就出latch”的写法

RTL里最经典的隐患是产生latch。常见写法是:在always块里写了多个if分支,但没有写else;或者case里漏了default;又或者某个信号同时被两个always块赋值。仿真是测不出latch的,因为功能上它往往“恰好能工作”,直到综合报告里出现“inferred latch”或者上板后状态随机跳变才暴露。在这套CPU里,最容易出现latch的地方是译码器和总线状态机。译码器里每个输出在当前指令没有意义时也要赋默认值;状态机里状态编码不完整时要补充复位态和default态。经验法则是:看一个组合逻辑always的输出是否在每一个执行路径上都覆盖了赋值,没有覆盖的地方不是保持就是latch。

5. 让源码可复现的验证链路:testbench、riscv-tests与波形排障

5.1 testbench最简结构:从hex加载到指令跑完

没有任何验证环境的源代码是不完整的。这套代码的testbench思路很直接:例化CPU核作为主设备,接一个用Verilog行为模型实现的AXI从设备,模拟一块足够大的内存;仿真开始后读取外部编译好的hex文件,按节写入内存模型,复位释放后CPU从0地址开始取指。为了确认程序跑完,在指令内存里放一个特定指令序列,testbench一旦抓到死循环跳转指令,就打印寄存器堆快照并结束仿真。

initial begin $readmemh("../hex/riscv_test.hex", mem); reset_cpu(); wait(cpu_done); dump_regfile(); $finish; end

写testbench时一直强调:不要用initial块里的force把信号钉死,要让CPU通过真实的AXI事务发起访存,这样验证的才是协议层面正确性,而不是功能点层面的凑巧。

5.2 用riscv-tests做指令集回归

能跑hello world只说明程序装载和基本通路没大问题,要证明指令集实现正确,最直接的方法是回归riscv-tests里的指令级测试。流程是:用交叉编译工具链生成RV32I的测试用例,把测试的hex加载到仿真内存,逐个运行,比对输出或者由测试程序自检。第一次跑回归时一定会有不少失败项,不要慌,先把失败用例里具体是哪种指令分离出来,多数问题集中在符号扩展、分支偏移计算或load-store的对齐处理上。

这里有个非常实用的技巧:在仿真波形里把“取指地址、译码指令、写回数据”三组信号单独拉一组总线,手动跟踪一条失败指令从取指到写回的完整生命周期,通常几十拍就能定位到是哪个阶段算错了。如果手头有Verilator,配合--trace选项生成vcd波形,再在GTKWave里按信号名过滤,定位速度会快很多。

5.3 一次典型的访存时序排障记录

举个实际的排障例子,也是很多初学者会遇到的。原版代码里LSU发出AR请求后,为了偷一点性能把AW请求同时发出,结果testbench里因为数据侧和指令侧接的是同一个内存模型,两个通道的transaction在内存模型里交错执行,导致store操作完成后紧跟着的load读到了旧值。排查链路是这样的:先看指令序列和访存地址,确认前后两条指令确实访问同一地址;再检查内存模型的读写优先级,发现模型的写通道和读通道分属独立逻辑,没有建立“先处理完写再处理读”的顺序;最后定位到LSU缺少一个资源分配约束。

修复不复杂,在LSU里把写事务和紧跟的读事务用FIFO串行化,保证同地址的写后读不被乱序。这个问题纯看功能波形不一定看得出来,只有把通道级transaction和指令级时序放一起对照才能暴露。没有回归测试环境,靠眼睛盯波形能盯到怀疑人生。

6. 拿到这套代码后怎么落地:目录结构、工具链与下一步改造

6.1 源码目录与命名:方便自己也方便别人

代码交付时,目录结构是否清晰直接决定别人愿不愿意继续用。这套源码按下面的结构组织,几乎不加改动就能跑仿真和综合:

rtl/ ifu.v idu.v exu.v lsu.v wbu.v csr.v regfile.v rv32_axi_top.v sim/ tb_top.v mem_model.sv run_verilator.sh run_vivado_sim.tcl hex/

模块命名尽量用功能缩写,每个文件顶部写清楚端口来源和目标,这是开源硬件项目的基本规矩。还有一个细节:AXI信号的命名不要贪图省事只写aw/p等缩写,端口名里明确带出通道和工作方向,比如s_axi_awvalid。这种命名看起来啰嗦,但在后续AHB转AXI、AXI转PCIe这类桥接场景里能少翻无数遍手册。

6.2 仿真工具链:Verilator还是ModelSim

仿真环境我提供两套入口:一套基于Verilator加GTKWave,适合命令行跑大量回归;一套是Vivado自带仿真器的TCL脚本,适合点开图形界面看波形,ModelSim用户把TCL脚本稍作修改也能用。Verilator跑回归的速度比事件驱动仿真器快一到两个数量级,所以riscv-tests的全量回归我建议放在Verilator里执行。不过Verilator对SystemVerilog的支持在部分语法上有差异,测试平台的写法要尽量保守,避免使用复杂OOP特性,否则排语法错误的时间比排设计问题还长。这也是把testbench命名为mem_model.sv但内部只用简单过程块的原因之一。

6.3 后续改造方向:Cache、AXI-Stream桥与中断控制器

拿到源码后最常见的需求是继续往下扩展。几个方向按推荐顺序排:第一是实现一个简单的ICache,用指令侧AXI的burst能力一次回填多拍,这个改造能最直观地提升性能;第二是做一个AXI-Full到AXI-Stream的桥接模块,用来对接高速数据通路或DMA;第三是加入中断控制器,把定时器和外部中断接入CSR里的mstatus/mie/mip,让核从裸机代码进化成能跑更复杂软件的状态。四个方向里,Cache和总线桥的Verilog实现细节我后续会分别专门写文章记录,这里先不展开。有一点要再提醒:每扩展一个特性,就回去跑一遍riscv-tests和手写的访存回归用例,保住已有的正确性。

最后分享一个我在整理这套AXI接口时反复吃亏得出的经验:设计RISC-V AXI核时,先把AXI的ID位宽、数据位宽和每通道的outstanding深度定义成宏或参数,放在一个专门的配置头文件里,后边所有模块都引用这几个参数,而不是散落在各文件里。我早期版本把ID位宽写死在几个模块里,后来要加Cache不得不一次改十几个文件,那种痛苦经历过一次就再也不想经历。如果你正在规划自己的RISC-V核,多花半天定义好全局参数,后面省下的时间远超这个数。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 16:26:14

LangChain.js 智能代理入门:从一次安装到跑通第一个 Agent 的路径

LangChain.js 智能代理入门&#xff1a;从一次安装到跑通第一个 Agent 的路径 【免费下载链接】langchainjs The agent engineering platform 项目地址: https://gitcode.com/GitHub_Trending/la/langchainjs 做一个能回答问题的 AI 应用&#xff0c;往往不是接一个大模…

作者头像 李华
网站建设 2026/9/9 16:25:48

自建ntfy推送服务:从部署到实战的完整指南

简介&#xff1a;这是一款基于 Go 语言开发的轻量级推送通知工具 ntfy&#xff0c;面向需要向手机或桌面实时推送消息的开发者和运维人员。它通过简洁的 PUT/POST 请求即可触发通知&#xff0c;适用于服务报警、脚本执行提醒、定时任务状态推送、IoT 事件通知等场景&#xff0c…

作者头像 李华
网站建设 2026/9/9 16:25:42

Overleaf上手指南:免费学术排版的零门槛实战

Overleaf上手指南&#xff1a;免费学术排版的零门槛实战 【免费下载链接】overleaf A web-based collaborative LaTeX editor 项目地址: https://gitcode.com/GitHub_Trending/ov/overleaf 论文截稿前夜&#xff0c;本地LaTeX编译报错&#xff0c;你盯着终端一行行找依赖…

作者头像 李华
网站建设 2026/9/9 16:23:38

ISAC多域优化:电磁成形与网络协作的交替迭代框架

ISAC&#xff08;Integrated Sensing And Communication&#xff0c;通信感知一体化&#xff09;最近在无线通信圈子里热度一直没下来过&#xff0c;尤其到了5G-A和6G预研阶段&#xff0c;几乎每个做物理层和网络层的团队都在往这个方向靠。说白了&#xff0c;ISAC的核心诉求就…

作者头像 李华
网站建设 2026/9/9 16:21:05

用Open Generative AI三步搭建自己的免费AI图片视频工作室

用Open Generative AI三步搭建自己的免费AI图片视频工作室 【免费下载链接】Open-Generative-AI Unrestricted Open-source alternative to AI video platforms — Free AI image & video generation studio with 600 models (Flux, Midjourney, Kling, Sora, Veo). No con…

作者头像 李华