第19课 · 分支预测

分支预测静态预测延迟槽BTB

📌 学习目标:实现静态分支预测(不跳转/跳转)和 2-bit 动态预测器,理解延迟槽机制,通过 Verilator 验证预测逻辑。

一、控制冒险的代价

分支指令在流水线中产生控制冒险。如果不做预测,每个分支损失 2-3 个周期。

BEQ x1, x2, target   IF  ID  EX(确定是否跳转) MEM WB
instr+4               IF  ← 如果跳转,这条白取了
instr+8                    IF  ← 这条也白取了
target: ...                     IF  ← 正确的指令

→ 分支惩罚 = 2 周期(从 EX 到 IF 的延迟)

二、静态预测

静态预测不依赖运行时信息,策略固定:

2.1 预测不跳转(Predict Not Taken)

默认顺序执行。如果实际跳转则冲刷流水线,损失 = 分支惩罚。

准确率:约 50-60%(对于前向条件分支更差)

2.2 预测跳转(Predict Taken)

默认跳转到目标。如果实际不跳转则冲刷。

准确率:对于后向分支(循环)较好

2.3 BTFN 预测

Backward Taken, Forward Not Taken:后向分支(循环)预测跳转,前向分支(if-else)预测不跳转。

准确率:约 70-80%

三、2-bit 动态预测

2-bit 饱和计数器是最经典的动态预测器:

状态编码预测
强不跳转00不跳转
弱不跳转01不跳转
弱跳转10跳转
强跳转11跳转

状态转换规则:

准确率:约 85-90%

四、分支延迟槽

RISC-V 最初版本支持延迟槽,但在 ISA 2.0 中已废弃。但理解其原理仍有价值。

BEQ x1, x2, target   IF  ID  EX  MEM WB
NOP (延迟槽)           IF  ID  EX  MEM WB ← 总是执行
target: ADD ...               IF  ID  EX  MEM WB

→ 延迟槽内的指令无论跳不跳转都执行
→ 编译器负责把有用指令放入延迟槽
→ 消除分支惩罚(代价:编译器复杂度+ISA兼容性)

五、Verilog 实现

// branch_predict.v — 分支预测器 // 2-bit 饱和计数器预测器 module predict_2bit ( input clk, rst, input branch_taken, // 实际是否跳转 input update, // 更新使能 output predict_taken // 预测是否跳转 ); reg [1:0] counter; assign predict_taken = counter[1]; // MSB 决定预测方向 always @(posedge clk or posedge rst) begin if (rst) counter <= 2'b01; // 弱不跳转 else if (update) begin if (branch_taken && counter != 2'b11) counter <= counter + 1; else if (!branch_taken && counter != 2'b00) counter <= counter - 1; end end endmodule // 静态预测器 module predict_static ( input [31:0] branch_target, input [31:0] current_pc, output predict_taken ); // BTFN: 后向分支预测跳转,前向预测不跳转 assign predict_taken = ($signed(branch_target) < $signed(current_pc)); endmodule // 分支目标缓冲 (BTB) module btb #(parameter ENTRIES = 16) ( input clk, rst, input [31:0] pc, input update, input [31:0] target_pc, output hit, output [31:0] predicted_pc ); reg [31:0] targets [0:ENTRIES-1]; reg valid [0:ENTRIES-1]; wire [3:0] idx = pc[5:2]; assign hit = valid[idx]; assign predicted_pc = targets[idx]; always @(posedge clk) begin if (update) begin targets[idx] <= target_pc; valid[idx] <= 1'b1; end end integer i; initial begin for (i = 0; i < ENTRIES; i = i + 1) valid[i] = 1'b0; end endmodule

5.1 测试台

// tb_branch_predict.v module tb_branch_predict; reg clk, rst; reg branch_taken, update; wire predict_taken; predict_2bit u_pred (.*); integer pass=0, fail=0; always #5 clk = ~clk; initial begin clk=0; rst=1; branch_taken=0; update=0; #12; rst=0; // 初始状态:弱不跳转(01)→预测不跳转 if (predict_taken !== 0) begin $display("FAIL initial"); fail=fail+1; end else pass=pass+1; // 连续两次跳转 → 强跳转(11) update=1; branch_taken=1; #10; // 01→10 (弱跳转) if (predict_taken !== 1) begin $display("FAIL after 1 taken"); fail=fail+1; end else pass=pass+1; #10; // 10→11 (强跳转) if (predict_taken !== 1) begin $display("FAIL strong taken"); fail=fail+1; end else pass=pass+1; // 一次不跳转不会改变预测方向(11→10) branch_taken=0; #10; if (predict_taken !== 1) begin $display("FAIL 1 miss: still predict taken"); fail=fail+1; end else pass=pass+1; // 第二次不跳转(10→01)→预测变不跳转 #10; if (predict_taken !== 0) begin $display("FAIL 2 miss: predict not taken"); fail=fail+1; end else pass=pass+1; $display("========================================"); $display("分支预测测试: PASS=%0d FAIL=%0d", pass, fail); if (fail == 0) $display("✅ 静态预测+2-bit动态预测验证正确!"); else $display("❌ 存在失败!"); $display("========================================"); $finish; end endmodule

六、Verilator 编译命令

verilator --cc branch_predict.v --exe tb_branch_predict.v \ --build --top-module tb_branch_predict ./obj_dir/Vtb_branch_predict

七、预测器对比

预测器准确率硬件开销适用场景
总是不跳转50-60%0简单流水线
BTFN 静态70-80%极少嵌入式
2-bit 动态85-90%中等流水线
2-level 自适应90-95%高性能
神经网络/TAGE95-97%超标量

🤔 思考题:为什么 2-bit 预测器比 1-bit 预测器对循环更友好?

💡 提示:1-bit 预测器在循环末尾(一次不跳转→退出)后,下次循环第一次迭代预测错误;2-bit 需要连续两次错误才改变方向

🏆 成就解锁:分支预测专家

✅ Verilator 仿真验证通过

✅ 静态 BTFN 预测逻辑正确

✅ 2-bit 动态预测状态转换正确

✅ 延迟槽机制理解

✅ BTB 分支目标缓冲设计

🎯 下一目标:完整流水线 CPU → 第20课:流水线CPU