DirectedBTB in Gem5
实现基于 v25.1.0.0 的 Gem5 , github repo: https://github.com/xixi-shredp/gem5/tree/directed-btb
1. 概述
DirectedBTB 是对 gem5 Branch Target Buffer (BTB) 子系统的扩展实现,核心思想是在 BTB entry 中嵌入一个轻量级的方向分类器 (direction classifier),用于对 conditional branch 提供 coarse-grained 的 taken/not-taken hint。当 BTB 中的饱和计数器 (saturating counter) 积累了足够的 taken 历史后,DirectedBTB 可以绕过 conditional predictor 直接给出 “taken” 的方向预测,从而在流水线早期阶段 (pipeline front-end) 缩短分支预测延迟。
1.1 设计动机
在多周期流水线分支预测器 (PipelinedBPredUnit) 架构下,conditional predictor 的查询可能需要多个周期才能返回最终结果。对于历史上高度倾向于 taken 的 conditional branch(如循环回边 loop back-edge),BTB 本身已经持有目标地址,如果 BTB 同时能判断该分支”几乎总是 taken”,就可以在第一个周期直接给出方向预测,无需等待 conditional predictor 的结果。
1.2 核心设计原则
- 最小侵入 (minimal intrusion):通过在
BranchTargetBuffer基类中添加带有 no-op 默认实现的 virtual method,确保SimpleBTB和其他现有 BTB 实现完全不受影响 - predictor 状态完整性保护:即使 DirectedBTB 覆盖了方向预测结果,仍然通过
branchPlaceholder()为 conditional predictor 创建 history record,保证后续 commit/squash 路径上的状态恢复正确 - 可参数化 (parameterizable):counter 位宽、初始值、阈值均可通过 Python 配置对象灵活调整
2. 文件清单与变更范围
| 文件 | 变更类型 | 描述 |
|---|---|---|
src/cpu/pred/directed_btb.hh |
新增 | DirectedBTBEntry 和 DirectedBTB 类声明 |
src/cpu/pred/directed_btb.cc |
新增 | DirectedBTBEntry 和 DirectedBTB 完整实现 |
src/cpu/pred/btb.hh |
修改 | 基类新增 lookupDirectionHint() 和 updateDirectionInfo() 两个 virtual method |
src/cpu/pred/BranchPredictor.py |
修改 | 新增 DirectedBTB SimObject 定义及参数 |
src/cpu/pred/SConscript |
修改 | 注册 directed_btb.cc 源文件和 DirectedBTB SimObject |
src/cpu/pred/bpred_unit.cc |
修改 | 单周期 BPredUnit 集成 direction hint 逻辑 |
src/cpu/pred/pipelined_bpred_unit.cc |
修改 | 多周期 PipelinedBPredUnit 集成 direction hint 逻辑 |
tests/pyunit/pred/pyunit_pipelined_bpred_config.py |
新增 | Python 单元测试,验证 DirectedBTB 参数配置 |
3. 类层次结构
1 | ClockedObject |
3.1 BranchTargetBuffer 基类扩展
在 btb.hh 中新增两个 virtual method,均提供 no-op 默认实现:
1 | // 查询 direction hint:对于 conditional branch,返回 BTB 是否建议 taken |
这一设计保证了 SimpleBTB 以及所有未覆写这两个方法的 BTB 子类行为完全不变。
4. DirectedBTBEntry 详细设计
4.1 数据结构
DirectedBTBEntry 继承自 BTBEntry,在基类的 target、inst、valid、tag 之外新增三个字段:
| 字段 | 类型 | 说明 |
|---|---|---|
counterBits |
unsigned |
饱和计数器位宽(构造时确定,取值范围 [1, 8]) |
takenCounter |
SatCounter8 |
饱和计数器,记录该 entry 对应分支 taken 的倾向程度 |
conditional |
bool |
标记该 entry 是否对应 conditional branch |
4.2 关键方法
resetDirectionState(bool is_conditional, unsigned initial_counter)
当一个 entry 被新分配(首次插入或替换旧 entry 后重新使用)时调用。根据分支类型设置 conditional 标志,并将计数器重置为 initialTakenCounter 参数指定的初始值。
1 | void DirectedBTBEntry::resetDirectionState(bool is_conditional, |
updateDirection(bool taken)
在 commit 或 squash 时由 updateDirectionInfo() 调用。仅对 conditional branch 有效:taken 时递增计数器,not-taken 时递减,由 SatCounter8 保证 saturate 语义(不溢出、不下溢)。
directsTaken(unsigned threshold) → bool
查询接口。当且仅当 entry 有效、是 conditional branch、且计数器值 >= directedTakenThreshold 时返回 true,表示 BTB 建议该分支预测为 taken。
replacementCandidate(unsigned threshold) → bool
替换策略辅助接口。当 entry 无效,或者是 conditional branch 且计数器值 < replacementCandidateThreshold 时返回 true。这些 entry 被认为是”弱”entry,在替换时优先被淘汰。
5. DirectedBTB 核心逻辑
5.1 构造与参数校验
构造函数通过 fatal_if 进行严格的参数校验:
numEntries / associativity必须是 2 的幂(set 数量约束)takenCounterBits必须在 [1, 8] 范围内initialTakenCounter、directedTakenThreshold、replacementCandidateThreshold均不得超过计数器最大值(1 << takenCounterBits) - 1
底层存储使用 gem5 的 AssociativeCache<DirectedBTBEntry> 模板,复用 gem5 现有的 indexing policy 和 replacement policy 框架。
5.2 查找路径 (Lookup Path)
lookup(tid, instPC, type) → PCStateBase*
标准 BTB 查找。通过 btb.accessEntry() 查找 entry(同时更新 replacement policy 的 access 信息),返回目标地址。命中/缺失统计由 stats.lookups[type] 和 stats.misses[type] 记录。
lookupDirectionHint(tid, instPC, type) → bool
DirectedBTB 的核心扩展方法:
1 | bool DirectedBTB::lookupDirectionHint(ThreadID tid, Addr instPC, BranchType type) |
注意这里使用 findEntry()(即 btb.findEntry())而非 btb.accessEntry(),不更新 replacement policy 状态,因为同一分支的 lookup() 已经调用过 accessEntry() 了。
分支类型判定通过 isConditionalBranch() 静态方法完成,覆盖 DirectCond 和 IndirectCond 两种 conditional branch 类型。
5.3 更新路径 (Update Path)
update(tid, instPC, target, type, inst)
在 BTB 目标地址更新时调用(由 BPredUnit::updateBTB() 触发):
- 尝试
findEntry()查找现有 entry - 若 miss,调用
findVictim()选择替换目标,插入新 entry,并通过resetDirectionState()初始化 direction 状态 - 调用
btb.accessEntry()更新 replacement policy - 调用
entry->update()写入目标地址和指令指针
关键设计点:只有新分配的 entry 才重置 direction 计数器。已存在的 entry 在更新目标地址时保留其计数器状态,避免目标地址变化时丢失积累的方向统计信息。
updateDirectionInfo(tid, instPC, type, actually_taken)
在 commit 和 squash 路径上均被调用,将分支的实际执行结果反馈给对应 entry 的饱和计数器:
1 | void DirectedBTB::updateDirectionInfo(ThreadID tid, Addr instPC, |
仅对 conditional branch 且 BTB 中存在对应 entry 时才更新。不存在的 entry(如 compulsory miss)不做任何操作。
5.4 替换策略 (Replacement Policy)
findVictim() 实现了一种两层优先级的替换策略:
1 | ┌─────────────────────────────────┐ |
设计意图:保护”有价值”的 entry(counter 较高、表明该分支频繁 taken 的 entry),优先淘汰无效或 counter 低的 entry。当所有 entry 都是”强” entry 时,退化为标准 replacement policy(如 LRU)选择 victim。
6. 预测器集成 (Predictor Integration)
6.1 对 bpred_unit.cc (BPredUnit) 的修改
6.1.1 BTB 查找前移
原始代码中,BTB lookup 在 direction prediction 之后执行。修改后将 BTB lookup 提前到 direction prediction 之前,因为 lookupDirectionHint() 需要已知 BTB hit/miss 状态:
1 | 原始流程: 修改后流程: |
6.1.2 Direction hint 覆盖逻辑
在 predict() 中插入 direction hint 查询:
1 | const bool directed_taken_hint = |
三个条件缺一不可:
!hist->uncond:必须是 conditional branch(unconditional 已经是 taken,无需 hint)hist->btbHit:必须 BTB 命中(miss 则无 entry 可查询 counter)btb->lookupDirectionHint()返回true:counter 达到阈值
当 hint 生效时:
1 | if (directed_taken_hint) { |
关键操作:
- 调用
branchPlaceholder()而非lookup():创建 conditional predictor 的 history record 但不执行真正的预测查询,不影响 predictor 内部状态(如 GHR 等)。参数uncond=false明确告知这是 conditional branch。 - 将
condPred强制设为true(taken)
这确保了当预测错误需要 squash 时,conditional predictor 仍能正确恢复其内部状态。
6.1.3 Commit/Squash 路径更新
在 commitBranch() 和 squash() 中均新增一行:
1 | btb->updateDirectionInfo(tid, hist->pc, hist->type, hist->actuallyTaken); |
确保无论分支最终是 commit 还是被 squash 修正,direction counter 都能收到实际的 taken/not-taken 反馈。
6.2 对 pipelined_bpred_unit.cc (PipelinedBPredUnit) 的修改
与 BPredUnit 的修改逻辑一致,但需要适配多阶段 (multi-stage) 预测器架构:
- BTB lookup 同样前移到 stage prediction 之前
- 当
directed_taken_hint生效时,对每一个 stage predictor 都调用branchPlaceholder():
1 | for (size_t stageIndex = 0; stageIndex < stagePredictors.size(); ++stageIndex) { |
这保证了所有 pipeline stage 的 conditional predictor 都生成了正确的 history record,使得后续 squash 能够逐 stage 恢复。
7. Python 配置接口
7.1 SimObject 定义
DirectedBTB 继承自 BranchTargetBuffer,在 BranchPredictor.py 中定义:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
numEntries |
Unsigned | 4096 | BTB entry 总数 |
tagBits |
Unsigned | 16 | tag 位宽 |
instShiftAmt |
Unsigned | 继承 Parent | 指令地址右移位数(对齐) |
associativity |
Unsigned | 1 | 组相联度(1 = direct-mapped) |
btbReplPolicy |
BaseReplacementPolicy | LRURP | 替换策略 |
btbIndexingPolicy |
BTBIndexingPolicy | BTBSetAssociative | 索引策略 |
takenCounterBits |
Unsigned | 2 | 饱和计数器位宽 |
initialTakenCounter |
Unsigned | 0 | 新 entry 计数器初始值 |
directedTakenThreshold |
Unsigned | 2 | 达到此阈值时 hint 为 taken |
replacementCandidateThreshold |
Unsigned | 1 | 低于此阈值的 entry 是优先替换候选 |
7.2 默认参数分析
以默认 2-bit counter (位宽=2, 取值范围 [0, 3]) 为例:
1 | Counter 值: 0 1 2 3 |
- 新 entry 从
0(Strongly Not-Taken) 开始 - 需要至少连续 2 次 taken 才能达到 threshold
2开始给出 hint - counter 值为
0的 entry 是 weak replacement candidate(低于 threshold1) - counter 值
1,2,3的 entry 受保护,不优先被替换
7.3 使用示例
1 | from m5.objects import DirectedBTB, PipelinedBPredUnit, LTAGE, LocalBP |
8. 数据流与时序
8.1 Predict 阶段完整数据流
1 | ┌──────────────────┐ |
8.2 Commit/Squash 阶段反馈
1 | commit 路径: squash 路径: |
9. 与 SimpleBTB 的对比
| 特性 | SimpleBTB | DirectedBTB |
|---|---|---|
| Entry 类型 | BTBEntry |
DirectedBTBEntry (继承 BTBEntry) |
| 方向信息 | 无 | 每 entry 一个 saturating counter |
lookupDirectionHint() |
基类默认 return false |
基于 counter 阈值判断 |
updateDirectionInfo() |
基类默认 no-op | 更新 counter |
| 替换策略 | 标准 LRU | 两层优先级 (weak candidate + LRU) |
| 存储开销 (每 entry) | target + inst + tag + valid |
同上 + counterBits + takenCounter + conditional |
| 额外存储估算 (每 entry) | — | ~2 bytes (1 byte SatCounter8 + 1 bit conditional + padding) |
10. 统计量 (Statistics)
DirectedBTB 复用基类 BranchTargetBufferStats 中已有的统计量:
| 统计量 | 类型 | 说明 |
|---|---|---|
stats.lookups[type] |
Vector | 按 BranchType 分类的 BTB 查找次数 |
stats.misses[type] |
Vector | 按 BranchType 分类的 BTB 缺失次数 |
stats.updates[type] |
Vector | 按 BranchType 分类的 BTB 更新次数 |
stats.mispredict[type] |
Vector | 按 BranchType 分类的错误预测次数 |
stats.evictions |
Scalar | BTB entry 被替换淘汰的次数 |
注意:当前实现中 DirectedBTB 未新增专门的 direction hint 相关统计量(如 hint 触发次数、hint 准确率等)。这些信息可通过后续扩展添加。
11. 构建集成
11.1 SConscript
Source('directed_btb.cc'):注册编译单元SimObject列表中添加'DirectedBTB':注册 Python SimObject
11.2 依赖关系
DirectedBTB 依赖以下 gem5 基础设施:
| 依赖 | 头文件 | 用途 |
|---|---|---|
AssociativeCache |
base/cache/associative_cache.hh |
组相联缓存容器 |
SatCounter8 |
base/sat_counter.hh |
8-bit 饱和计数器 |
BTBEntry |
cpu/pred/btb_entry.hh |
基础 BTB entry |
BranchTargetBuffer |
cpu/pred/btb.hh |
BTB 基类 |
replacement_policy::Base |
mem/cache/replacement_policies/base.hh |
可插拔替换策略 |
BTBIndexingPolicy |
mem/cache/tags/indexing_policies/base.hh |
可插拔索引策略 |
12. 架构决策与 Trade-off 分析
12.1 为什么在 BTB 中嵌入 direction classifier 而非独立组件
选择:将 direction counter 作为 BTB entry 的扩展字段。
理由:
- BTB entry 的生命周期管理(allocation、eviction、invalidation)天然与该分支的”存在”绑定;独立组件需要额外同步
- 在 BTB lookup 的同一拍内即可获取 direction hint,无额外的延迟开销
- 利用 BTB entry 的 valid bit 和 tag matching 机制,无需单独的 tag 存储
代价:
- DirectedBTB entry 的存储开销略大于 SimpleBTB
- DirectedBTB 与 BTB 容量耦合——高 associativity 的大 BTB 才能充分发挥 direction classifier 的效果
12.2 branchPlaceholder vs. 忽略 conditional predictor
选择:即使 direction hint 覆盖了预测结果,仍调用 branchPlaceholder() 为 conditional predictor 创建 history。
理由:
- conditional predictor(如 TAGE、Perceptron)通常维护 speculative history(GHR、path history 等)。跳过 history 创建会导致后续 squash 时无法正确恢复
branchPlaceholder()已经是 gem5 中用于”BTB miss 但 pre-decode 检测到分支”场景的既有模式,语义明确- 保证了 predictor training 在 commit 路径上仍然正常工作
代价:
- 每次 direction hint 覆盖时仍有一次
branchPlaceholder()调用的开销(但该调用本身设计为轻量级)
12.3 两层优先级替换 vs. 纯 LRU
选择:优先淘汰 counter 低于 replacementCandidateThreshold 的 entry。
理由:
- 高 counter 值的 entry 意味着该分支频繁 taken,direction hint 价值高,应尽量保留
- 低 counter 值的 entry 贡献的 direction hint 较少,替换它们的损失较小
- 当所有 entry 的 counter 都高于阈值时,自动退化为标准 replacement policy,不会造成死锁
代价:
- 替换路径上需要两轮遍历(收集 weak candidates + 选择 victim),略增加
findVictim()的计算开销 - 在 set 较大(高 associativity)时,遍历成本线性增长
13. 潜在改进方向
- Direction hint 统计量:新增
directionHints、directionHitRate等统计量,便于性能分析 - Hysteresis 初始值策略:考虑根据分支类型(如 loop back-edge vs. if-else)设置不同的
initialTakenCounter - Adaptive threshold:根据运行时 misprediction rate 动态调整
directedTakenThreshold - 与 multi-stage predictor 的深度集成:当前 direction hint 完全绕过所有 stage;可以考虑仅绕过 slow stage 而保留 fast stage 的精确预测
- C++ 单元测试补充:为
DirectedBTBEntry和DirectedBTB补充 GTest,覆盖 counter 边界行为和替换策略逻辑