前递无法消除的最后1周期停顿
上一课的前递可以解决ALU指令之间的RAW冒险,但Load指令是特殊情况:
Load-Use是流水线中唯一需要硬件停顿的数据冒险。其他RAW冒险都可以通过前递零停顿解决。这个1周期停顿在现代处理器中约占所有停顿的10-20%。
| 冒险类型 | 前递可解决? | 停顿周期 | 频率 |
|---|---|---|---|
| ALU→ALU (EX→EX) | ✅ 是 | 0 | ~30% |
| ALU→ALU (MEM→EX) | ✅ 是 | 0 | ~20% |
| Load→ALU (Load-Use) | ❌ 否 | 1 | ~15% |
| 无冒险 | — | 0 | ~35% |
Load-Use冒险的检测条件比普通前递更严格:
// Lesson 34: Load-Use Hazard Detection + 1-cycle Stall
module load_use_hazard #(
parameter DATA_W = 32
)(
input wire [4:0] id_rs1_i, id_rs2_i,
input wire id_valid_i,
input wire [4:0] ex_rd_i,
input wire ex_memread_i, // EX阶段是Load
input wire ex_valid_i,
output reg stall_o, // 1周期停顿
output reg flush_id_o, // 清空ID
output reg flush_ex_o // 清空EX(气泡)
);
always @(*) begin
stall_o = 0; flush_id_o = 0; flush_ex_o = 0;
if (ex_valid_i && ex_memread_i && id_valid_i) begin
if ((ex_rd_i == id_rs1_i && ex_rd_i != 5'd0) ||
(ex_rd_i == id_rs2_i && ex_rd_i != 5'd0)) begin
stall_o = 1; // 停顿取指和ID
flush_id_o = 1; // 阻止ID推进到EX
flush_ex_o = 1; // EX插入气泡
end
end
end
endmodule
| 技术 | 原理 | 效果 | 复杂度 |
|---|---|---|---|
| 编译器调度 | 在Load-Use之间插入无关指令 | 消除50-70% | 低 |
| Load-Use前递 | MEM阶段前半周期读内存,后半周期前递 | 消除100% | 中 |
| 提前读(Early Read) | 将内存读提前到EX阶段末尾 | 消除100% | 高 |
| 乱序执行 | 后续无关指令先执行,Load结果出来后再执行依赖指令 | 消除100% | 很高 |
在现代高性能处理器中,Load-Use停顿通过快速缓存访问和乱序执行几乎完全消除。但在简单的顺序流水线中,1周期停顿是最实用的方案。