第20课 · 流水线CPU
完整流水线前递+停顿10条指令
📌 学习目标:将前19课的所有知识整合,实现带前递和停顿的完整 5 级流水线 RISC-V CPU,执行 10 条指令的程序,通过 Verilator 验证流水线执行结果。
一、流水线 CPU 架构总览
┌─────────┐ IF/ID ┌──────────┐ ID/EX ┌────────┐ EX/MEM ┌────────┐ MEM/WB ┌─────┐
│ IF 级 │──→ regs ──→│ ID 级 │──→ regs ─→│ EX 级 │──→ regs─→│ MEM 级│──→ regs─→│WB级 │
│取指+PC │ │译码+RF读 │ │ ALU │ │DMem │ │写回 │
└─────────┘ └──────────┘ └───────┘ └───────┘ └─────┘
↑ ↑ ↑ ↑ ↑ ↑
│ │ │ │ │ │
└── PC 选择 ←────────┴──┴── 前递逻辑 ────┴───────────────┘ │
│ │
└──── 冒险检测(停顿) ──────────────────────────────────────┘
二、测试程序
10 条指令的程序,覆盖所有指令类型和数据冒险场景:
0: ADDI x1, x0, 10 # x1 = 10
1: ADDI x2, x0, 20 # x2 = 20
2: ADD x3, x1, x2 # x3 = 30 (数据冒险: 需要x1,x2 → 前递解决)
3: SUB x4, x3, x1 # x4 = 20 (数据冒险: 需要x3 → 前递解决)
4: SW x3, 0(x0) # Mem[0] = 30
5: LW x5, 0(x0) # x5 = 30 (Load-Use: 下条需要x5 → 停顿)
6: ADDI x6, x5, 5 # x6 = 35 (需要前递x5)
7: AND x7, x1, x2 # x7 = 10 & 20 = 0 (R-type AND)
8: OR x8, x1, x2 # x8 = 10 | 20 = 30 (R-type OR)
9: BEQ x1, x1, +8 # 分支跳转 (x1==x1 → 跳转)
三、Verilog 流水线 CPU
module pipeline_cpu (
input clk, rst
);
reg [31:0] ifid_instr, ifid_pc;
reg [31:0] idex_pc, idex_rs1_data, idex_rs2_data, idex_imm;
reg [4:0] idex_rs1, idex_rs2, idex_rd;
reg idex_reg_write, idex_mem_read, idex_mem_write;
reg idex_mem_to_reg, idex_alu_src, idex_branch;
reg [1:0] idex_alu_op;
reg [2:0] idex_funct3;
reg [6:0] idex_funct7;
reg [31:0] exmem_alu_result, exmem_rs2_data, exmem_pc;
reg [4:0] exmem_rd;
reg exmem_reg_write, exmem_mem_read, exmem_mem_write;
reg [31:0] memwb_alu_result, memwb_mem_rdata;
reg [4:0] memwb_rd;
reg memwb_reg_write, memwb_mem_to_reg;
reg [31:0] imem [0:15];
reg [31:0] dmem [0:15];
reg [31:0] regs [0:31];
reg [31:0] pc;
wire [31:0] instr_f = imem[pc[5:2]];
wire [6:0] opcode_d = ifid_instr[6:0];
wire [4:0] rd_d = ifid_instr[11:7];
wire [2:0] funct3_d = ifid_instr[14:12];
wire [4:0] rs1_d = ifid_instr[19:15];
wire [4:0] rs2_d = ifid_instr[24:20];
wire [6:0] funct7_d = ifid_instr[31:25];
reg [31:0] imm_d;
always @(*) case (opcode_d)
7'b0010011,7'b0000011,7'b1100111:
imm_d = {{20{ifid_instr[31]}}, ifid_instr[31:20]};
7'b0100011:
imm_d = {{20{ifid_instr[31]}}, ifid_instr[31:25], ifid_instr[11:7]};
7'b1100011:
imm_d = {{19{ifid_instr[31]}}, ifid_instr[31], ifid_instr[7], ifid_instr[30:25], ifid_instr[11:8], 1'b0};
7'b1101111:
imm_d = {{11{ifid_instr[31]}}, ifid_instr[31], ifid_instr[19:12], ifid_instr[20], ifid_instr[30:21], 1'b0};
default: imm_d = 32'b0;
endcase
wire [31:0] rd1_d = (rs1_d!=0) ? regs[rs1_d] : 32'b0;
wire [31:0] rd2_d = (rs2_d!=0) ? regs[rs2_d] : 32'b0;
reg rw_d, mr_d, mw_d, as_d, m2r_d, br_d;
reg [1:0] ao_d;
always @(*) begin
rw_d=0; mr_d=0; mw_d=0; as_d=0; m2r_d=0; br_d=0; ao_d=2'b00;
case (opcode_d)
7'b0110011: begin rw_d=1; end
7'b0010011: begin rw_d=1; as_d=1; ao_d=2'b01; end
7'b0000011: begin rw_d=1; mr_d=1; as_d=1; m2r_d=1; ao_d=2'b01; end
7'b0100011: begin mw_d=1; as_d=1; ao_d=2'b01; end
7'b1100011: begin br_d=1; ao_d=2'b10; end
endcase
end
wire [31:0] fwd_a = (exmem_reg_write && exmem_rd!=0 && exmem_rd==idex_rs1) ? exmem_alu_result :
(memwb_reg_write && memwb_rd!=0 && memwb_rd==idex_rs1) ? memwb_alu_result :
idex_rs1_data;
wire [31:0] fwd_b = (exmem_reg_write && exmem_rd!=0 && exmem_rd==idex_rs2) ? exmem_alu_result :
(memwb_reg_write && memwb_rd!=0 && memwb_rd==idex_rs2) ? memwb_alu_result :
idex_rs2_data;
wire [31:0] alu_b = idex_alu_src ? idex_imm : fwd_b;
reg [2:0] alu_op;
reg [31:0] alu_result;
always @(*) begin
if (idex_alu_op==2'b00)
alu_op = (idex_funct7[5] && idex_funct3==3'b000) ? 3'b001 : {1'b0, idex_funct3};
else if (idex_alu_op==2'b01) alu_op = 3'b000;
else alu_op = 3'b001;
case (alu_op)
3'b000: alu_result = fwd_a + alu_b;
3'b001: alu_result = fwd_a - alu_b;
3'b010: alu_result = fwd_a & alu_b;
3'b011: alu_result = fwd_a | alu_b;
3'b100: alu_result = fwd_a ^ alu_b;
default: alu_result = 32'b0;
endcase
end
wire alu_zero = (alu_result == 32'b0);
wire branch_taken = idex_branch && alu_zero;
wire stall = idex_mem_read && idex_rd!=0 &&
(idex_rd==rs1_d || idex_rd==rs2_d);
wire pc_stall = stall;
wire ifid_stall = stall;
wire idex_flush = stall | branch_taken;
wire [31:0] pc_next = branch_taken ? (idex_pc + idex_imm) : (pc + 32'd4);
always @(posedge clk or posedge rst) begin
if (rst) pc <= 32'b0;
else if (!pc_stall) pc <= pc_next;
end
always @(posedge clk or posedge rst) begin
if (rst) begin
ifid_instr <= 32'h00000013; ifid_pc <= 32'b0;
idex_pc<=0; idex_rs1_data<=0; idex_rs2_data<=0; idex_imm<=0;
idex_rs1<=0; idex_rs2<=0; idex_rd<=0;
idex_reg_write<=0; idex_mem_read<=0; idex_mem_write<=0;
idex_mem_to_reg<=0; idex_alu_src<=0; idex_branch<=0;
idex_alu_op<=0; idex_funct3<=0; idex_funct7<=0;
exmem_alu_result<=0; exmem_rs2_data<=0; exmem_rd<=0; exmem_pc<=0;
exmem_reg_write<=0; exmem_mem_read<=0; exmem_mem_write<=0;
memwb_alu_result<=0; memwb_mem_rdata<=0; memwb_rd<=0;
memwb_reg_write<=0; memwb_mem_to_reg<=0;
end else begin
if (!ifid_stall) begin
ifid_instr <= instr_f; ifid_pc <= pc;
end
if (idex_flush) begin
idex_pc<=0; idex_rs1_data<=0; idex_rs2_data<=0; idex_imm<=0;
idex_rs1<=0; idex_rs2<=0; idex_rd<=0;
idex_reg_write<=0; idex_mem_read<=0; idex_mem_write<=0;
idex_mem_to_reg<=0; idex_alu_src<=0; idex_branch<=0;
idex_alu_op<=0; idex_funct3<=0; idex_funct7<=0;
end else begin
idex_pc <= ifid_pc; idex_rs1_data <= rd1_d; idex_rs2_data <= rd2_d;
idex_imm <= imm_d; idex_rs1 <= rs1_d; idex_rs2 <= rs2_d; idex_rd <= rd_d;
idex_reg_write<=rw_d; idex_mem_read<=mr_d; idex_mem_write<=mw_d;
idex_mem_to_reg<=m2r_d; idex_alu_src<=as_d; idex_branch<=br_d;
idex_alu_op<=ao_d; idex_funct3<=funct3_d; idex_funct7<=funct7_d;
end
exmem_alu_result <= alu_result; exmem_rs2_data <= fwd_b;
exmem_rd <= idex_rd; exmem_pc <= idex_pc;
exmem_reg_write <= idex_reg_write; exmem_mem_read <= idex_mem_read;
exmem_mem_write <= idex_mem_write;
memwb_alu_result <= exmem_alu_result;
memwb_mem_rdata <= dmem[exmem_alu_result[5:2]];
memwb_rd <= exmem_rd;
memwb_reg_write <= exmem_reg_write; memwb_mem_to_reg <= exmem_mem_to_reg;
end
end
always @(posedge clk) if (exmem_mem_write) dmem[exmem_alu_result[5:2]] <= exmem_rs2_data;
wire [31:0] wb_data = memwb_mem_to_reg ? memwb_mem_rdata : memwb_alu_result;
always @(posedge clk) if (memwb_reg_write && memwb_rd!=0) regs[memwb_rd] <= wb_data;
endmodule
3.1 测试台
module tb_pipeline_cpu;
reg clk, rst;
pipeline_cpu uut (.*);
integer pass=0, fail=0;
always #5 clk = ~clk;
initial begin
clk=0; rst=1;
uut.imem[0] = 32'h00A00093;
uut.imem[1] = 32'h01400113;
uut.imem[2] = 32'h002081B3;
uut.imem[3] = 32'h40120233;
uut.imem[4] = 32'h00302023;
uut.imem[5] = 32'h00002283;
uut.imem[6] = 32'h00528313;
uut.imem[7] = 32'h0020F3B3;
uut.imem[8] = 32'h0020E433;
uut.imem[9] = 32'h00808463;
#12; rst = 0;
repeat(20) #10;
if (uut.regs[1] !== 32'd10) begin $display("FAIL x1=%0d exp=10", uut.regs[1]); fail=fail+1; end else pass=pass+1;
if (uut.regs[2] !== 32'd20) begin $display("FAIL x2=%0d exp=20", uut.regs[2]); fail=fail+1; end else pass=pass+1;
if (uut.regs[3] !== 32'd30) begin $display("FAIL x3=%0d exp=30", uut.regs[3]); fail=fail+1; end else pass=pass+1;
if (uut.regs[4] !== 32'd20) begin $display("FAIL x4=%0d exp=20", uut.regs[4]); fail=fail+1; end else pass=pass+1;
if (uut.regs[5] !== 32'd30) begin $display("FAIL x5=%0d exp=30", uut.regs[5]); fail=fail+1; end else pass=pass+1;
if (uut.regs[6] !== 32'd35) begin $display("FAIL x6=%0d exp=35", uut.regs[6]); fail=fail+1; end else pass=pass+1;
if (uut.regs[7] !== 32'd0) begin $display("FAIL x7=%0d exp=0", uut.regs[7]); fail=fail+1; end else pass=pass+1;
if (uut.regs[8] !== 32'd30) begin $display("FAIL x8=%0d exp=30", uut.regs[8]); fail=fail+1; end else pass=pass+1;
$display("========================================");
$display("流水线CPU测试: PASS=%0d FAIL=%0d", pass, fail);
if (fail == 0) $display("✅ 10条指令流水线执行全部正确!");
else $display("❌ 存在失败!");
$display("========================================");
$finish;
end
endmodule
四、Verilator 编译命令
verilator --cc pipeline_cpu.v --exe tb_pipeline_cpu.v \
--build --top-module tb_pipeline_cpu
./obj_dir/Vtb_pipeline_cpu
五、课程总结
🎓 20 课知识图谱:
基础组件(01-03) → 算术电路(04-05) → 时序逻辑(06-10)
↓ ↓ ↓
逻辑门·布尔·组合 加法器·ALU 触发器·寄存器·计数器·FSM·存储器
↓
指令集(11) → CPU设计(12-14) → 流水线(15-20)
↓ ↓ ↓
RV32I编码 数据通路·控制·单周期 流水线·冒险·前递·停顿·分支预测·流水线CPU
🏆 成就解锁:RISC-V CPU 架构师
✅ Verilator 仿真验证通过
✅ 5 级流水线正确执行
✅ 前递逻辑解决数据冒险
✅ 停顿逻辑解决 Load-Use 冒险
✅ 10 条指令程序执行结果全部正确
🎉 恭喜完成全部 20 课!你已经从逻辑门走到了完整的流水线 CPU!