第17课 · 前递
前递转发EX/MEMMEM/WB
📌 学习目标:实现完整的前递(Forwarding/Bypassing)逻辑,将 EX/MEM 和 MEM/WB 阶段的 ALU 结果直接前递到 EX 阶段的 ALU 输入,通过 Verilator 验证前递正确性。
一、前递原理
前递(也称转发/旁路)是解决数据冒险的主要方法。核心思想:不等数据写回寄存器文件,直接从流水线中间阶段获取结果。
无前递:
ADD x1, x2, x3 IF ID EX MEM WB
SUB x4, x1, x5 IF ID stall stall EX MEM WB
↑ 等待 x1 写回
有前递:
ADD x1, x2, x3 IF ID EX──→MEM WB
SUB x4, x1, x5 IF ID──→EX MEM WB
↑ 从 EX/MEM 直接获取 x1
二、前递场景分析
2.1 EX/MEM 前递(1条指令间隔)
ADD x1, x2, x3 ← 结果在 EX/MEM 寄存器中
SUB x4, x1, x5 ← 需要 x1,从 EX/MEM 前递
前递条件:EX/MEM.RegWrite && EX/MEM.rd != 0 &&
EX/MEM.rd == ID/EX.rs1
2.2 MEM/WB 前递(2条指令间隔)
ADD x1, x2, x3 ← 结果在 MEM/WB 寄存器中
OR x6, x7, x8 ← 不影响 x1
SUB x4, x1, x5 ← 需要 x1,从 MEM/WB 前递
前递条件:MEM/WB.RegWrite && MEM/WB.rd != 0 &&
MEM/WB.rd == ID/EX.rs1 &&
!(EX/MEM.RegWrite && EX/MEM.rd != 0 && EX/MEM.rd == ID/EX.rs1)
↑ 注意:EX/MEM 优先级更高!
2.3 前递优先级
当 EX/MEM 和 MEM/WB 都有匹配的前递时,EX/MEM 优先(因为它是最新的结果)。
三、Verilog 前递实现
module forwarding (
input [4:0] ex_rs1, ex_rs2,
input [4:0] mem_rd,
input mem_reg_write,
input [31:0] mem_alu_result,
input [4:0] wb_rd,
input wb_reg_write,
input [31:0] wb_result,
input [31:0] rs1_data, rs2_data,
output [31:0] fwd_a, fwd_b
);
reg [31:0] fwd_a_r;
assign fwd_a = fwd_a_r;
always @(*) begin
if (mem_reg_write && mem_rd != 5'b0 && mem_rd == ex_rs1)
fwd_a_r = mem_alu_result;
else if (wb_reg_write && wb_rd != 5'b0 && wb_rd == ex_rs1)
fwd_a_r = wb_result;
else
fwd_a_r = rs1_data;
end
reg [31:0] fwd_b_r;
assign fwd_b = fwd_b_r;
always @(*) begin
if (mem_reg_write && mem_rd != 5'b0 && mem_rd == ex_rs2)
fwd_b_r = mem_alu_result;
else if (wb_reg_write && wb_rd != 5'b0 && wb_rd == ex_rs2)
fwd_b_r = wb_result;
else
fwd_b_r = rs2_data;
end
endmodule
3.1 测试台
module tb_forwarding;
reg [4:0] ex_rs1, ex_rs2, mem_rd, wb_rd;
reg mem_reg_write, wb_reg_write;
reg [31:0] mem_alu_result, wb_result, rs1_data, rs2_data;
wire [31:0] fwd_a, fwd_b;
forwarding uut (.*);
integer pass=0, fail=0;
initial begin
ex_rs1=1; ex_rs2=2; mem_rd=3; wb_rd=4;
mem_reg_write=0; wb_reg_write=0;
rs1_data=32'd100; rs2_data=32'd200; #1;
if (fwd_a!==32'd100||fwd_b!==32'd200) begin $display("FAIL no fwd"); fail=fail+1; end else pass=pass+1;
ex_rs1=3; mem_rd=3; mem_reg_write=1;
mem_alu_result=32'd42; #1;
if (fwd_a!==32'd42) begin $display("FAIL EX/MEM fwd_a: got %d", fwd_a); fail=fail+1; end else pass=pass+1;
ex_rs2=4; wb_rd=4; wb_reg_write=1;
mem_rd=5; mem_reg_write=0;
wb_result=32'd99; #1;
if (fwd_b!==32'd99) begin $display("FAIL MEM/WB fwd_b: got %d", fwd_b); fail=fail+1; end else pass=pass+1;
ex_rs1=3; mem_rd=3; mem_reg_write=1;
wb_rd=3; wb_reg_write=1;
mem_alu_result=32'd111; wb_result=32'd222; #1;
if (fwd_a!==32'd111) begin $display("FAIL priority: got %d", fwd_a); fail=fail+1; end else pass=pass+1;
$display("========================================");
$display("前递测试: PASS=%0d FAIL=%0d", pass, fail);
if (fail == 0) $display("✅ EX/MEM 前递逻辑验证正确!");
else $display("❌ 存在失败!");
$display("========================================");
$finish;
end
endmodule
四、Verilator 编译命令
verilator --cc forwarding.v --exe tb_forwarding.v \
--build --top-module tb_forwarding
./obj_dir/Vtb_forwarding
五、前递 vs 停顿
| 场景 | 解决方法 | 性能损失 |
| ALU→ALU (1条间隔) | EX/MEM前递 | 0 周期 |
| ALU→ALU (2条间隔) | MEM/WB前递 | 0 周期 |
| Load→ALU (紧跟) | 停顿1周期+前递 | 1 周期 |
| Load→Store (紧跟) | 停顿1周期+前递 | 1 周期 |
前递消除了大部分数据冒险的停顿,但 Load-Use 冒险仍需1个周期停顿。
🤔 思考题:为什么前递不能解决 Load-Use 冒险?LW 的数据最早在哪个阶段才可用?
💡 提示:LW 在 MEM 阶段末尾才读出数据,但下条指令在 EX 阶段就需要,差一个周期
🏆 成就解锁:前递工程师
✅ Verilator 仿真验证通过
✅ EX/MEM 前递路径正确
✅ MEM/WB 前递路径正确
✅ 前递优先级(EX/MEM > MEM/WB)正确
🎯 下一目标:停顿 → 第18课:停顿