资讯动态

RISC-V AXI Verilog源码实战:从仿真到SoC集成

发布时间:2026/10/19 16:02:50 来源:尧图企业网站定制
简介本资源为基于Verilog的RISC-V处理器与AXI总线RTL源代码包面向数字IC设计、FPGA开发及嵌入式系统学习者尤其适合希望深入理解处理器微架构与片上总线接口的中高级工程师。包内以SiFive U54MC多核RISC-V处理器IP的AXI4接口实现为核心涵盖RISC-V基础指令集与扩展、AXI4读写事务与突发传输机制、Verilog模块化建模、RTL综合与仿真验证等关键知识点。资源共1463个文件以414个SystemVerilog、399个Verilog源文件为主体辅以151个C/C头文件、111个C源文件及大量Makefile、脚本与文档压缩包约4.69MB目录结构完整便于按模块检索与工程化学习。目前已有1022人学习下载。通过研读可掌握处理器AXI接口的地址解码、命令生成、数据读写控制与错误处理并借助测试平台理解功能验证与覆盖率评估流程对提升硬件设计与SoC集成能力具有实际参考价值。1. 从一份 RISC-V AXI Verilog 源码说起为什么我建议你亲手跑一遍如果你正在找一份能直接上手、结构清晰的 RISC-V 处理器 RTL 源码那这份基于 Verilog 实现的 RISC-V AXI 总线互联项目值得你花一个周末拆一拆。它不是纸上谈兵的架构图而是一套可综合、可仿真的完整 RTL 代码核心在于把 RISC-V 核、AXI 总线互联和存储子系统串成了一条能跑通的链路。适合谁正在学 SoC 集成的 IC 方向学生、想从 ARM 生态切到 RISC-V 的 FPGA 工程师、以及需要一套干净参考设计做二次开发的老手。你拿到手就能用 Verilog 仿真器跑通一个最小系统看到 CPU 通过 AXI 发起读写、拿到返回数据的完整波形。这比看十篇总线协议文章都管用。2. 项目定位与技术选型这套 RTL 到底解决了什么问题2.1 它不是一个 CPU 核而是一个 SoC 集成骨架很多人看到“RISC-V 源码”第一反应是找一个处理器核。但这份代码的重点不在核本身而在于把核、总线、存储和外设粘合起来的集成层。RISC-V 核负责取指、译码、执行但指令和数据从哪里来、怎么送到核里、多个主设备怎么共享存储这些才是 SoC 设计里最容易翻车的地方。这套 RTL 用 AXI4 总线作为主干把处理器核的指令接口和数据接口分别接到 AXI 互联矩阵上再通过 AXI 转 SRAM 的桥接模块访问片上存储。整个数据通路是完整的不是零散的 IP 拼凑。从技术选型看用 Verilog 而不是 SystemVerilog 做 RTL 描述说明它更偏向可综合、可移植的工程实现而不是验证平台。AXI4 而不是 AXI3 或 AHB意味着它支持突发传输、乱序完成和独立的读写通道更贴近现代 SoC 的实际需求。存储侧用 SRAM 模型而不是 DDR 控制器降低了仿真门槛你不需要外挂复杂的存储模型就能跑通。这种“够用就好”的选型思路恰恰是学习总线互联最合适的起点。2.2 目录结构从 RTL 到仿真的完整组织拿到源码包后我一般先看目录结构因为它直接反映了设计者的模块划分思路。这套代码的组织方式大致如下riscv-axi-rtl/ ├── rtl/ │ ├── core/ # RISC-V 核相关 RTL │ ├── axi/ # AXI 互联矩阵与协议转换 │ ├── mem/ # SRAM 控制器与存储模型 │ └── top/ # 顶层模块与系统集成 ├── sim/ │ ├── tb/ # 测试平台 │ └── run/ # 仿真脚本与波形配置 ├── constraints/ # 时序约束文件 └── docs/ # 接口说明与寄存器映射rtl/core/下是处理器核的取指、译码、执行和寄存器堆模块通常以core_top.v作为核的顶层。rtl/axi/是重头戏包含 AXI 互联矩阵axi_interconnect.v、AXI 转 SRAM 桥axi2sram.v以及协议检查模块。rtl/mem/里是 SRAM 的行为级模型和控制器仿真时直接替换成真实 SRAM 即可。rtl/top/把核、互联、存储串起来形成soc_top.v。sim/tb/下的测试平台负责产生时钟复位、加载测试程序、发起激励并检查结果。提示不同版本的目录名可能有细微差异但模块划分逻辑基本一致。先找到soc_top.v顺着端口连线就能理清整个数据通路。2.3 关键模块的接口约定在动手改代码之前必须搞清楚几个核心模块的接口信号。以 AXI 互联矩阵为例它通常有多个主设备端口和多个从设备端口每个端口都遵循 AXI4 的五通道结构读地址通道、读数据通道、写地址通道、写数据通道和写响应通道。每个通道都有valid和ready握手信号这是 AXI 协议里最容易出问题的地方。处理器核的指令接口一般配置为只读主设备数据接口配置为读写主设备。互联矩阵根据地址译码把请求路由到对应的从设备。SRAM 控制器作为从设备接收 AXI 读写请求并转换为 SRAM 的片选、写使能和地址数据信号。地址映射通常在顶层模块里以参数形式定义比如指令存储区从0x8000_0000开始数据存储区从0x8000_0000开始外设区从0x1000_0000开始。这些参数在soc_top.v里以localparam或parameter声明改地址映射就是改这几个值。3. 跑通仿真从零到波形里看到 AXI 握手3.1 仿真环境准备与工具选择跑通这套 RTL 不需要昂贵的商业工具。常见的开源仿真器如 Icarus Verilog 和 Verilator 都能胜任前者对 Verilog 支持更完整后者速度更快但需要 C 测试平台。我一般先用 Icarus Verilog 跑功能仿真因为它对 AXI 这种带时序的协议仿真更直观波形里能看到每个握手信号的变化。安装 Icarus Verilog 在 Ubuntu 下就是一行命令sudo apt-get install iverilog gtkwavegtkwave用来查看生成的波形文件。如果你用 Windows可以装 MSYS2 或 WSL或者用 EDA Playground 在线跑但本地跑更自由改代码后重新编译只要几秒。仿真脚本通常放在sim/run/下名字可能是run.sh或Makefile。先看一眼脚本内容确认它调用了哪些源文件、顶层模块名是什么、仿真时间设了多长。常见做法是用一个filelist.f列出所有 RTL 文件然后iverilog -o sim.out -f filelist.f编译再vvp sim.out运行。3.2 编译与运行一条命令背后的文件依赖假设目录结构如上所述编译命令大致如下iverilog -g2012 -o sim.out \ -I rtl/core -I rtl/axi -I rtl/mem -I rtl/top \ rtl/core/*.v rtl/axi/*.v rtl/mem/*.v rtl/top/*.v \ sim/tb/tb_soc_top.v-g2012启用 SystemVerilog 2012 标准虽然代码是 Verilog但测试平台可能用了 SystemVerilog 的断言或logic类型。-I指定头文件搜索路径如果代码里有include语句会用到。最后列出所有 RTL 文件和测试平台文件。编译成功后生成sim.out用vvp sim.out运行。运行时会打印仿真日志通常包括测试用例开始、读写请求发起、数据比对结果。如果测试平台生成了 VCD 波形文件用gtkwave dump.vcd打开就能看到信号跳变。注意如果编译报错“module not found”先检查filelist.f里是否漏了某个文件或者模块名和文件名不一致。Verilog 不强制文件名等于模块名但仿真器按文件名找模块时容易出问题。3.3 波形解读一次 AXI 读事务的完整生命周期跑通仿真后在波形窗口里添加以下信号时钟clk、复位rst_n、AXI 读地址通道的arvalid、arready、araddr读数据通道的rvalid、rready、rdata。你会看到一次典型的 AXI 读事务主设备在arvalid拉高的同时给出araddr表示读地址有效。从设备在准备好接收时拉高arready两者同时为高的时钟上升沿完成地址握手。若干周期后从设备拉高rvalid并给出rdata主设备拉高rready表示可以接收。两者同时为高的时钟上升沿完成数据握手一次读事务结束。如果arvalid一直高但arready一直低说明从设备没准备好可能是地址译码没命中或 SRAM 控制器状态机卡住了。如果rvalid迟迟不来检查从设备是否收到了读请求以及 SRAM 读延迟是否设得过大。这些排查经验在仿真阶段就能积累比上板调试省事得多。3.4 测试平台怎么改换一个测试程序测试平台通常用一个initial块加载测试程序到指令存储器然后释放复位让 CPU 开始取指。测试程序一般以十六进制文件形式存在比如test.hex每行一条指令的机器码。如果你想换一个测试程序比如让 CPU 执行一段简单的加法循环可以用 RISC-V 工具链编译 C 代码生成hex文件替换掉原来的。常见做法是用riscv64-unknown-elf-gcc编译然后objcopy -O verilog生成十六进制文件。测试平台里的$readmemh任务负责加载initial begin $readmemh(test.hex, u_imem.mem_array); endu_imem是指令存储器的实例名mem_array是内部存储数组。改测试程序就是改这个文件路径和内容。如果你想验证数据通路的读写可以在测试平台里直接发起 AXI 读写请求不经过 CPU这样更可控。4. 核心模块拆解AXI 互联与 SRAM 桥的 RTL 实现4.1 AXI 互联矩阵的地址译码逻辑AXI 互联矩阵的核心是地址译码根据主设备发出的地址判断该请求应该路由到哪个从设备。下面是一段简化的地址译码逻辑// 地址译码根据 araddr 高位判断从设备 always (*) begin // 默认不选中任何从设备 slave_sel 2b00; if (araddr[31:28] 4h8) begin slave_sel 2b01; // 0x8000_0000 区域选中 SRAM end else if (araddr[31:28] 4h1) begin slave_sel 2b10; // 0x1000_0000 区域选中外设 end endaraddr[31:28]取地址的高 4 位做粗粒度译码实际项目中可能用更长的位宽做更细的划分。slave_sel信号控制多路选择器把读地址通道的信号转发到对应从设备的端口。写地址通道同理但要注意读写通道是独立的译码逻辑要分别处理。参数说明araddr是 32 位读地址slave_sel是 2 位从设备选择信号支持最多 4 个从设备。如果从设备数量超过 4 个需要加宽slave_sel位宽。地址映射关系通常写在顶层模块的参数里改映射就是改这里的比较值。4.2 AXI 转 SRAM 桥的状态机设计SRAM 接口比 AXI 简单得多片选、写使能、地址、写数据、读数据。AXI 转 SRAM 桥需要把 AXI 的突发传输拆解成单次 SRAM 访问或者用 SRAM 的流水线模式支持突发。下面是一个简化的状态机// AXI 写事务转 SRAM 写操作的状态机 localparam IDLE 2b00; localparam WRITE 2b01; localparam RESP 2b10; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; end else begin case (state) IDLE: begin if (awvalid wvalid) begin sram_cs 1b1; sram_we 1b1; sram_addr awaddr[15:2]; // 字对齐 sram_wdata wdata; state WRITE; end end WRITE: begin sram_we 1b0; state RESP; end RESP: begin bvalid 1b1; // 写响应有效 if (bready) begin bvalid 1b0; sram_cs 1b0; state IDLE; end end endcase end end这个状态机假设写地址和写数据同时有效实际 AXI 协议允许它们不同时到达所以需要加缓冲或握手逻辑。sram_addr取awaddr[15:2]是因为 SRAM 按字寻址地址低 2 位忽略。sram_cs是片选sram_we是写使能sram_wdata是写数据。写响应通道的bvalid在写完成后拉高等待主设备bready握手。提示如果 SRAM 读延迟大于 1 个周期读数据通道需要加寄存器缓冲否则rvalid和rdata对不上。4.3 处理器核的取指与数据接口对接处理器核通常有两个 AXI 主接口一个用于取指一个用于数据读写。取指接口只读数据接口读写。在顶层模块里这两个接口分别接到互联矩阵的不同主端口上。取指接口的地址来自程序计数器数据接口的地址来自执行单元的访存指令。对接时要注意位宽匹配。如果核的地址是 32 位AXI 互联也是 32 位直接连即可。如果核的数据是 32 位AXI 数据也是 32 位也没问题。但如果核的数据是 64 位而 AXI 是 32 位就需要加数据宽度转换逻辑把一次 64 位访问拆成两次 32 位访问。这套代码里通常保持一致避免转换开销。另一个注意点是时钟域。如果核和 AXI 互联在同一个时钟域直接连。如果跨时钟域需要加异步 FIFO 或握手同步器。仿真阶段通常用单时钟上板时如果核跑 50MHz 而 AXI 跑 100MHz就必须处理跨时钟域问题。4.4 存储模型与地址映射的修改方法SRAM 行为级模型通常是一个二维数组reg [31:0] mem_array [0:16383]; // 64KB SRAM always (posedge clk) begin if (sram_cs sram_we) begin mem_array[sram_addr] sram_wdata; end if (sram_cs !sram_we) begin sram_rdata mem_array[sram_addr]; end endmem_array大小决定 SRAM 容量16383是深度减一对应 64KB。改容量就是改这个数字。地址映射在顶层模块里定义比如指令存储区从0x8000_0000开始数据存储区从0x8000_0000开始两者共用同一块 SRAM 的不同区域。如果想让指令和数据分开可以例化两个 SRAM 模型分别映射到不同地址段。改地址映射时同步修改互联矩阵的译码逻辑和测试平台的地址常量否则 CPU 取指会跑到错误的位置。常见做法是把地址映射写成parameter在顶层统一管理子模块通过参数传递获取。5. 避坑与常见问题仿真跑不通时先查这几条5.1 编译报错“unknown module type”现象iverilog编译时提示某个模块未定义比如axi_interconnect。原因通常是文件列表不全或者模块名和文件名不一致导致仿真器找不到。解决用find . -name *.v列出所有 RTL 文件确认filelist.f里没有遗漏。如果模块名和文件名不同在filelist.f里显式列出文件名不要依赖自动搜索。5.2 仿真卡死波形里没有任何握手现象运行vvp sim.out后卡住不动波形里arvalid一直低或arready一直低。原因可能是复位没释放或者时钟没产生。解决检查测试平台的initial块确认rst_n在若干周期后拉高clk用always #5 clk ~clk产生。如果复位释放了但握手不动检查地址译码是否命中可以在译码逻辑里加$display打印地址和选中信号。5.3 读数据全为 X 或 0现象波形里rdata一直是x或0但rvalid有拉高。原因可能是 SRAM 模型没初始化或者读地址超出了mem_array范围。解决在测试平台里用$readmemh初始化 SRAM或者用for循环给mem_array赋初值。检查读地址是否在 SRAM 映射范围内如果超出范围互联矩阵可能返回默认值0。5.4 写事务完成但读不回数据现象写操作看起来完成了bvalid也拉高过但后续读操作返回的数据不是写入的值。原因可能是写地址和读地址没有对齐到同一字或者 SRAM 写使能时序不对。解决确认读写地址都按字对齐低 2 位为 0。检查 SRAM 写使能是否在时钟上升沿有效写数据是否在写使能有效时稳定。可以在 SRAM 模型里加$display打印每次读写地址和数据对比波形定位。5.5 上板后时序不满足现象仿真通过但综合实现后时序报告显示建立时间违例。原因通常是组合逻辑路径太长比如地址译码和互联选择逻辑在一个周期内完成。解决在关键路径上插入寄存器把地址译码和通道选择分成两级流水。或者降低时钟频率给组合逻辑更多时间。常见做法是在 AXI 互联矩阵的输入输出加寄存器切片牺牲一个周期延迟换时序余量。6. 二次开发从跑通到改出自己的一套 SoC跑通仿真只是第一步真正有价值的是在这套 RTL 上改出你自己的东西。我一般会从三个方向入手换核、扩总线、加外设。换核是最直接的改造。这套代码的处理器核是 RISC-V但接口是标准的 AXI 主接口。你可以把核换成另一个 RISC-V 实现只要它提供 AXI 主接口就能直接接到现有的互联矩阵上。如果新核用的是 AHB 或自定义总线就需要加一个协议转换桥。常见做法是保留互联矩阵和 SRAM 控制器只替换rtl/core/下的内容顶层模块的端口连线基本不用动。扩总线是更有挑战的改造。现有互联矩阵可能只支持 2 个主设备和 2 个从设备如果你想接入更多主设备比如 DMA或更多从设备比如 UART、SPI就需要扩展互联矩阵的端口数量和译码逻辑。AXI 互联矩阵的扩展通常用交叉开关crossbar结构每个主设备到每个从设备都有一条独立通路。你可以用现成的 AXI 交叉开关 IP也可以自己写一个简化的共享总线版本。自己写的好处是可控坏处是要处理更多的握手和仲裁逻辑。加外设是最实用的改造。比如加一个 UART让 CPU 能打印调试信息。UART 的寄存器接口通常是 AXI4-Lite比完整 AXI4 简单得多。你可以在互联矩阵上挂一个 AXI4-Lite 从设备端口把 UART 的寄存器映射到某个地址段。CPU 通过读写这些寄存器来发送和接收数据。测试平台里可以模拟 UART 接收把收到的字符打印到终端。验证改造是否成功我习惯用一个小测试程序让 CPU 通过 UART 打印 “Hello” 然后进入死循环。如果终端能看到 “Hello”说明取指、数据访问、总线互联和外设接口都通了。这个测试比复杂的基准程序更直接出问题时也更容易定位是哪一级出了问题。从那以后我每次改完 RTL都强制走一遍“编译→仿真→看波形→上板测 UART”的流程不跳过任何一步。因为很多问题在仿真阶段就能暴露上板只是确认。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