当前位置: 首页 > news >正文

miniRV CPU 设计实战:从数据通路到 FPGA 下板运行 CoreMark

从单周期 CPU 到五级流水线 SoC:miniRV 的设计、AXI 集成与 FPGA 验证

本文记录一个 miniRV CPU/SoC 项目的完整实现过程:先完成支持全部实验指令的单周期 CPU,再将数据通路改造成五级流水线,加入数据前递、流水线暂停和静态分支预测,最后集成 ICache、DCache、AXI 总线及外设,并在 FPGA 上完成 C_TEST 与 CoreMark 验证。

1. 项目目标与最终结果

这个项目并不只是实现一个能够执行几条指令的 CPU,而是要逐步建立一套可以仿真、可以连接总线、可以运行 C 程序并且能够真正下板的处理器系统。

最终完成的主要功能如下:

  • 支持 miniRV 测试框架中的 44 条指令,包括算术逻辑、移位、比较、分支跳转、访存以及乘除法指令;
  • 完成完整单周期 CPU,并通过 Basic Trace;
  • 将单周期数据通路改造成 IF、ID、EX、MEM、WB 五级流水线;
  • 实现 EX/MEM/WB 到 ID 的数据前递;
  • 实现 load-use 冒险检测、气泡插入和多周期操作暂停;
  • 使用“默认不跳转”的静态预测处理控制冒险;
  • 集成 ICache、DCache 和基于状态机的 AXI4 主控制器;
  • 支持拨码开关、LED、数码管、UART 和计时器五类外设;
  • Cache 开启后通过 AXI Trace 44/44;
  • 在 FPGA 上通过 UART、格式化输入输出、排序和动态内存测试;
  • 在 50 MHz 下完成 700 次 CoreMark 迭代,CRC 校验正确;
  • Vivado 实现后的 WNS 为 +5.728 ns,TNS 为 0,无时序违例。

下面按“单周期 CPU—流水线 CPU—SoC—验证”的顺序介绍整个设计。

2. 从完整单周期 CPU 开始

单周期 CPU 的特点是:一条指令从取指、译码、执行、访存到写回的全部组合逻辑都在一个时钟周期内完成。它的结构直观,很适合先验证指令译码和基本功能,但时钟周期必须覆盖最慢指令的完整路径,因此主频提升空间有限。

2.1 单周期数据通路

下图是本项目绘制的完整单周期 CPU 数据通路图。图中包含 PC/NPC、Controller、SEXT、RF、ALU、MREQ、MEXT、数据存储器接口和写回选择器。

数据通路图2

图 1 完整单周期 CPU 数据通路图(draw.io 绘制)

一条普通算术指令的通路可以概括为:

PC → 指令存储器 → Controller/RF/SEXT→ ALU 输入选择 → ALU 运算→ 写回选择器 → RF

访存指令则在 ALU 计算地址后经过 MREQ 和 MEXT:

  • MREQ 根据 ram_ropram_wop 产生字节写使能、访问地址和对齐后的写数据;
  • MEXT 根据加载类型和地址低两位,从返回的 32 位数据中选出字节、半字或字,再进行符号扩展或零扩展;
  • lb/lbu/lh/lhu/lw 的结果最终由写回 MUX 送回寄存器堆。

乘除法器是多周期部件。启动后 busy 拉高,CPU 暂停提交新指令;运算完成后 busy 拉低,结果才写回。需要注意的是,PC 并不是因为执行了乘法就永远停止,而是只在多周期运算进行期间保持,完成后继续更新到下一条指令。

2.2 控制信号的作用

Controller 读取 opcodefunct3funct7,产生以下几类控制信号:

控制信号 作用
npc_op 选择顺序执行、分支、JAL 或 JALR 的下一 PC
sext_op 选择 I/S/B/U/J 型立即数格式
alu_op 选择 ALU 的算术、逻辑、移位、比较或乘除法操作
alua_selalub_sel 选择 ALU 两个操作数的来源
ram_ropram_wop 指定加载扩展方式和存储字节使能
rf_wsel 选择写回数据来自 ALU、PC+4、立即数还是存储器
rf_we 寄存器堆写使能

单周期阶段最重要的工作,是让每条指令的控制信号和数据通路严格对应。Basic Trace 全部通过后,才适合继续进行流水化改造。

3. 五级流水线数据通路

流水线版本采用经典的五级结构:

