第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 | 跳转 |
状态转换规则:
- 实际跳转:计数器 +1(上限 11)
- 实际不跳转:计数器 -1(下限 00)
- 需要连续两次预测错误才改变预测方向
准确率:约 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 实现
module predict_2bit (
input clk, rst,
input branch_taken,
input update,
output predict_taken
);
reg [1:0] counter;
assign predict_taken = counter[1];
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
);
assign predict_taken = ($signed(branch_target) < $signed(current_pc));
endmodule
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 测试台
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;
if (predict_taken !== 0) begin $display("FAIL initial"); fail=fail+1; end else pass=pass+1;
update=1; branch_taken=1; #10;
if (predict_taken !== 1) begin $display("FAIL after 1 taken"); fail=fail+1; end else pass=pass+1;
#10;
if (predict_taken !== 1) begin $display("FAIL strong taken"); fail=fail+1; end else pass=pass+1;
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;
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% | 中 | 高性能 |
| 神经网络/TAGE | 95-97% | 大 | 超标量 |
🤔 思考题:为什么 2-bit 预测器比 1-bit 预测器对循环更友好?
💡 提示:1-bit 预测器在循环末尾(一次不跳转→退出)后,下次循环第一次迭代预测错误;2-bit 需要连续两次错误才改变方向
🏆 成就解锁:分支预测专家
✅ Verilator 仿真验证通过
✅ 静态 BTFN 预测逻辑正确
✅ 2-bit 动态预测状态转换正确
✅ 延迟槽机制理解
✅ BTB 分支目标缓冲设计
🎯 下一目标:完整流水线 CPU → 第20课:流水线CPU