从零开始:用Verilog手把手实现一个RISC-V五级流水线CPU(附完整代码)

张开发
2026/4/13 22:03:59 15 分钟阅读

分享文章

从零开始:用Verilog手把手实现一个RISC-V五级流水线CPU(附完整代码)
从零开始用Verilog手把手实现一个RISC-V五级流水线CPU附完整代码在数字电路设计的领域中CPU的设计与实现一直是硬件工程师的终极挑战之一。而RISC-V架构的兴起为学习CPU设计提供了一个绝佳的切入点。本文将带领你从零开始用Verilog HDL实现一个完整的RISC-V五级流水线CPU涵盖从模块划分到冒险处理的每一个细节。1. 环境准备与基础架构1.1 开发环境配置在开始编码之前我们需要搭建一个合适的开发环境。推荐使用以下工具链仿真工具Verilator开源或ModelSim商业综合工具Yosys开源或Vivado商业RISC-V工具链riscv-gnu-toolchain# 安装VerilatorUbuntu示例 sudo apt-get install verilator # 安装RISC-V工具链 git clone --recursive https://github.com/riscv/riscv-gnu-toolchain cd riscv-gnu-toolchain ./configure --prefix/opt/riscv make1.2 五级流水线基础架构RISC-V五级流水线包含以下五个阶段IFInstruction Fetch指令获取IDInstruction Decode指令解码与寄存器读取EXExecute执行运算MEMMemory Access数据存储器访问WBWrite Back结果写回寄存器module riscv_core( input wire clk, input wire reset, output wire [31:0] pc ); // 流水线寄存器定义 reg [31:0] IF_ID_inst, IF_ID_pc; reg [31:0] ID_EX_rs1, ID_EX_rs2, ID_EX_imm; reg [4:0] ID_EX_rd; // ...其他寄存器定义 // 各阶段模块实例化 always (posedge clk) begin if (reset) begin // 复位逻辑 end else begin // 流水线推进逻辑 end end endmodule2. 核心模块实现2.1 取指阶段IF取指阶段负责从指令存储器中读取指令并更新程序计数器PC。module instruction_fetch( input wire clk, input wire reset, input wire stall, input wire branch_taken, input wire [31:0] branch_target, output reg [31:0] pc, output wire [31:0] instruction ); reg [31:0] imem [0:1023]; // 1KB指令存储器 always (posedge clk) begin if (reset) begin pc 32h8000_0000; // 复位地址 end else if (!stall) begin if (branch_taken) pc branch_target; else pc pc 4; end end assign instruction imem[pc[31:2]]; // 字寻址 endmodule2.2 译码阶段ID译码阶段解析指令读取寄存器文件并生成控制信号。module instruction_decode( input wire [31:0] inst, input wire [31:0] pc, input wire reg_write_en, input wire [4:0] reg_write_addr, input wire [31:0] reg_write_data, output wire [31:0] rs1_data, output wire [31:0] rs2_data, output wire [31:0] imm, output wire [4:0] rd, output wire [6:0] opcode, output wire [2:0] funct3, output wire [6:0] funct7 ); reg [31:0] reg_file [0:31]; // 32个32位寄存器 // 寄存器写回逻辑 always (posedge clk) begin if (reg_write_en reg_write_addr ! 0) reg_file[reg_write_addr] reg_write_data; end // 立即数生成 assign imm { inst[31] ? 20hFFFFF : 20h00000, inst[31:20] }; // I-type立即数 assign rs1_data reg_file[inst[19:15]]; assign rs2_data reg_file[inst[24:20]]; assign rd inst[11:7]; assign opcode inst[6:0]; assign funct3 inst[14:12]; assign funct7 inst[31:25]; endmodule3. 冒险处理机制3.1 数据冒险与转发数据冒险发生在后续指令需要前面指令的结果时。转发Forwarding是最常见的解决方案。module forwarding_unit( input wire [4:0] ID_EX_rs1, input wire [4:0] ID_EX_rs2, input wire EX_MEM_reg_write, input wire [4:0] EX_MEM_rd, input wire MEM_WB_reg_write, input wire [4:0] MEM_WB_rd, output reg [1:0] forwardA, output reg [1:0] forwardB ); always (*) begin // 默认不转发 forwardA 2b00; forwardB 2b00; // EX阶段转发 if (EX_MEM_reg_write EX_MEM_rd ! 0) begin if (EX_MEM_rd ID_EX_rs1) forwardA 2b10; if (EX_MEM_rd ID_EX_rs2) forwardB 2b10; end // MEM阶段转发 if (MEM_WB_reg_write MEM_WB_rd ! 0) begin if (MEM_WB_rd ID_EX_rs1) forwardA 2b01; if (MEM_WB_rd ID_EX_rs2) forwardB 2b01; end end endmodule3.2 控制冒险与分支预测控制冒险主要由分支指令引起。我们实现一个简单的静态分支预测器。module branch_predictor( input wire clk, input wire reset, input wire [31:0] pc, input wire branch_taken, input wire [31:0] branch_target, output wire predict_taken, output wire [31:0] predict_target ); reg [31:0] btb [0:1023]; // 分支目标缓冲区 always (posedge clk) begin if (reset) begin // 初始化BTB end else if (branch_taken) begin btb[pc[11:2]] branch_target; // 更新BTB end end assign predict_taken (btb[pc[11:2]] ! 0); assign predict_target btb[pc[11:2]]; endmodule4. 完整实现与测试4.1 顶层模块集成将所有模块集成到顶层设计中并添加必要的流水线寄存器。module riscv_pipeline( input wire clk, input wire reset ); // 信号定义 wire [31:0] pc, instruction; wire [31:0] rs1_data, rs2_data, imm; wire [4:0] rd; wire [6:0] opcode; wire [2:0] funct3; wire [6:0] funct7; // 模块实例化 instruction_fetch IF( .clk(clk), .reset(reset), .pc(pc), .instruction(instruction) ); instruction_decode ID( .inst(IF_ID_inst), .pc(IF_ID_pc), // ...其他信号连接 ); // ...其他模块实例化 // 流水线寄存器 always (posedge clk) begin if (reset) begin IF_ID_inst 0; IF_ID_pc 0; // ...其他寄存器复位 end else if (!stall) begin IF_ID_inst instruction; IF_ID_pc pc; // ...其他寄存器更新 end end endmodule4.2 测试程序编写编写一个简单的测试程序验证CPU功能。# test.s _start: addi x1, x0, 5 # x1 5 addi x2, x0, 3 # x2 3 add x3, x1, x2 # x3 x1 x2 8 sub x4, x1, x2 # x4 x1 - x2 2 and x5, x1, x2 # x5 x1 x2 1 or x6, x1, x2 # x6 x1 | x2 7 beq x1, x2, _end # 不跳转 sw x3, 0(x0) # 存储x3到内存0地址 lw x7, 0(x0) # x7 8 _end: j _end4.3 仿真与调试使用Verilator进行仿真并观察波形。# 编译仿真 verilator -Wall --cc riscv_pipeline.v --exe sim_main.cpp make -C obj_dir -f Vriscv_pipeline.mk # 运行仿真 obj_dir/Vriscv_pipeline在仿真中重点关注以下信号流水线各阶段的指令寄存器文件的变化冒险处理单元的决策分支预测的结果5. 性能优化与扩展5.1 流水线深度优化通过分析关键路径我们可以优化流水线的性能IF阶段添加指令缓存减少访存延迟ID阶段提前生成立即数和控制信号EX阶段使用进位选择加法器优化关键路径// 进位选择加法器实现 module carry_select_adder( input wire [31:0] a, input wire [31:0] b, output wire [31:0] sum ); wire [15:0] sum_low, sum_high0, sum_high1; wire carry_low; // 低16位加法 ripple_carry_adder #(16) rca_low( .a(a[15:0]), .b(b[15:0]), .cin(1b0), .sum(sum_low), .cout(carry_low) ); // 高16位两种可能 ripple_carry_adder #(16) rca_high0( .a(a[31:16]), .b(b[31:16]), .cin(1b0), .sum(sum_high0) ); ripple_carry_adder #(16) rca_high1( .a(a[31:16]), .b(b[31:16]), .cin(1b1), .sum(sum_high1) ); // 根据低16位进位选择结果 assign sum { carry_low ? sum_high1 : sum_high0, sum_low }; endmodule5.2 指令集扩展在基础实现上我们可以扩展支持更多指令指令类型新增指令功能描述算术mul, div乘除法运算逻辑xor, sll, srl异或、移位操作控制jal, jalr跳转并链接系统ecall, ebreak系统调用和调试中断// 扩展的ALU操作 always (*) begin case (alu_op) ALU_ADD: result a b; ALU_SUB: result a - b; ALU_AND: result a b; ALU_OR: result a | b; ALU_XOR: result a ^ b; ALU_SLL: result a b[4:0]; ALU_SRL: result a b[4:0]; ALU_SRA: result $signed(a) b[4:0]; ALU_SLT: result ($signed(a) $signed(b)) ? 32d1 : 32d0; ALU_SLTU: result (a b) ? 32d1 : 32d0; default: result 32d0; endcase end5.3 高级特性实现对于更高级的实现可以考虑以下特性异常处理添加CSR寄存器和异常处理逻辑中断支持实现中断控制器和优先级机制缓存系统添加指令和数据缓存总线接口实现AXI或Wishbone总线接口// 异常处理逻辑示例 module exception_handler( input wire clk, input wire reset, input wire [31:0] pc, input wire [31:0] inst, input wire illegal_inst, input wire ecall, input wire ebreak, output wire [31:0] trap_vector, output wire trap_taken ); reg [31:0] mtvec; // 陷阱向量基址 reg [31:0] mepc; // 异常PC reg [31:0] mcause; // 异常原因 always (posedge clk) begin if (reset) begin mtvec 32h8000_0000; end else if (trap_taken) begin mepc pc; if (illegal_inst) mcause 32d2; else if (ecall) mcause 32d11; else if (ebreak) mcause 32d3; end end assign trap_taken illegal_inst | ecall | ebreak; assign trap_vector mtvec (mcause 2); endmodule

更多文章