流水级 主要工作
IF 产生取指地址,通过 ICache 取回指令
ID 指令译码、读取寄存器、立即数扩展、选择前递数据
EX ALU 运算、乘除法、分支判断和目标地址计算
MEM 通过 MREQ/DCache 完成数据访问并扩展加载结果
WB 选择最终结果并写回寄存器堆

相邻流水级之间加入 IF/ID、ID/EX、EX/MEM 和 MEM/WB 四组流水寄存器。除了数据本身,目标寄存器号、写使能、访存类型和写回选择等控制信息也必须与对应指令一起向后传递,否则就会发生“后一条指令的控制信号写回前一条指令结果”的错误。

3.1 完整流水线数据通路

下图是按照实际 RTL 信号绘制的完整流水线数据通路。由于原图较宽,建议点击图片查看 SVG 原图;后文也给出了各区域的局部图。

实验二数据通路图

图 2 五级流水线 CPU 完整数据通路图

3.2 IF 与 ID:带缓冲的取指前端

这一部分对应图 2 左侧的 IF、IF/ID 与 ID 区域。由于完整图较宽,可以点击图 2 打开 SVG 后放大查看 Controller、取指缓冲以及 ID Instruction Flags 之间的具体连线。

流水线取指端不能只写成简单的 ifetch_req = 1。ICache 使用 ready/valid 握手,流水线又可能因为访存、乘除法或 load-use 暂停,因此本项目增加了请求记录和响应缓冲:

  • fetch_pending:已有一个被 ICache 接收的请求正在等待响应;
  • fetch_req_pc:保存该请求对应的 PC;
  • fetch_buf_valid:暂停期间返回的指令已经保存在缓冲区;
  • fetch_discard:分支冲刷后,尚未返回的旧路径响应需要丢弃。

前端只有在没有复位、没有冲刷、没有暂停且缓冲区有空间时才发出新请求:

wire fetch_request = !cpu_rst && !flush && !fetch_discard &&!pipeline_stall &&(!fetch_buf_valid || fetch_buf_consume);

这个缓冲逻辑解决了两个容易出错的问题:一是流水线暂停时,已经返回的指令不能丢;二是 AXI/Cache 请求发出后不能随意取消,发生跳转时必须把错误路径响应标记并丢弃。

3.3 EX:运算、分支与多周期控制

ID/EX 寄存器把源操作数、立即数、PC、目标寄存器号以及控制信号送到 EX。ALU 两个输入分别由 ex_alua_selex_alub_sel 控制,因此能够支持寄存器—寄存器、寄存器—立即数以及 PC—立即数等组合。

本项目采用静态预测不跳转:IF 始终先按 PC+4 取指,分支和跳转在 EX 阶段得到实际结果。当实际需要跳转时,flush 拉高,PC 改为正确目标,同时将年轻指令替换为 NOP:

