基于 Vivado 的 RISC-V 五级流水线 CPU FPGA 实现详解
背景
在《计算机组成原理》课程中,五级流水、数据旁路、控制冒险等概念往往停留在理论层面。本文介绍在 Xilinx Artix-7 FPGA 上实现一个完整的 RISC-V 五级流水线 CPU,支持跑通汇编程序。
本文讲解每个模块的实现细节、关键信号连接及常见问题处理。
设计选型
为什么选择五级流水?
性能和资源的平衡是关键。单周期 CPU 虽然代码简单,但主频较低,大部分时间 ALU、内存处于空闲状态。五级流水将指令拆分为五个小步,从第 5 个周期开始,每个周期都能送出一条新指令的结果,实现吞吐率的飞跃。
RISC-V 的开放性和简洁性,特别是 RV32I 基础整数集只有几十条指令,控制逻辑清晰,适合 FPGA 开发。
硬件与工具
- 开发板:Artix-7 XC7A35T
- 工具:Vivado 2023.1
- 架构:支持 RV32I 的轻量级核心设计
流水线架构
五级流水线让多条指令像工厂流水线上的产品一样,并行推进。
| 阶段 | 干什么 | 关键任务 |
|---|---|---|
| IF(取指) | 取指令 | 给 PC 找地址,从 IMEM 拿指令 |
| ID(译码) | 拆指令 | 解析 opcode,读寄存器,生成控制信号 |
| EX(执行) | 算东西 | ALU 运算,地址计算,判断分支 |
| MEM(访存) | 访问内存 | Load/Store 数据,其他指令透传 |
| WB(写回) | 写结果 | 把数据写回寄存器 |
理想状态下,每一拍都有五条指令分布在不同阶段,CPU 利用率达到极致。但需处理流水线冒险。
核心模块设计
取指单元 (IF)
IF 单元负责更新 PC 和取指。
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
pc <= 32'h0;
else
pc <= pc + 4;
end
关键挑战
- 跳转指令:JAL 直接跳,BEQ 条件满足才跳。必须在 EX 阶段判断后反馈给 IF。
- 分支预测:初期默认不跳。如果跳了,清空 IF/ID 寄存器,重新从目标地址取指。
- IMEM 实现:使用 Xilinx XPM 原语创建双端口 RAM。
xpm_memory_sdpram #( .ADDR_WIDTH_A(10), // 1KB = 256 words .DATA_WIDTH_A(32) ) imem_inst ( .clka(clk), .addra(pc[3:2]), // 字对齐 .douta(inst_out) );
PC 更新需受控,加入 pc_en 和 pc_src 多路选择器,支持 jump、branch、exception 等多种来源。
译码单元 (ID)
ID 阶段核心任务是拆指令和读操作数。
寄存器文件实现要点
module regfile (
input clk,
input we, // 写使能
input [4:0] waddr, // 写地址
input [31:0] wdata, // 写数据
input [4:0] raddr1,
input [4:0] raddr2,
output [31:0] rdata1,
output [31:0] rdata2
);
reg [31:0] regs [0:31]; // 同步写:只在上升沿更新
always @(posedge clk) begin
if (we && waddr != 5'd0) // x0 永远为 0!
regs[waddr] <= wdata;
end
// 异步读:组合逻辑输出
assign rdata1 = (raddr1 == 5'd0) ? 32'd0 : regs[raddr1];
assign rdata2 = (raddr2 == 5'd0) ? 32'd0 : regs[raddr2];
endmodule

