FTQ 深入:多指针、多副本与状态一致性
基础页已经回答“FTQ 在系统中保存什么、一个动态取指块如何从预测走到训练”。本页不再重复字段语义和正常生命周期,而是专门回答实现问题:为什么需要六个主指针,语义信息怎样落到不同存储体,redirect 如何同时恢复多条消费流水,以及同拍 enqueue/commit/redirect 怎样不破坏身份一致性。
若还不清楚 ftqIdx/ftqOffset、预测快照、恢复快照和执行真相的区别,先读 FTQ:连接预测、取指与提交。
阅读源码建议按以下顺序:
FtqPtr和各Bundle;- 六个指针及其next-state wire;
- 语义字段到物理存储的映射;
- BPU enqueue与S2/S3 correction;
- IFU request和predecode writeback;
- backend/IFU redirect;
- commit read与
BranchPredictionUpdate。
一、六个指针不是六份队列
它们都指向同一批FTQ物理槽,只是代表不同生产者/消费者的进度。
| 指针 | 准确语义 | 主要推进条件 |
|---|---|---|
bpuPtr | BPU下一次S1分配的位置,即年轻端tail | S1 enqueue;预测修正/redirect时回退 |
ifuPtr | 下一项正式送往IFU/ICache的位置 | toIfu.req.fire |
pfPtr | 下一项送往预取侧的位置 | toPrefetch.req.fire |
ifuWbPtr | IFU预译码写回进度边界 | fromIfu.pdWb.valid |
commPtr | 最老的待检查、训练和退休项 | canMoveCommPtr |
robCommPtr | 后端ROB已报告的FTQ提交进度 | rob_commits |
在没有redirect的稳定状态下,可以形成这样的流水:
老 年轻
commPtr ... ifuWbPtr ... ifuPtr ... bpuPtr
\... pfPtr .../ (pfPtr相对独立)
这不是任何周期都必须严格相邻的示意。重要的不变量是:消费者不能越过尚未生产的bpuPtr,commPtr不能越过尚未完成predecode的ifuWbPtr。
为什么不能只用head和tail
假设BPU很快连续产生8个块:
- 预取器可能已经消费到第7项;
- IFU因Cache miss只正式消费到第4项;
- 第3项刚完成predecode;
- ROB只提交到第1项;
- 训练端还在读第1项的SRAM。
一个head无法同时表示这些不同进度,也无法只对某个消费者施加背压。
二、环形指针的flag、满空和距离
FtqPtr继承CircularQueuePtr:
{flag, value}
value是物理下标,flag在绕回时翻转。于是:
{0,5} 与 {1,5}
虽然访问同一物理槽,却代表相差一圈的逻辑位置。
FTQ占用量为:
validEntries = distanceBetween(bpuPtr, commPtr)
同下标同flag表示空;同下标异flag表示满。仅比较value会把满和空混淆。
源码还直接检查:
ifuPtr不可跑到bpuPtr之后
pfPtr不可跑到bpuPtr之后
commPtr不可跑到ifuWbPtr之后
copied_bpu_ptr必须等于bpuPtr
三、语义信息如何映射到物理存储
概览页把一项信息分成“当时预测了什么、如何恢复、后来真实发生了什么”。硬件不能照这个逻辑分类做成一个巨型 entry,因为每类字段的位宽、写入时刻和读端口需求完全不同。NewFtq.scala 因而采用 SRAM 保存宽而稳定的快照,寄存器保存频繁更新的小状态。
1. 从语义到存储体的总映射
| 语义信息 | 物理结构 | 主要写入者 | 主要读取者 | 为什么单独存放 |
|---|---|---|---|---|
| PC 与块边界 | ftq_pc_mem | BPU S1/S2/S3 | IFU、预取、commit | 读口多,且需要新写旁路 |
| 历史/RAS 恢复快照 | ftq_redirect_mem | BPU 最终预测级 | IFU/backend redirect、commit | redirect 关键路径需要独立预读 |
| 训练 meta | ftq_meta_mem | Composer/最终预测级 | commit update | 位宽大,通常只在退休端读取 |
| FTB 结构快照 | ftb_entry_mem | 预测结果 | redirect、false-hit、commit | 读口时机不同于训练 meta |
| IFU 预译码真相 | ftq_pd_mem | IFU pdWb | redirect、commit | 写入比预测晚,内容来自真实指令字 |
| target/CFI/mispredict/状态 | 多组寄存器数组 | BPU、IFU、backend、ROB | 多方 | 小字段、多写口或逐 slot 更新 |
这张表才是深入页第三章的重点:不是再次罗列“FTQ 项有什么”,而是解释同一动态身份为什么要跨多个物理存储体实现。
2. PC memory 为何需要 wrapper 和旁路
ftq_pc_mem 中的 Ftq_RF_Components 保存:
startAddr
nextLineAddr
isNextMask
fallThruError
IFU、ICache 预取和 commit 可能并行读取不同 FTQ 项,所以源码用 FtqPcMemWrapper 组织多个读位置。又因为 PC memory 是同步存储,BPU 刚写入 FTQ[x] 后,年轻端可能立即请求 x;如果只等 SRAM 正常读出会插入气泡,因此还需要与 ftqIdx 绑定的新写旁路。
3. 恢复快照为何不能并入训练 meta
ftq_redirect_mem 保存 histPtr 以及 RAS 的 ssp/sctr/TOSW/TOSR/NOS/topAddr。它与 ftq_meta_mem 都来自预测阶段,但访问特征不同:
- 恢复快照在 redirect 关键路径上,需要被 ahead 机制提前读;
- 训练 meta 主要在 commit 时读出,用于更新 TAGE/SC/ITTAGE 等组件;
- 把两者绑成同一 SRAM,会让 redirect 和 commit 争用端口,并扩大关键路径读宽。
因此“预测时产生”不是合并存储的充分条件,真正决定结构的是读写时序。
4. 为什么 FTB entry 还有独立副本
MetaEntry 中包含 Composer meta 和最终 FTB 相关信息,但 redirect 与 false-hit 检查也需要直接访问 FTB 结构。源码保留 ftb_entry_mem,使这些访问不必等待或争用 commit 的 meta 读取。
这里保存的是本次动态预测所看到的 FTB 快照,不是把持久 FTB 表本身放进 FTQ。
5. 真实预译码为什么晚写另一块 memory
ftq_pd_mem 保存 IFU 看到真实指令字后得到的:
brMask
jmpInfo / jmpOffset
jalTarget
rvcMask
这些字段在 BPU 分配 FTQ 时尚不存在,必须等 ICache 返回并由 IFU predecode 后写入。把它们与预测字段放在同一 SRAM,不仅需要额外写口,还会使早期分配时写入大量无效占位数据。
6. 哪些字段适合寄存器数组
以下状态会被不同阶段小粒度覆盖,或需要逐 slot 修改:
update_target
cfiIndex_vec
mispredict_vec
pred_stage
entry_fetch_status
entry_hit_status
commitStateQueueReg[ftq][offset]
它们使用寄存器阵列,是用面积换取多写口、逐位更新和组合读取便利;并不表示这些字段在语义上比 SRAM 字段更重要。
7. 分裂存储产生的新正确性责任
一项横跨多个 memory 后,commit 或 redirect 读取的 PC、meta、恢复快照和 predecode 必须仍来自同一个完整 FtqPtr。实现需要同时处理:
- 同步 SRAM 的一拍读延迟;
- 环绕后相同物理下标属于不同代次;
- redirect 与普通读同拍的优先级;
- 新写项尚未来得及从 SRAM 正常读出的旁路;
- 多块 memory 的读地址必须使用同一个冻结身份。
因此,物理拆分节省了面积和时序,却把“跨存储体身份一致性”变成 FTQ 最重要的不变量之一。
四、S1分配与S2/S3覆盖的精确关系
S1创建新动态项
enq_fire = fromBpu.resp.fire && allowBpuIn
新项索引 = bpuPtr
bpuPtr = bpuPtr + 1
只有S1握手创建occupancy。S2/S3响应携带此前分配的ftq_idx,写回同一个槽。
S2/S3发现目标变化
当晚级预测产生redirect:
bpuPtr = corrected.ftq_idx + 1
比该项年轻、由旧方向产生的预测项被丢弃。若ifuPtr/pfPtr已经走到修正项或更年轻位置,也要回到修正边界重新消费;如果还没走到那里,则不必倒退。
只有 !isBefore(consumerPtr, correctedIdx) 时才回退consumerPtr
这避免把尚未消费到错误区域的指针无谓回滚。
为什么需要新写旁路
PC memory是同步读。若BPU刚写入FTQ[x],IFU同拍或下一拍就要发送x,等待SRAM正常读出会产生气泡。因此源码保留bpu_in_bypass_buf和对应ptr;命中刚写项时直接旁路PC bundle和target。
五、三个不同层次的“纠错”
1. BPU S2/S3 correction
预测器内部较晚级覆盖较早级。它修正同一FTQ项并flush已经沿旧target发出的IFU/prefetch请求,但还不是“程序已经执行后发现误预测”。
2. IFU redirect
取到真实指令后,predecode可能发现FTB false hit、指令边界或目标问题。FTQ读取对应的redirect恢复信息,生成给BPU的恢复请求。
3. Backend redirect
执行结果、异常或内存重放证明当前路径错误。后端redirect优先于IFU redirect,并携带真实CFI信息。
三者都能改变取指路径,但证据强度、延迟和需要清除的范围不同,波形分析时不能只看一个redirect.valid。
六、backend/IFU redirect如何恢复FTQ
源码把backend和IFU redirect组成有优先级的redirectVec。选中redirect后:
idx = redirect.ftqIdx
next = idx + 1
bpuPtr = next
ifuPtr = next
ifuWbPtr = next
pfPtr = next
同时:
- flush ICache及前端相关在途事务;
- 根据redirect level判断源指令本身是保留还是flush;
- 清除同一FTQ项中redirect offset之后的slot状态;
- 更新该项真实target、taken CFI和mispredict信息;
- 向BPU发送history/RAS恢复信息。
为什么commPtr不一起设为next
commPtr代表训练/退休顺序。redirect发生时,更老项可能尚未完成训练;redirect源项自身也需要保留真实结果。若把commPtr直接跳到next,会丢失老项和最有价值的误预测训练。
stale writeback风险
redirect前发出的ICache/IFU请求可能晚到。正确性依赖完整FtqPtr、flush协议和状态检查,确保旧代响应不能写进绕回后复用的同下标新项。只比较ftqIdx.value是不够的。
七、ftqIdxAhead为什么能降低redirect延迟
ftq_redirect_mem和ftb_entry_mem是同步存储。若backend正式redirect到达后才给地址,至少还要等待一次memory read。
后端提前给出若干潜在redirect候选ftqIdxAhead,FTQ预读这些项:
前一拍:候选ftqIdx → redirect/FTB SRAM读地址
后一拍:正式redirect到达 → 在预读结果中选择匹配项
若ahead命中,恢复信息可更早送往BPU;若没命中,则使用正式redirect读口结果。它是用额外读口、选择逻辑和候选准确性换恢复周期。
调试时同时观察:
ftqIdxAhead.valid/bits
aheadValid
realAhdValid
ftqIdxSelOH
正式backendRedirect.ftqIdx
八、IFU writeback如何建立提交状态
IFU的pdWb带回真实指令范围和predecode结果:
对每个 valid && inRange 的slot:
c_empty → c_toCommit
同时写ftq_pd_mem并推进ifuWbPtr。之后ROB提交消息按ftqIdx/offset把slot更新为c_committed;redirect则可把slot标为c_flushed或清空更年轻slot。
这解释了为何FTQ不能只保存“整块已提交”一位:块内早期分支可能redirect,后面的slot根本没有资格训练。
九、canCommit与canMoveCommPtr并不完全相同
canCommit要求:
commPtr != ifuWbPtr
没有FTB update stall
并且:ROB提交边界已越过该项
或 该项最后一个有效slot已committed
canMoveCommPtr还允许“第一条指令已被flush”的项直接退休。这类项可能不需要生成有效BPU update,但不能永久占住FTQ槽。
commit读取多个同步memory需要一拍:
周期N:canCommit,使用commPtr发起PC/meta/spec/pd读取
周期N+1:do_commit,组合BranchPredictionUpdate
因此波形中toBpu.update对应的是do_commit_ptr,不一定等于当前已经前进的commPtr。
FTB miss为何可能引入额外stall
若真实存在taken CFI但原来FTB没有hit,生成/写回新FTB entry需要额外控制。源码用bpu_ftb_update_stall走2 → 1 → 0状态,短暂停止后续commit读取,避免FTB更新资源冲突。
十、同拍事件怎样理解
enqueue + commit
当FTQ逻辑已满,但本拍canCommit会释放一项时:
new_entry_ready = validEntries < FtqSize || canCommit
允许同拍退休老项并分配新项。新旧项可能具有相同物理下标但不同环绕flag,所有旁路和状态写入都必须匹配正确代次。
enqueue + IFU read
使用BPU新写旁路,不能依赖SRAM read-during-write的器件特性。
redirect + 普通推进
redirect必须压过普通enqueue、IFU发送和prefetch推进;否则指针会继续进入已知错误路径。源码通过allowBpuIn/allowToIfu门控以及后面的redirect next-state覆盖实现。
pdWb + redirect
旧请求的predecode可能与redirect同拍。必须根据ftq身份和flush范围决定哪些slot状态可保留,不能无条件把整项重新标成c_toCommit。
十一、指针副本为什么存在
bpuPtr、ifuPtr驱动大量比较、SRAM端口和下游请求,高扇出会成为物理时序问题。源码建立多个copied_bpu_ptr/copied_ifu_ptr降低扇出。
这些副本不是协议状态的新版本:
copied_bpu_ptr(i) 必须始终等于 bpuPtr
copied_ifu_ptr(i) 必须表达同一个ifu进度
物理优化不能允许副本在redirect后多延迟一拍,否则不同下游会对“哪些项有效”产生不同认识。
十二、必须成立的不变量
容量与年龄
0 <= distanceBetween(bpuPtr,commPtr) <= FtqSize;- IFU和prefetch不能消费
bpuPtr之后的项; commPtr不能越过ifuWbPtr;- 环绕后不能把旧代写回识别成新项。
身份一致性
- commit update的PC、meta、FTB entry、spec info和真实结果来自同一
do_commit_ptr; - S2/S3 correction的
ftq_idx等于S1首次分配项; - backend redirect的
ftqIdx/offset与更新的target/mispredict槽一致。
flush安全
- redirect后所有更年轻请求最终都被取消或丢弃;
- 错误路径不能产生合法BPU训练;
- redirect源项需要的meta和真实结果不会被提前清除;
- stale IFU/ICache响应不能写入复用后的槽。
状态机
- slot不能从
c_empty直接无来源地变为c_committed; c_flushedslot不算br_committed;- 每个已分配FTQ项最终退休或被明确flush,不永久泄漏容量。
十三、FTQ full应怎样归因
FTQ full只是现象,根因可能在不同阶段:
| 观察 | 更可能的瓶颈 |
|---|---|
ifuPtr远落后bpuPtr | IFU/ICache请求吞吐或前端flush |
ifuWbPtr远落后ifuPtr | ICache miss、ITLB、跨行或predecode返回 |
commPtr远落后ifuWbPtr且ROB未越过 | 后端长延迟、提交堵塞 |
bpu_ftb_update_stall!=0频繁 | FTB miss插入/更新带宽 |
| occupancy锯齿并伴随大量redirect | 预测错误导致反复清空重取 |
pfPtr落后但ifuPtr正常 | 预取侧背压,不一定阻塞正式取指 |
性能优化前应同时画出六个指针的距离,而不是只统计“FTQ满了多少周期”。
十四、推荐波形视图
视图A:分配与取指
bpuPtr, ifuPtr, pfPtr
fromBpu.resp.valid/ready
bpu_in_resp_ptr/stage/target
toIfu.req.valid/ready/ftqIdx
toPrefetch.req.valid/ready/ftqIdx
entry_fetch_status
视图B:预译码与提交
ifuWbPtr, commPtr, robCommPtr
fromIfu.pdWb.valid/ftqIdx
commitStateQueueReg[selected entry]
canCommit, canMoveCommPtr, do_commit_ptr
toBpu.update.valid/pc/cfi_idx/mispred_mask
视图C:恢复
bpu_s2_redirect, bpu_s3_redirect
fromIfu.redirect
fromBackend.redirect
ftqIdxAhead / realAhdValid
redirect ftqIdx/offset/level
histPtr和RAS恢复字段
icacheFlush
十五、工程练习
- 连续输入A、B、C三个块,让S2修正A。画出
bpuPtr/ifuPtr/pfPtr回退位置以及B、C为何失效。 - 构造FTQ满且
canCommit=1,证明同拍enqueue/retire后occupancy仍守恒。 - 让一个后端redirect与IFU redirect同拍,确认优先级、恢复meta和flush范围。
- 让物理下标绕回,在相同
value上制造旧IFU响应,证明flag/flush能阻止污染新项。 - 为enqueue、IFU request、pdWb、ROB commit、backend redirect建立同拍优先级表,并对每个主指针写出next-state。