wire ex_bj_taken = (ex_npc_op == `NPC_JMP)| (ex_npc_op == `NPC_JALR)| ((ex_npc_op == `NPC_BRA) & ex_br);assign flush = ex_bj_taken;
assign npc   = flush ? ex_bj_target : npc_pc4;

乘除法同样位于 EX,但它们不能在一拍内完成。首次进入 EX 时产生启动条件,busy 有效期间保持相关流水寄存器,完成后才允许流水线继续前进。

4. 数据冒险:前递与 load-use 暂停

经典顺序五级流水线主要需要处理 RAW(Read After Write)冒险。后一条指令在 ID 读取源寄存器时,前一条指令可能仍处于 EX、MEM 或 WB,尚未完成寄存器堆写回。

4.1 EX/MEM/WB 到 ID 的数据前递

本设计比较 ID 阶段的 rs1/rs2 与 EX、MEM、WB 阶段的目标寄存器 rd。只有前级确实写寄存器、rd != x0 且寄存器号相等时,才允许前递。

wire fwd_ex_rs1  = ex_rf_we  & (ex_wR  != 5'h0) & (ex_wR  == id_inst[19:15]);
wire fwd_mem_rs1 = mem_rf_we & (mem_wR != 5'h0) & (mem_wR == id_inst[19:15]);
wire fwd_wb_rs1  = wb_rf_we_reg & (wb_wR != 5'h0) & (wb_wR == id_inst[19:15]);wire [31:0] id_rD1 = fwd_ex_rs1  ? ex_fwd_result :fwd_mem_rs1 ? mem_result    :fwd_wb_rs1  ? wb_rf_wD      : id_rD1_raw;

两个源操作数的选择优先级均为:

EX > MEM > WB > Register File

优先选择最近的结果非常重要。例如 EX 和 MEM 中两条指令都准备写同一个寄存器时,ID 应使用程序顺序上更新的 EX 结果,而不是较旧的 MEM 结果。

4.2 load-use 为什么仍需暂停一拍

普通 ALU 指令的结果在 EX 末尾已经产生,可以直接前递;但 load 的有效数据要等到 MEM 阶段收到 daccess_rvalid 后才得到。因此相邻的 load-use 不能只靠 EX 前递解决。

本项目的检测条件是:EX 当前为加载指令、目标寄存器非零,而且 ID 指令实际使用的源寄存器与其相同。

wire load_use_hazard = ex_rf_we& (ex_ram_rop != `RAM_EXT_N)& (ex_wR != 5'h0)& ((id_uses_rs1 & (ex_wR == id_inst[19:15]))|  (id_uses_rs2 & (ex_wR == id_inst[24:20])));

检测到冒险后,PC 和 IF/ID 保持,ID/EX 写入一个 NOP。等 load 数据在 MEM 阶段有效后,再通过 MEM→ID 前递送给依赖指令。实际波形中使用过下面这一组相邻指令进行检查:

lw   x14, 4(x1)
addi x6,  x14, 0

这里暂停不是越多越安全。早期版本直接比较指令的 rs1/rs2 字段,可能把立即数字段误认为源寄存器,造成无意义停顿;最终版本增加 id_uses_rs1/id_uses_rs2,只在指令真正读取对应寄存器时判断冒险。

5. MEM、WB 与数据访问

EX/MEM 中的 ALU 结果既可能是算术结果,也可能是访存地址。MREQ 将内部加载/存储控制转换为 DCache 请求;加载数据返回后,MEXT 根据 mem_ram_rop 和地址低两位完成字节选择与扩展。

MEM/WB 保存四类可能的写回来源:

  • ALU 结果;
  • 扩展后的加载数据;
  • PC+4;
  • U 型立即数。

WB MUX 根据 wb_rf_wsel 生成 wb_rf_wD,再与 wb_wRwb_rf_we 一起返回寄存器堆。wb_rf_wD 同时也是 WB→ID 的前递数据,因此写回和前递使用的是同一份最终结果。

6. ICache、DCache 与 AXI 总线

流水线核心之外,SoC 还加入了独立的 ICache 和 DCache。两者在 L1 层面构成哈佛结构,可以让取指和数据访问使用独立接口;Cache miss 后则共同通过 AXI 主控制器访问统一主存。

6.1 Cache 的基本组织

本工程 Cache 行大小为 4 个 32 位字,也就是 16 字节。一次命中通过 Tag、Valid 和 Index 判断;未命中时请求整行回填。

ICache miss 的主要时序如下:

CPU 取指请求→ ICache 查找未命中→ bus_rreq 拉高,给出 16 字节对齐地址→ AXI AR 通道握手,ARLEN=3→ AXI R 通道返回 4 个数据拍→ fill_count 从 0 增加到 3→ RLAST 到达,写入 Tag/Valid→ 输出请求指令并拉高 inst_valid

DCache 的读缺失流程类似。MMIO 访问不进入 DCache,避免外设读写被缓存;外设写请求也只提交一次,防止 UART 重复发送字符。

6.2 AXI 主控制器状态机

AXI 读写通道相互独立,不能假设地址和数据一定同拍握手。因此 axi_master 使用状态机管理事务:

状态 含义
ST_IDLE 仲裁指令读、数据读和数据写请求
ST_AR 保持 ARVALID,等待 ARREADY
ST_R 接收一个或多个 R 数据拍,直到 RLAST
ST_AW_W 分别完成 AW 和 W 握手
ST_B 等待写响应 BVALID

控制器还使用 read_src 记录当前读事务来自 ICache 还是 DCache,保证返回数据送到正确的请求方。AXI 写地址和写数据可以在不同周期被接收,因此代码分别记录 aw_donew_done,两者都完成后才进入写响应状态。

7. SoC 外设与 C 程序接口

在总线系统之上,SoC 通过 MMIO 提供五类外设:

  • 拨码开关输入;
  • LED 输出;
  • 八位数码管显示;
  • UART 收发;
  • 64 位计时器。

UART 软件驱动通过状态寄存器判断 TX FIFO 是否已满、RX FIFO 是否非空,再读写数据寄存器。初始化时通过控制寄存器清空收发 FIFO。该接口既支撑简单字符输入输出,也支撑 printfscanf、程序下载和 CoreMark 结果打印。

8. 验证方法与结果

CPU 设计不能只看综合是否通过。综合通过只说明 RTL 可以映射成硬件,并不能证明指令结果、总线握手或流水线冒险处理正确。本项目采用了四层验证。

8.1 Basic Trace

Basic Trace 直接比较 CPU 提交结果与参考模型,主要验证指令语义和流水线控制。最终结果为:

Passed Tests (44)
Failed Tests (0)

覆盖的指令包括 add/addi、逻辑与移位、六类条件分支、jal/jalr、五类加载、三类存储以及 mul/div/rem 等。

在 WSL 中可以使用以下方式重新构建流水线 Basic Trace:

cd /mnt/c/Users/lenovo/Desktop/CPU/cdp-tests
make clean
make -f Makefile.pipeline_basic build
python3 run_all_tests.py

8.2 AXI Trace(Cache 开启)

AXI Trace 使用完整 miniRV_SoC,仿真模型中包含流水线核心、ICache、DCache、AXI Master 和 AXI BRAM。最终在 Cache 开启状态下同样通过 44/44。

cd /mnt/c/Users/lenovo/Desktop/CPU/cdp-tests
make clean
make build SOC_DIR=../miniRV_basic_FLOAT/miniRV_basic_FLOAT/miniRV_basic/src/rtl
python3 run_all_tests.py

调试 Cache miss 时,重点观察:

ICache.state、cpu_hit、bus_rreq、bus_raddr
axi_master.state、m_axi_arvalid、m_axi_arready
m_axi_rvalid、m_axi_rready、m_axi_rdata、m_axi_rlast
fill_count、inst_valid

它们能够完整展示“未命中—AXI 地址握手—四拍回填—重新命中”的全过程。

8.3 C_TEST 下板

流水线 SoC 完成了三个接口测试程序的实板验证:

  1. UART 输出 Hello World!,并能逐字符接收输入;
  2. 格式化输入 21 G good,能够正确解析整数、字符和字符串;
  3. 固定数组排序正确,malloc 创建的 30 元素数组也能完成排序和释放。

这些测试共同覆盖了 UART、格式化 I/O、递归/排序、动态内存和计时器访问,比只观察 LED 更接近真实软件运行环境。

8.4 CoreMark 与时序

本项目选择 CoreMark 作为最终性能下板程序。Cache 开启、CPU 主频 50 MHz、迭代 700 次时,串口输出为:

Iterations       : 700
Correct operation validated.
CoreMark 1.0     : 54.0495
CoreMark/MHz     : 1.0809
FINISH

同时 Vivado 实现结果为:

项目 结果
CPU 主频 50 MHz
WNS +5.728 ns
TNS 0 ns
WHS +0.024 ns
THS 0 ns
DRC violations 0
Routing errors 0

WNS 为正、TNS 为 0,说明设计在 50 MHz 约束下不存在 setup 时序违例。CoreMark 的 CRC 全部正确,说明得到分数的前提是程序执行结果正确,而不是仅仅“串口打印完成”。

9. 调试过程中最值得记录的几个问题

9.1 不要把 ready/valid 当成固定延迟

Cache 和 AXI 的响应时间不是固定一拍。请求方必须保持有效信号直到握手,响应方也只能在 valid 与 ready 同时有效时推进状态。大量总线问题都来自“默认下一拍一定返回”的单周期思维。

9.2 冲刷不仅要清流水寄存器

分支跳转时清空 IF/ID、ID/EX 还不够。已经被 ICache/AXI 接收的旧路径请求无法撤销,所以还要使用 fetch_discard 丢弃未来到达的错误响应,否则错误路径指令仍可能进入流水线。

9.3 暂停和保持不是同一个概念

load-use 冒险需要保持 PC 与 IF/ID,同时向 ID/EX 插入气泡;访存等待或乘除法执行期间,则需要保持正在执行的 EX/MEM 指令。把所有寄存器统一“全部保持”或“全部清零”,都可能造成重复执行或指令丢失。

9.4 波形要围绕因果链观察

分析数据前递时,不应只看 fwd_ex_rs1 是否为 1,还要同时检查:

id_inst 的 rs1/rs2
ex_wR / mem_wR / wb_wR
各级 rf_we
ex_fwd_result / mem_result / wb_rf_wD
id_rD1_raw 与最终 id_rD1

分析 load-use 时,则应把 load_use_hazardpipeline_stall、ID/EX 是否写入 NOP、数据响应和后续 MEM 前递放在同一时间轴上。只有形成完整因果链,波形截图才真正有说服力。

10. 总结

这个项目的难点不在某一条 Verilog 语句,而在于让多层协议同时保持一致:指令语义要正确,流水级之间的数据和控制要对齐,暂停与冲刷不能丢指令,Cache 必须正确处理命中和回填,AXI 又必须遵守各通道的独立握手规则。

从单周期 CPU 到五级流水线,再到能够运行 C 程序的 AXI SoC,整个过程让我对“CPU 是怎样真正运行程序的”有了更具体的认识。最终的 44/44 Basic Trace、44/44 AXI Trace、C_TEST、CoreMark 和正时序裕量,也分别从指令、总线、软件和硬件实现四个层面证明了系统的完整性。

后续如果继续优化,可以从以下方向入手:减少暂停周期、改善 Cache 组织和替换策略、提高乘除法吞吐率、缩短关键路径,并在保证时序收敛的前提下继续提高主频。


项目关键词:RISC-VminiRVVerilog五级流水线数据前递load-useCacheAXI4FPGACoreMark

http://www.jsqmd.com/news/1394618/

相关文章:

  • 2026厦门经济犯罪律师选择指南:3家专业刑辩律所对比 帮信罪/合同诈骗委托避坑要点 - 商业大观
  • 经营驾驶舱:业务财务贯通让异常可逐级追溯
  • 0061基于 SpringBoot 的投稿与稿件处理系统设计与实现
  • 盘锦装修签合同前门窗选购要看哪些细节
  • 请假陪床3天,被扣光全勤[特殊字符][特殊字符]
  • 2026生成式引擎优化平台品牌代运营哪家口碑好?严选评析分享
  • 如何通过一张照片来获取 ip 地址?
  • 微修点喷和整车喷漆,哪种更赚钱?从成本、效率、复购看艺猫点喷的独有优势 - 资讯报道
  • 2026成都一站式高性价比家装公司选型指南:3家合规靠谱品牌盘点+签约避坑全维度FAQ - 行业观察网
  • 2026四大AI论文写作软件深度测评|从降重到润色,各有所长别盲选
  • 厦门合同诈骗辩护律所:3家商事犯罪律所对比与合作选择指南(2026版) - 行业观察网
  • NeteaseCloudMusicFlac 快速上手:一条命令批量下载网易云无损FLAC音乐
  • 《2026成都全屋整装公司推荐大全:正规合规、高性价比、口碑扎实、全场景适配的服务商盘点+选型标准与签约避坑全维度FAQ》 - 商业大观
  • 登报道歉范文怎么写?怎么办?流程怎么走?5分钟搞定!
  • 上饶4家景区新晋国家3A!2026上饶旅行社怎么选?北京出发家庭游看这篇 - 江西旅讯
  • Skill - 把风格写成规格:拆解 ian-xiaohei-illustrations 中文配图 Skill
  • 毕业证公证认证流程全解:从国内公证到海牙领事认证完整操作指南
  • 2026正规靠谱的转转垫生产代工厂甄选指南:行业头部代工厂盘点+选型标准+合作避坑FAQ - U渠道
  • 2026年智能运维平台选型指南:四类技术路线的定位差异与决策框架
  • 新一轮国补来了!国补政策 2026 最新消息:8 月第三批国补重磅落地!买手机、家电、电脑、空调国补领取方法操作流程一览!学生还有返校季优惠叠加! - 生活动态圈
  • 【电子战】电子战系列博客预告:从检测到定位——MATLAB 实战指南【含matlab代码】
  • 六西格玛是什么意思:概念、σ等级换算表、DMAIC流程一文讲清
  • 2026厦门非法吸收公众存款案件维权:3家刑事律所筛选避坑指南及实力机构推荐 - U渠道
  • CK9561怎么接线?
  • 2026福建自动换套马桶盖OEM代工厂家全维度选型指南:正规合规、实力口碑兼具的服务商盘点+合作避坑FAQ - 商业大观
  • 产品全览:,MEMS红外测温传感器如何用多系列覆盖实现场景闭环
  • 2026成都工序验收装修公司推荐大全:具备标准化工序验收体系的正规服务商盘点+签约避坑指南FAQ - 产业观察报
  • 小程序定制开发怎么选性价比高的合作方?
  • Moveit常用命令
  • 2026酒店翻新转转垫ODM代工供应商选型全指南 正规实力品牌盘点+签约避坑全维度FAQ - 商业大观