跳到主要内容

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 → FTQS1/S2/S3 预测、meta、恢复快照建立动态身份并保存预测上下文
FTQ → ICache/IFUPC、下一行信息、ftqIdx启动预取和正式取指
IFU → FTQ预译码结果、真实指令边界检查 FTB false hit,建立有效 slot
Backend → FTQredirect、真实 target、CFI 结果修正预测真相并清错误路径
ROB → FTQftqIdx/offset 的提交消息确定哪些真实结果可以用于训练
FTQ → BPUBranchPredictionUpdate用已提交结果训练预测器

FTQ 因而是前端与后端之间的控制流“对账点”:任何一侧只传 PC 而不传动态身份,都会在循环、重放和 redirect 下产生歧义。

七、正常路径逐步示例​

假设 BPU 为代码块 A 给出 S1 预测:

  1. 分配:FTQ 创建 A 的动态项 ftqIdx=12,保存初步 PC、target 和恢复点;
  2. 修正:S2/S3 若得到更准确结果,仍写 ftqIdx=12;
  3. 取指:FTQ 分别驱动 ICache 预取和正式 IFU 请求,请求都携带 12;
  4. 预译码:IFU 发现真实 branch/RVC 边界,写回 12,并标出哪些 offset 是有效指令;
  5. 执行:每条控制流指令携带 {12, offset},后端据此更新真实 taken/target;
  6. 提交:ROB 按 {12, offset} 通知哪些 slot 已提交;
  7. 训练:FTQ 汇合预测快照与真实结果,生成 BPU update;
  8. 退休:训练读取完成后,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 数值。身份错位时,数值碰巧相同也不能证明正确。

十二、读完本页应能回答​

  1. 为什么循环中相同 PC 的多个动态实例必须使用不同 FTQ 身份?
  2. FTB、FTQ、ROB 的生命周期为何不同?
  3. 预测 meta 与 redirect 恢复快照分别解决什么问题?
  4. 为什么 S1 创建项,而 S2/S3 只能修正同一项?
  5. redirect 后为什么保留源项、清除年轻项?
  6. 为什么 ICache 已经收到请求,FTQ 项仍不能被复用?

下一步阅读 FTQ 深入,把这里的语义映射到六个指针、多块存储体和具体 next-state 规则。