FTQ:连接预测、取指与提交
FTQ(Fetch Target Queue)既不是分支预测器,也不是指令 Cache。它是一张动态在途取指块账本:预测器很早给出一个取指方向,真正指令很晚才被译码、执行和提交,FTQ 负责证明这些不同时间发生的事件仍属于同一个动态取指块。
本页建立 FTQ 的系统角色和一项请求的生命周期;多指针、物理存储、旁路及同拍冲突等实现问题统一放在 FTQ 深入:多指针、多副本与状态一致性 中。
源码入口:frontend/NewFtq.scala。
一、为什么只有 PC 还不够
同一个静态 PC 可以在循环中同时存在多个动态实例:
循环第10次:PC=0x80001000,使用历史 H10 预测
循环第11次:PC=0x80001000,使用历史 H11 预测
循环第12次:PC=0x80001000,使用历史 H12 预测
等第 10 次执行完成时,不能只凭 PC 寻找预测上下文,因为第 11、12 次也有相同 PC。前端给每个动态取指块分配 ftqIdx,块内每条指令再用 ftqOffset 标识:
动态控制流身份 = {ftqIdx, ftqOffset}
ftqIdx回答“是哪一次动态取指”;ftqOffset回答“是该取指块中的哪条指令”;- PC 只描述代码位置,不能替代动态身份。
二、FTB、FTQ 与 ROB 分别记录什么
| 结构 | 性质 | 主要内容 | 生命周期 |
|---|---|---|---|
| FTB | 持久预测表 | 某个 PC 块通常有哪些分支、目标和 fall-through | 跨多次动态执行保留 |
| FTQ | 动态取指块队列 | 本次预测、恢复点、预译码真相和最终结果 | 从预测到训练/退休 |
| ROB | 动态指令队列 | 每条指令的执行、异常和提交状态 | 从派发到提交/flush |
三者不是包含关系:FTB 不是 FTQ 的元素,FTQ 也不是 ROB 的前半段。一份 FTB entry 可以在循环的不同迭代中产生多个 FTQ 项;一个 FTQ 项又可能覆盖多条进入 ROB 的指令。
可以把它们分别理解为:
FTB:过去学到了什么?
FTQ:这一次动态预测发生了什么?
ROB:这一次执行的每条指令完成了吗?
三、一个 FTQ 项要回答的三类问题
FTQ 项不是简单的“PC + target”。它要把预测、恢复、真实执行三种时间尺度的信息绑定在同一个动态身份上。
1. 当时预测了什么
这类信息描述前端沿哪条路径发起取指:
- 取指块起始 PC、下一行地址和跨行选择;
- 预测的 taken CFI 位置、target 与 fall-through;
- 最终采用 S1、S2 还是 S3 的预测;
- 当时看到的 FTB entry 和各预测组件的 meta。
其中 meta 不是预测结果的重复副本,而是训练时需要的“旧状态”:例如 provider、alternate 选择和计数器状态。没有它,提交时只知道“预测错了”,却不知道应该训练哪个表项。
2. 如果走错了,恢复到哪里
预测发生时,前端已经推测性修改全局历史和 RAS。FTQ 必须保存对应恢复点,例如:
- 全局历史指针
histPtr; - RAS 的
ssp/sctr; TOSW/TOSR/NOS/topAddr。
这些字段回答“回到该动态块时,预测器状态应恢复为什么”,与训练 counter 所需的 meta 是两类不同信息。
3. 后来真实发生了什么
取到指令字并执行后,FTQ 逐步补入真相:
- 哪些 slot 是 branch、jump、call、return 或 RVC;
- 真实 taken CFI、target 和 mispredict 位置;
- 哪些 slot 进入了 ROB、已提交或被 flush;
- 原预测是 hit、false hit 还是根本没有 FTB entry。
最终训练更新本质上是在比较:
预测时快照 vs. 预译码/执行得到的真相
至于这些信息为何拆进多块 SRAM 和寄存器阵列,由深入页的 语义信息如何映射到物理存储 专门解释。
四、一个 FTQ 项的生命周期
一个取指块可能包含多条指令,因此它不会简单地整体从 Executed 跳到 Committed。块内 slot 至少要区分:
empty → 等待提交 → committed
↘ flushed
例如块内第二条分支造成 redirect 时,前面的指令可能仍可提交,分支本身需要记录真实结果,后面的指令则属于错误路径。
五、预测为什么由 S1 分配、S2/S3 修正
昆明湖的预测是分级流水:S1 最早给出方向,S2/S3 用更复杂的预测器修正它。FTQ 采用:
S1:尽早创建动态身份并分配 ftqIdx
S2/S3:携带同一个 ftqIdx 覆盖预测字段
如果一定等 S3 最终预测完成才分配 FTQ:
- BPU 到 IFU 的启动延迟增加;
- 难以维持每拍一个取指块的吞吐;
- 早级已经可以开始的 ICache 预取被白白推迟。
代价是:晚级一旦改变 target,沿旧方向产生的年轻 FTQ 项和取指请求必须被取消。这里仍是修正同一个动态项,不能把 S2/S3 correction 当成另一次新预测再分配新项。
六、FTQ 同时服务哪些上下游
| 方向 | FTQ 提供/接收什么 | 为什么不能省略 |
|---|---|---|
| BPU → FTQ | S1/S2/S3 预测、meta、恢复快照 | 建立动态身份并保存预测上下文 |
| FTQ → ICache/IFU | PC、下一行信息、ftqIdx | 启动预取和正式取指 |
| IFU → FTQ | 预译码结果、真实指令边界 | 检查 FTB false hit,建立有效 slot |
| Backend → FTQ | redirect、真实 target、CFI 结果 | 修正预测真相并清错误路径 |
| ROB → FTQ | ftqIdx/offset 的提交消息 | 确定哪些真实结果可以用于训练 |
| FTQ → BPU | BranchPredictionUpdate | 用已提交结果训练预测器 |
FTQ 因而是前端与后端之间的控制流“对账点”:任何一侧只传 PC 而不传动态身份,都会在循环、重放和 redirect 下产生歧义。
七、正常路径逐步示例
假设 BPU 为代码块 A 给出 S1 预测:
- 分配:FTQ 创建 A 的动态项
ftqIdx=12,保存初步 PC、target 和恢复点; - 修正:S2/S3 若得到更准确结果,仍写
ftqIdx=12; - 取指:FTQ 分别驱动 ICache 预取和正式 IFU 请求,请求都携带 12;
- 预译码:IFU 发现真实 branch/RVC 边界,写回 12,并标出哪些 offset 是有效指令;
- 执行:每条控制流指令携带
{12, offset},后端据此更新真实 taken/target; - 提交:ROB 按
{12, offset}通知哪些 slot 已提交; - 训练:FTQ 汇合预测快照与真实结果,生成 BPU update;
- 退休:训练读取完成后,12 对应的物理槽才可被后来的动态块复用。
注意:正式取指已经发出,并不等于 FTQ 项可以释放;它还承担后续恢复与训练职责。
八、redirect 时保留谁、取消谁
redirect 携带 {ftqIdx, ftqOffset}。假设 FTQ 项 A 中的一条分支造成 redirect:
更老 FTQ 项 | A 中分支前 | A 的分支 | A 中分支后 | 比 A 年轻的 FTQ 项
保留 | 保留 | 记录真相 | 清除 | 全部清除
还需要:
- 用 A 保存的历史/RAS 恢复点重建预测状态;
- 把 A 的真实 taken、target 和 mispredict 原因保留下来;
- 让 BPU、预取、IFU 等年轻端重新从正确路径开始;
- 继续按顺序处理比 A 更老的训练和退休。
所以“redirect 就把源项也删掉”是错误的。造成 redirect 的控制流往往提供最有价值的训练样本;真正应取消的是它之后的错误路径。
精确的指针回退、同一项 offset 清理与 stale writeback 防护见 深入页的 redirect 章节。
九、FTQ 满意味着什么
FTQ 满只表示“预测产生的新动态块多于退休释放的旧动态块”,并不直接说明 BPU 慢。常见原因包括:
- ICache/ITLB miss 导致正式取指落后;
- IFU 预译码写回受阻;
- 后端长延迟使 ROB 无法提交;
- 预测器训练端更新受阻;
- redirect 频繁造成重复取指。
满时 FTQ 会反压 BPU,预测事务必须整体停住:PC、历史和预测结果不能只有一部分前进。
定位 FTQ full 时,应问“哪一个消费者落后”,而不是立即扩大队列。深入页会用六个主指针的距离区分这些瓶颈。
十、四个常见误区
误区 1:FTQ 是 FTB 的队列
FTB 是跨执行保留的预测表;FTQ 保存某一次动态预测。FTQ 会保存当时读到的 FTB entry 快照,但两者不是组成关系。
误区 2:IFU 消费后即可释放 FTQ
此时还没有执行真相和提交状态,预测器也尚未训练。必须等该项完成训练/退休。
误区 3:S2/S3 修正要新建 FTQ 项
它们修正 S1 已分配的同一个动态实例。新建会让同一取指块出现两个身份。
误区 4:错误路径一律没有价值
年轻错误路径不能训练,但 redirect 源指令的真实方向和目标必须保留,否则预测器会重复犯错。
十一、波形中怎样跟踪一项
先选择一个完整 FtqPtr,然后按事件链跟踪:
BPU S1 分配
→ S2/S3 是否修正同一 ftqIdx
→ ICache/IFU 请求是否携带该身份
→ IFU pdWb 是否写回同一项
→ backend redirect 或执行结果的 ftqIdx/offset
→ ROB commit 的 ftqIdx/offset
→ BPU update 的 PC/meta/CFI 是否来自该项
每一步都应先核对身份,再看 taken、target 或 meta 数值。身份错位时,数值碰巧相同也不能证明正确。
十二、读完本页应能回答
- 为什么循环中相同 PC 的多个动态实例必须使用不同 FTQ 身份?
- FTB、FTQ、ROB 的生命周期为何不同?
- 预测 meta 与 redirect 恢复快照分别解决什么问题?
- 为什么 S1 创建项,而 S2/S3 只能修正同一项?
- redirect 后为什么保留源项、清除年轻项?
- 为什么 ICache 已经收到请求,FTQ 项仍不能被复用?
下一步阅读 FTQ 深入,把这里的语义映射到六个指针、多块存储体和具体 next-state 规则。