Gem5: Fetch Target Queue(FTQ)
当前 O3 前端里的 FTQ 的定位是:
BAC(Branch Address Calculation) 生成FetchTarget后,用于在BAC与Fetch之间传递取指目标的队列。FetchTarget的承载体,用来描述一段连续的 fetch 范围,以及该范围退出点的预测信息。- 一个临时托管
BPU PredictorHistory的结构,用于桥接“预测发生时刻”与“预译码拿到真实 branch/seqNum 时刻”之间的时间差。
当前实现中:
BAC是FTQ的生产者。Fetch是FTQ的消费者。BPU是预测与恢复逻辑的真正拥有者。FTQ是FetchTarget和尚未正式入账的PredictorHistory的过渡驻留点。
与 FTQ 直接相关的文件是:
ftq.hh/cc:定义FetchTarget与FTQ。bac.hh/cc:负责生成FetchTarget、把它放入 FTQ、在 fetch 阶段把历史迁回 BPU。fetch.cc:负责消费 FTQ 头部的FetchTarget。
前端组织方式
BAC 被拆成两部分
BAC 的职责:
- Branch prediction / FTQ feeding 部分
- 在 decoupled frontend 模式下作为一个独立活动阶段运行。
- 负责利用
BTB扫描地址流、发现可能的 branch、调用 BPU 产生预测,并构造FetchTarget插入FTQ
- PC update / predecode 协调部分
- 与
Fetch紧耦合 Fetch每预译码一条指令,就调用BAC::updatePC()决定下一条 PC- 在 decoupled 模式下,真正把
FTQ里暂存的预测历史迁回 BPU,也发生在这里
- 与
源码注释也明确指出,这种组织方式并不完美,但目的是把 decoupling 逻辑主要收敛在
BAC内,而不是进一步复杂化Fetch
Coupled frontend 与 decoupled frontend 的差异
当前代码同时支持两种模式:
Coupled frontend
Fetch在预译码到 branch 时,直接调用bpu->predict(...)。- 不需要
FTQ介入预测流程。 - 预测和
Fetch更同步。
Decoupled frontend
BAC提前基于BTB扫描地址流,先生成FetchTarget。Fetch后续只按 FTQ 头项取指。Fetch在预译码到 branch 后,不再重新做一次预测,而是从FTQ里取出已生成的预测历史并“正式入账”。
本文重点讨论的是第二种,也就是 FTQ 真正参与的模式。
5. FetchTarget 的数据模型
5.1 FetchTarget 的含义
FetchTarget 可以近似理解成一个“前端预测驱动的连续取指块”。它和传统基本块 basic block 有相似性,但并不完全等价:
- 它有一个
startPC和一个endPC。 - 它通常在一个 branch 处结束。
- 它也可能不以 branch 结束,而只是因为达到了允许的最大搜索宽度。
- 它还可能包含
surprise branch,也就是 BAC 当时看不到、只能在后续 predecode 才发现的 branch。
5.2 FetchTarget 的关键字段
FetchTarget 核心包含以下信息:
startPC- 本 fetch target 的起始地址。
endPC- 本 fetch target 的结束地址,对应退出 instruction 的 PC。
predPC- 如果退出 instruction 是 branch,则这是该退出 branch 对应的预测目标地址。
- 如果退出 instruction 不是 branch,那么这个字段仍会在
finalize()时被填入“下一个 FT 的起点”。
ftSeqNumFetchTarget自己的编号。- 注意这不是指令的
seqNum,因为 FT 是在真正生成动态指令之前就构建出来的。
tid- 所属线程。
is_branch- 退出 instruction 是否是 branch。
taken- 退出 branch 是否预测为 taken。
bpuHistory- 最关键字段。
- 这是一个
BPredUnit::PredictorHistory *,用于在 FT 等待于 FTQ 期间暂存该 fetch target 对应的分支预测历史。
5.3 为什么 bpuHistory 需要挂在 FetchTarget 上
因为在 decoupled frontend 中,预测发生得更早,而真正动态指令的 seqNum 更晚才出现。
在 gem5 当前实现里:
- BPU 的正式历史管理以指令序号
seqNum为核心。 - 但 BAC 构造 FT 的那一刻,还没有真实的动态指令,自然拿不到最终
seqNum。
于是实现采用了一个过渡策略:
- 在 BAC 生成 FT 并调用
bpu->predict(...)时,先拿到一份PredictorHistory。 - 这份历史不立即放进 BPU 的
predHist正式队列。 - 它先挂到
ft->bpuHistory上,在 FTQ 中临时保存。 - 等
Fetch真正预译码到那个 branch,拿到动态指令和真实seqNum后,再把这份历史转移回 BPU 主历史队列。
这个设计是整个 FTQ/BPU 协同机制的核心。
6. FTQ 的职责与状态
6.1 FTQ 的基本职责
FTQ 本身非常“瘦”。
它不负责:
- 生成预测。
- 更新预测器状态。
- 计算恢复后的历史。
- 解释 branch 类型。
它主要负责:
- 存放
FetchTarget。 - 提供头尾插入和弹出。
- 提供一些状态控制接口,如
invalidate()、lock()。 - 在 squash 时清空队列。
- 为外部逻辑提供遍历接口,以便 BAC 回滚 FTQ 中仍未正式转入 BPU 的历史。
6.2 FTQ 的状态
当前实现中,FTQ 具有如下状态:
Valid- 可以安全读取和消费队列。
Invalid- 当前队列中的 fetch target 不再可信。
- 进入该状态后,需要依靠
resteer/squash来恢复。
Locked- 头部 fetch target 仍然可用,但其后的项不再可信。
- 常出现在复杂指令或多 branch micro-op 的保守处理路径中。
代码里还有 Full 相关概念,但严格来说“是否已满”主要通过 size >= numEntries 计算,不是一个单独用于外部语义判断的常驻状态机值;BAC 侧会据此进入 FTQFull 阻塞状态。
6.3 FTQ 的几个关键接口
insert()
- 将
FetchTarget压入队尾。 - 由
BAC调用。
readHead()
- 返回当前头部
FetchTarget。 - 如果
FTQ已Invalid或队列为空,则返回nullptr。
isHeadReady()
- 只有在队列非空且不为
Invalid时才为真。 Fetch在 decoupled 模式下会依赖这个条件来判断能否继续取指。
popHead()
- 消费当前 FTQ 头项。
- 这是一个非常关键的保护点。
- 如果头项仍然携带
bpuHistory,则弹出失败,并将 FTQ 置为Invalid。
这等价于一个重要不变量:
任意
FetchTarget在被真正弹出 FTQ 之前,挂在它上面的bpuHistory必须已经被转移走,或者在 squash 路径中被清空。
squash()
- 清空队列。
- 但它内部假设所有
FetchTarget上的bpuHistory已经先被外部清掉。 - 因此正常顺序是:
- BAC 先调用
squashBpuHistories()倒序回滚历史。 - 再调用
ftq->squash()清队列。
- BAC 先调用
7. BAC 如何生成 FetchTarget 并填充 FTQ
7.1 输入:BAC 维护自己的前端 PC
在 decoupled frontend 中,BAC 维护自己的 bacPC。
这个 PC 不是 Fetch 正在使用的那个 fetch PC,而是“预测生产侧”的 PC。
也就是说:
Fetch消费 FTQ 中已有的 fetch targets。BAC则在后台持续尝试生成后续 fetch targets。
7.2 搜索 branch 的方式
BAC::generateFetchTargets() 的基本策略是:
- 从当前
bacPC开始。 - 逐地址检查
BTB是否命中。 - 如果未命中,则继续向前扫描,直到:
- 找到 branch,或
- 扫描宽度达到
fetchTargetWidth。
这里的一个关键设计选择是:
BAC并不依赖预译码来识别 branch,而是纯粹依赖BTB来发现“这里看起来有 branch”。
这意味着:
- 只有进入过
BTB的 branch,BAC 才能在生成 FT 时看到。 never-taken branch、首次遇到的 branch、被 BTB 驱逐的 branch,都可能在 BAC 阶段完全不可见。
7.3 找到 branch 后如何预测
一旦 BTB 命中:
BAC通过BTBGetInst()取出对应的StaticInst。- 调用
BAC::predict()。 BAC::predict()内部进一步调用bpu->predict(...)。- BPU 返回预测方向,同时更新传入的
pc为预测目标或 fallthrough。 - BPU 生成的
PredictorHistory不进入 BPU 正式历史队列,而是直接挂到ft->bpuHistory。
7.4 完成 FetchTarget
无论是否发现 branch,BAC 都会调用 curFT->finalize(...),设置:
endPCis_branchtakenpredPC
随后将该 FT 插入 FTQ。
7.5 下一 FT 的起点如何确定
如果当前 branch 预测为 taken:
- 下一个 FT 的起点就是预测目标
predPC。
如果当前 branch 预测为 not-taken,或根本没有 branch:
- 下一个 FT 的起点就是顺序地址。
也就是说,FTQ 中的每个 FT 实际上隐含构成了一条由 BPU 提前规划出来的 fetch 路径。
8. Fetch 如何消费 FTQ
8.1 Fetch 只处理头部 FT
Fetch 在 decoupled 模式下,首先会检查 FTQ 是否 ready:
- 若 FTQ 为空或无效,则
Fetch进入FtqWait。 - 若 FTQ 可用,则读取头部
FetchTarget。
8.2 当前 PC 必须落在当前 FT 的范围内
Fetch 会验证:
- 当前
fetch PC是否在curFT->inRange(...)范围内。
如果不在范围内,说明前端状态与 FTQ 内容已经不同步,需要触发 bacResteer() 进行恢复。
这说明 FTQ 不只是一个“建议列表”,而是当前前端取指路径的强约束。
8.3 Fetch 在 FT 范围内顺序取指
只要当前 PC 仍然位于 curFT 内:
Fetch就继续从 I-cache / decoder 中取出指令。- 每生成一条
DynInst,都调用bac->updatePC(instruction, next_pc, curFT)。
这时 FTQ 头项相当于给 Fetch 提供了一个“当前有效取指段”的边界条件。
9. Fetch 预译码到 branch 后,历史如何从 FTQ 转回 BPU
这是当前设计中最重要、也最容易误解的一步。
9.1 预译码到 control instruction 时
当 Fetch 发现当前 instruction 是 control instruction:
- 在 coupled 模式下,直接在这里调用
bpu->predict(...)。 - 在 decoupled 模式下,不再重新预测,而是调用
BAC::updatePreDecode(...)。
9.2 updatePreDecode 的基本动作
updatePreDecode() 会做以下事情:
情况 A:这是当前 FT 的 exit branch,且 FT 上有 bpuHistory
这说明:
- BAC 之前已经对这个 branch 做过预测。
- 现在只是到了真正的预译码阶段,需要把历史正式转入 BPU。
流程是:
- 将
ft->bpuHistory取出。 - 给这份 history 填入真实
seqNum。 - 调用
bpu->insertPredictorHistory(...)插入 BPU 主历史队列。 - 按 history 中记录的预测结果更新
fetch PC。
这一步实际上完成了“临时历史 -> 正式历史”的生命周期切换。
情况 B:FT 上没有对应 history
这通常意味着:
- BAC 生成 FT 时根本没发现这个 branch。
- 常见原因是
BTB miss、首次出现、从未 taken、或者 BTB entry 被替换。
此时当前实现不会立即认为这是错误,而是采用一个保守恢复机制:
- 新建一个
PredictorHistory。 - 调用
bpu->branchPlaceholder(...)构造 predictor-specific 的占位历史。 - 假设该 branch 的预测是 not-taken。
- 先把这份 dummy history 插入 BPU 正式历史体系。
- 如果后续 decode/commit 发现真实情况不是这样,再依赖 squash 路径纠正。
这一策略非常关键,因为它保证了即使 BAC 事先没看到 branch,前端和预测器历史仍能在之后恢复到一致状态。
9.3 为什么不能在 BAC 预测时直接插入 BPU 主历史
原因主要有三个:
当时没有真实
seqNum- BPU 主历史以动态指令序号管理 in-flight branch。
当时 branch 类型信息可能不够稳定
- 特别是复杂 instruction / micro-op 场景下,单纯依赖 BTB 中保存的静态信息可能还不够安全。
需要允许“预测已发生,但尚未被真正消费”
- FTQ 本质上就是为了支持这种 decoupling。
10. FetchTarget 何时被弹出
10.1 一个 FT 不一定以 branch 结束
当前实现中,一个 FT 结束的条件可以是:
- 找到 branch。
- 达到最大搜索宽度。
- 在复杂 micro-op 场景下触发特殊处理后被保守截断。
因此 FT 的退出 instruction 不一定是 branch。
10.2 何时认为当前 FT 已被消费完成
在 decoupled 模式下,如果满足以下任一条件,当前 ft 指针会被置空:
- 当前 instruction 是 FT 的 exit instruction,且若是 micro-op 则必须是最后一个 micro-op。
- FTQ 已变为 not ready。
一旦当前 FT 被置空,Fetch 就尝试:
ftq->popHead()- 若成功,读取下一个 FT
- 若失败,则认为状态异常并触发
bacResteer()
10.3 popHead 为什么可能失败
popHead() 失败的核心条件是:
- 头部
FetchTarget上仍残留bpuHistory
这意味着某个关键步骤没有按预期发生,例如:
- exit branch 的 history 没有在
updatePreDecode()中成功取走。 - 或者发生了复杂 corner case,导致 FT 被消费结束,但对应分支历史还没有完成迁移。
当前实现对这种情况的态度非常保守:
- 不尝试在
popHead()时“补救性处理”历史。 - 而是直接把 FTQ 置
Invalid,让外层走 squash/resteer 恢复。
这是一个明确的安全优先选择。
11. FTQ 与 BPU 的真实边界
11.1 BPU 负责什么
BPredUnit 负责:
- 执行方向预测。
- 结合
BTB/RAS/ indirect predictor 得到目标。 - 创建和维护
PredictorHistory。 - 在 commit 进行训练更新。
- 在 decode/commit/fetch squash 时恢复历史。
11.2 FTQ 负责什么
FTQ 不负责预测语义本身,只负责:
- 暂存未来要给
Fetch使用的取指目标。 - 暂存还没“正式编号”的
PredictorHistory。
因此可以把两者关系概括为:
BPU是 owner of predictor stateFTQ是 temporary carrier of prediction products
11.3 为什么要有这种边界
这是 decoupled frontend 的直接代价。
一旦希望:
- 让预测早于真实 fetch/predecode 发生,
- 并且让 BAC 与 Fetch 分离,
就必然需要一个中间层来保存“已经算出来但还没有完全消费”的预测产物。
在当前实现里,这个中间层就是 FetchTarget + FTQ。
12. squash、mispredict、resteer 时的恢复流程
12.1 恢复顺序
当前实现对恢复流程非常谨慎,核心顺序通常是:
- 先回滚 FTQ 中尚未正式进入 BPU 主历史队列的历史。
- 再清空 FTQ。
- 再根据 decode/commit/fetch 反馈,对 BPU 正式历史进行 squash/update。
- 重设 BAC 的 PC,重新开始生成 FT。
12.2 squashBpuHistories 的作用
BAC::squashBpuHistories() 会:
- 倒序遍历 FTQ。
- 对每个仍带有
bpuHistory的 FT,调用bpu->squashHistory(...)。 - 清空对应
ft->bpuHistory。
之所以倒序,是因为这些 history 代表的是按预测路径逐步累积的 speculative history update,恢复时必须反向回滚。
12.3 FTQ::squash 的前提
FTQ::squash() 内部对所有 FT 都断言:
ft->bpuHistory == nullptr
这意味着 FTQ::squash() 不是一个包含全部恢复语义的“一站式接口”,而只是一个在历史已清空前提下的队列清空动作。
这进一步体现出当前实现的层次:
- 历史恢复归 BAC/BPU。
- 队列清空归 FTQ。
12.4 不同来源的 squash
当前实现会从多个来源触发恢复:
commit- 例如真实 branch mispredict 在提交时被确认。
decode- 例如更早发现的控制流纠错。
fetch- 例如前端发现当前 FT 与实际 PC 不一致,或在消费 FT 过程中检测到异常。
不管来源是什么,BAC 先处理 FTQ 中的悬挂历史,再让 BPU 继续处理正式历史。
13. BTB miss 与 surprise branch 的处理
这是理解当前 FTQ 设计时不可绕过的一点。
13.1 BAC 对 branch 可见性的前提
在 decoupled frontend 里,BAC 依赖 BTB 来发现 branch。
也就是说:
BTB hit才能让 BAC 在生成 FT 时“看到”一个 branch。
所以如果某个 branch:
- 首次出现,
- 从未被 taken,
- 被 BTB 驱逐,
那么 BAC 在生成 FT 时会把它当作普通顺序 instruction,根本不会提前预测它。
13.2 这类 branch 在何时被发现
只有在后续 Fetch 真正把对应 bytes 送进 decoder,预译码出 control instruction 时,系统才意识到:
这里其实有一个 BAC 当时没看到的 branch。
13.3 branchPlaceholder 的意义
这就是 branchPlaceholder() 存在的根本原因。
对于这类“漏检 branch”,系统仍然需要:
- 给 predictor 一个可恢复的占位点,
- 让后续 squash 能正确对齐历史,
- 避免 predictor 的 speculative history 与真实前端路径彻底脱节。
因此当前实现采用:
- 先插入一个 placeholder history,
- 默认按 not-taken 继续,
- 后续若错,再由 decode/commit squash 修正。
这是一个典型的保守一致性设计。
14. 复杂指令与多 branch micro-op 的处理
当前 FTQ/BPU 关系中最复杂的 corner case 是:
- 一个 instruction 含多个 branch 语义,
- 或 branch 不在最后一个 micro-op,
- 或 BTB 中保存的 branch 类型与最终预译码出的真实类型不一致。
当前实现对这些情况采取的是非常保守的策略。
14.1 branch type mismatch
如果从 FT 上取下来的 history 所记录的 branch 类型,与当前预译码得到的 branch 类型不同:
- 认为当前预测历史不可信。
- 先把 history 放回 FT。
- 立刻回滚 FTQ 中所有悬挂历史。
- 将 FTQ 置
Locked。
这样做的含义是:
- 不再信任当前队列后续项。
- 等当前复杂 instruction 处理完,再通过 resteer 重建前端状态。
14.2 多 branch complex instruction
对于包含多个 branch 的复杂 instruction,当前实现只在 BAC 生成 FT 时预测“第一个可见 branch”。
如果在后续 predecode 发现:
- 当前 micro-op 并不是最后一个,
- 但已经没有可用 history,
则会:
- 先回滚 FTQ 中现有悬挂历史。
- 将 FTQ
lock。 - 对当前 instruction 重新做一次新预测。
这表明当前实现并不试图为复杂 instruction 提供完美的细粒度多 branch FT 建模,而是通过锁定和重建来保守处理。
14.3 这种策略的特点
优点:
- 实现简单。
- 一致性风险较低。
- 对少见复杂场景足够稳妥。
代价:
- 会频繁走
lock -> squash/resteer路径。 - 不适合用来精确模拟非常激进、能在复杂 macro-op 内做高精度 branch streaming 的工业前端。
15. FTQ 的关键不变量
以下不变量对当前实现至关重要:
15.1 FTQ 头项在弹出前不能残留 bpuHistory
这是最重要的不变量。
若违反:
popHead()直接失败。- FTQ 置
Invalid。 - 外层必须走恢复路径。
15.2 FTQ 清空前,所有悬挂历史必须已被回滚并清空
FTQ::squash() 明确依赖这个前提。
15.3 当前 fetch PC 必须落在当前 FT 范围内
若不在:
- 说明 FTQ 与实际前端路径已经脱节。
- 必须 resteer。
15.4 对于由 BAC 预先预测的 exit branch,history 转入 BPU 主历史只能发生一次
也就是说,ft->bpuHistory 的所有权转移路径必须明确:
- 要么在
updatePreDecode()中被迁走并插入 BPU。 - 要么在 squash 路径中被回滚并清空。
不能重复消费,也不能遗失。
15.5 BAC 生成 FT 时看到的 branch 集合,不等于最终 decoder 看到的 branch 集合
这不是 bug,而是设计前提。
因此 placeholder、lock、invalidate、resteer 都是必需机制,而不是补丁式残留。
16. 当前实现的优点
16.1 将预测生产与取指消费解耦
这是引入 FTQ 的根本收益:
BAC可以提前规划 fetch path。Fetch可以更像一个按 target 执行的消费者。
16.2 缩短 fetch 关键路径
在 decoupled 模式下,预测不必等到 Fetch 预译码到 branch 才发生。
从模型语义上,前端取指路径的生成被前移到了 BAC 阶段。
16.3 允许 BPU 驱动的更精确 prefetch / target generation
源码注释也明确提到,这种设计利于由 BPU 引导的 fetch target 级别 prefetch。
16.4 对异常场景采用安全优先的恢复策略
当前实现对不一致的容忍方式不是“尽量在线修复”,而是:
- 发现问题,
- 标记 invalid/locked,
- 走 squash/resteer 恢复。
这种策略对模拟器来说通常比激进在线修补更稳健。
17. 当前实现的限制与代价
17.1 强依赖 BTB 的 branch 可见性
这是当前 decoupled 设计最本质的限制。
若 branch 不在 BTB 中:
- BAC 根本看不到。
- 只能在后续 predecode 补救。
因此这份实现并不是“完全凭静态指令流就能独立构建 FT”的前端模型,而是“BTB-guided FT generation”。
17.2 FTQ 和 BPU 耦合较深
虽然 FTQ 本身结构轻量,但整个机制依赖多个跨模块约定:
ft->bpuHistory何时生成。- 何时迁移。
- 何时回滚。
popHead()前必须清空。
这使得 BAC、FTQ、BPU 三者之间存在明显的隐式协作协议。
17.3 复杂指令场景依赖保守恢复
当前实现面对多 branch complex instruction 时,不是做精细建模,而是:
lockinvalidatesquashresteer
这对功能正确性是合理的,但对前端微结构时序细节的建模精度有限。
17.4 FTQ 不是完全自治的数据结构
FTQ 并不知道如何处理 BPU 历史。
它只是一个容器:
- 外部先回滚 history,
- 它再清队列。
因此 FTQ 的接口语义本身并不闭合,必须结合 BAC/BPU 才能正确使用。
18. 与 TAGE 预测器的关系
当前仓库里的 TAGE 预测器本体没有显式多周期预测延迟建模,而是同步返回预测结果。
放到 FTQ 设计中看,其关系是:
BAC在生成 FT 时,通过BPredUnit::predict()调用条件分支预测器。- 如果条件预测器配置为
TAGE,则TAGE::lookup()/TAGE::predict()会同步返回方向预测。 - BPU 再结合
BTB、RAS、indirect predictor 形成完整控制流预测。 - 这些预测结果被封装进
FetchTarget和bpuHistory,进入 FTQ。
因此:
TAGE决定“方向预测结果”。BPredUnit决定“完整 branch prediction 结果”。FTQ承载“被 BAC 预先规划出来的 fetch path”。
换句话说,FTQ 并不直接关心底层条件预测器是 TAGE、gshare 还是别的实现;它只消费由 BPredUnit 整合后的预测产物。
19. 一条完整的数据流示例
以下给出一条典型 decoupled frontend 路径,帮助理解各组件分工。
步骤 1:BAC 从 bacPC 开始扫描
- 检查若干顺序地址是否在
BTB中命中。 - 找到一个 branch 地址。
步骤 2:BAC 调用 BPU 预测
- 从 BTB 中取出
StaticInst。 - 调用
bpu->predict(...)。 - 得到 taken/not-taken 和 target。
- 同时获得一份
PredictorHistory。
步骤 3:构建 FetchTarget
- 设置
startPC/endPC/predPC/is_branch/taken。 - 把
PredictorHistory挂到ft->bpuHistory。 - 将
FetchTarget插入 FTQ。
步骤 4:Fetch 读取 FTQ 头项
- 检查当前
fetch PC是否处于 FT 范围内。 - 在 FT 范围内顺序取指并预译码。
步骤 5:Fetch 预译码到 branch
- 调用
bac->updatePC(...)。 - 在 decoupled 模式下进一步调用
updatePreDecode(...)。
步骤 6:history 从 FTQ 转回 BPU
- 若这是 FT 的 exit branch,则取下
ft->bpuHistory。 - 补上真实
seqNum。 - 调用
bpu->insertPredictorHistory(...)。
步骤 7:FetchTarget 完成消费
- 当 FT 的退出 instruction 被处理完后,当前 FT 被视为消费结束。
Fetch调用ftq->popHead()。- 若无残留
bpuHistory,则成功弹出。
步骤 8:后续训练与恢复
- 若预测正确,commit 时
bpu->update(...)正常训练。 - 若预测错误,decode/commit/fetch 任何一方都可能触发 squash。
- BAC 先回滚 FTQ 中尚未入账的历史,再清空 FTQ,重建路径。
20. 审查结论
基于当前源码,可以得到如下结论:
20.1 FTQ 的角色非常明确
FTQ 在当前实现中是:
BAC与Fetch之间的FetchTarget队列;- 同时也是尚未正式进入 BPU 主历史体系的
PredictorHistory的临时载体。
它不是 predictor 本体,也不是 predictor 训练队列。
20.2 当前实现逻辑上是自洽的
从源码逻辑看,FTQ、BAC、Fetch、BPU 之间的分工和恢复路径是闭合的:
- 预测先发生;
- 历史先挂 FT;
- 预译码后正式入账;
- 出错时先回滚 FTQ 悬挂历史,再清队列,再修正正式历史。
整体设计是自洽的。
20.3 这是一种“保守而稳健”的 decoupled frontend 建模
它的主要特征是:
- 使用
BTB-guided的 fetch target 生成; - 允许 branch 漏检并通过 placeholder 补救;
- 对复杂 corner case 优先选择
lock/invalidate/squash/resteer; - 把一致性置于建模激进度之前。
20.4 最大限制在于 BTB 可见性和复杂指令精细建模
这套实现适合模拟一种:
- 有 decoupled branch prediction,
- 但仍然高度依赖 BTB 发现 branch,
- 对复杂 branch packing 采取保守恢复策略
的前端。
如果未来希望建模更激进的工业级前端,可能需要进一步增强:
- 非 BTB 依赖的 branch discoverability;
- complex instruction / multi-branch micro-op 的细粒度建模;
- FTQ 与 predictor history 的所有权语义封装。
21. 简要术语表
BAC- Branch Address Calculation stage,当前实现里同时承担 decoupled prediction feeding 与 fetch-side PC update 的协调职责。
BPU- Branch Prediction Unit,即
BPredUnit。
- Branch Prediction Unit,即
FT- Fetch Target。
FTQ- Fetch Target Queue。
PredictorHistory- BPU 为单条 branch 预测维护的历史对象,支持后续 update/squash/recovery。
BTB- Branch Target Buffer,当前 decoupled FT 生成的 branch 可见性基础。
Placeholder- 为 BAC 漏检 branch 构造的占位历史,用于后续恢复和纠偏。
Resteer- 前端发现 FTQ 路径与实际取指路径不一致后,重定向 BAC/fetch 路径并恢复状态的机制。