第18课 · 停顿

停顿气泡Load-Use

📌 学习目标:实现 Load-Use 冒险的停顿逻辑,插入气泡(bubble)使流水线暂停1个周期,通过 Verilator 验证停顿行为正确。

一、为什么需要停顿?

前递能解决大部分数据冒险,但Load-Use 冒险无法仅靠前递解决:

LW  x1, 0(x2)    IF  ID  EX  MEM←数据在这里才出来
ADD x3, x1, x4       IF  ID  EX←需要 x1,但还在 MEM 阶段
                              ↑ 差一个周期!前递也来不及

解决:插入1个气泡(停顿1周期)
LW  x1, 0(x2)    IF  ID  EX  MEM WB
bubble                     --  --  --   ← 气泡
ADD x3, x1, x4       IF  ID  stall EX  MEM WB
                                     ↑ 现在 MEM/WB 可以前递了

二、停顿的实现

停顿需要做三件事:

  1. 冻结 PC:不更新 PC(stall IF 阶段)
  2. 冻结 IF/ID:保持当前指令(stall ID 阶段)
  3. 冲刷 ID/EX:插入气泡(NOP),清除控制信号

三、Load-Use 检测条件

Stall = ID/EX.MemRead && (ID/EX.rd != 0) &&
        ((ID/EX.rd == IF/ID.rs1) || (ID/EX.rd == IF/ID.rs2))
        ↑ 前一条是 Load      ↑ 不写 x0      ↑ 后一条用了 Load 的目标寄存器

四、Verilog 停顿控制

// stall_control.v — 停顿控制单元 module stall_control ( input [4:0] ex_rd, // EX 阶段的 rd input ex_mem_read, // EX 阶段是 Load input [4:0] id_rs1, id_rs2, // ID 阶段的源寄存器 output stall_if, // 停顿 IF(冻结 PC) output stall_id, // 停顿 ID(冻结 IF/ID) output flush_ex // 冲刷 EX(插入气泡) ); // Load-Use 冒险检测 wire load_use = ex_mem_read && (ex_rd != 5'b0) && ((ex_rd == id_rs1) || (ex_rd == id_rs2)); assign stall_if = load_use; assign stall_id = load_use; assign flush_ex = load_use; endmodule

4.1 带停顿的流水线寄存器

// pipe_reg_with_stall.v — 支持停顿的流水线寄存器 module pipe_reg_ifid_stall ( input clk, rst, input stall, // 停顿信号 input [31:0] instr_f, pc_f, output reg [31:0] instr_d, pc_d ); always @(posedge clk or posedge rst) begin if (rst) begin instr_d <= 32'b0; pc_d <= 32'b0; end else if (!stall) begin // 只在不停顿时更新 instr_d <= instr_f; pc_d <= pc_f; end // stall=1 时保持原值 end endmodule module pipe_reg_idex_stall ( input clk, rst, input flush, // 冲刷信号(插入气泡) input [31:0] pc_d, rs1_d, rs2_d, imm_d, input [4:0] rd_d, input reg_write_d, mem_read_d, mem_write_d, output reg [31:0] pc_e, rs1_e, rs2_e, imm_e, output reg [4:0] rd_e, output reg reg_write_e, mem_read_e, mem_write_e ); always @(posedge clk or posedge rst) begin if (rst || flush) begin // 气泡:所有控制信号清零 pc_e <= 32'b0; rs1_e <= 32'b0; rs2_e <= 32'b0; imm_e <= 32'b0; rd_e <= 5'b0; reg_write_e <= 0; mem_read_e <= 0; mem_write_e <= 0; end else begin pc_e <= pc_d; rs1_e <= rs1_d; rs2_e <= rs2_d; imm_e <= imm_d; rd_e <= rd_d; reg_write_e <= reg_write_d; mem_read_e <= mem_read_d; mem_write_e <= mem_write_d; end end endmodule

4.2 测试台

// tb_stall.v module tb_stall; reg [4:0] ex_rd, id_rs1, id_rs2; reg ex_mem_read; wire stall_if, stall_id, flush_ex; stall_control uut (.*); integer pass=0, fail=0; initial begin // 无冒险:不触发停顿 ex_rd=5; id_rs1=3; id_rs2=4; ex_mem_read=1; #1; if (stall_if||stall_id||flush_ex) begin $display("FAIL no hazard"); fail=fail+1; end else pass=pass+1; // Load-Use 冒险(rs1匹配) ex_rd=3; id_rs1=3; id_rs2=4; ex_mem_read=1; #1; if (!stall_if||!stall_id||!flush_ex) begin $display("FAIL load-use rs1"); fail=fail+1; end else pass=pass+1; // Load-Use 冒险(rs2匹配) ex_rd=4; id_rs1=3; id_rs2=4; ex_mem_read=1; #1; if (!stall_if||!stall_id||!flush_ex) begin $display("FAIL load-use rs2"); fail=fail+1; end else pass=pass+1; // 非Load不触发 ex_rd=3; id_rs1=3; id_rs2=4; ex_mem_read=0; #1; if (stall_if||stall_id||flush_ex) begin $display("FAIL non-load"); fail=fail+1; end else pass=pass+1; // x0 不触发 ex_rd=0; id_rs1=0; id_rs2=0; ex_mem_read=1; #1; if (stall_if||stall_id||flush_ex) begin $display("FAIL x0"); fail=fail+1; end else pass=pass+1; $display("========================================"); $display("停顿控制测试: PASS=%0d FAIL=%0d", pass, fail); if (fail == 0) $display("✅ Load-Use 停顿逻辑验证正确!"); else $display("❌ 存在失败!"); $display("========================================"); $finish; end endmodule

五、Verilator 编译命令

verilator --cc stall_control.v pipe_reg_with_stall.v \ --exe tb_stall.v --build --top-module tb_stall ./obj_dir/Vtb_stall

六、停顿的性能影响

每遇到一次 Load-Use 冒险,损失 1 个时钟周期。典型程序中 Load 占约 25%,其中约 20% 会产生 Load-Use 冒险,所以停顿率约 5%。

实际 CPI ≈ 1 + 0.05(Load-Use停顿) + 分支惩罚

🤔 思考题:编译器如何帮助减少 Load-Use 停顿?

💡 提示:指令调度——在 LW 和使用结果之间插入一条不相关的指令

🏆 成就解锁:停顿控制专家

✅ Verilator 仿真验证通过

✅ Load-Use 冒险检测正确

✅ 停顿信号(stall_if/stall_id)验证正确

✅ 气泡插入(flush_ex)验证正确

✅ x0 寄存器特殊处理正确

🎯 下一目标:分支预测 → 第19课:分支预测