跳到主要内容

FTQ 深入:多指针、多副本与状态一致性

基础页已经回答“FTQ 在系统中保存什么、一个动态取指块如何从预测走到训练”。本页不再重复字段语义和正常生命周期,而是专门回答实现问题:为什么需要六个主指针,语义信息怎样落到不同存储体,redirect 如何同时恢复多条消费流水,以及同拍 enqueue/commit/redirect 怎样不破坏身份一致性。

若还不清楚 ftqIdx/ftqOffset、预测快照、恢复快照和执行真相的区别,先读 FTQ:连接预测、取指与提交。

阅读源码建议按以下顺序:

  1. FtqPtr和各Bundle;
  2. 六个指针及其next-state wire;
  3. 语义字段到物理存储的映射;
  4. BPU enqueue与S2/S3 correction;
  5. IFU request和predecode writeback;
  6. backend/IFU redirect;
  7. commit read与BranchPredictionUpdate。

一、六个指针不是六份队列​

它们都指向同一批FTQ物理槽,只是代表不同生产者/消费者的进度。

指针准确语义主要推进条件
bpuPtrBPU下一次S1分配的位置,即年轻端tailS1 enqueue;预测修正/redirect时回退
ifuPtr下一项正式送往IFU/ICache的位置toIfu.req.fire
pfPtr下一项送往预取侧的位置toPrefetch.req.fire
ifuWbPtrIFU预译码写回进度边界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_memBPU S1/S2/S3IFU、预取、commit读口多,且需要新写旁路
历史/RAS 恢复快照ftq_redirect_memBPU 最终预测级IFU/backend redirect、commitredirect 关键路径需要独立预读
训练 metaftq_meta_memComposer/最终预测级commit update位宽大,通常只在退休端读取
FTB 结构快照ftb_entry_mem预测结果redirect、false-hit、commit读口时机不同于训练 meta
IFU 预译码真相ftq_pd_memIFU pdWbredirect、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

同时:

  1. flush ICache及前端相关在途事务;
  2. 根据redirect level判断源指令本身是保留还是flush;
  3. 清除同一FTQ项中redirect offset之后的slot状态;
  4. 更新该项真实target、taken CFI和mispredict信息;
  5. 向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_flushed slot不算br_committed;
  • 每个已分配FTQ项最终退休或被明确flush,不永久泄漏容量。

十三、FTQ full应怎样归因​

FTQ full只是现象,根因可能在不同阶段:

观察更可能的瓶颈
ifuPtr远落后bpuPtrIFU/ICache请求吞吐或前端flush
ifuWbPtr远落后ifuPtrICache 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

十五、工程练习​

  1. 连续输入A、B、C三个块,让S2修正A。画出bpuPtr/ifuPtr/pfPtr回退位置以及B、C为何失效。
  2. 构造FTQ满且canCommit=1,证明同拍enqueue/retire后occupancy仍守恒。
  3. 让一个后端redirect与IFU redirect同拍,确认优先级、恢复meta和flush范围。
  4. 让物理下标绕回,在相同value上制造旧IFU响应,证明flag/flush能阻止污染新项。
  5. 为enqueue、IFU request、pdWb、ROB commit、backend redirect建立同拍优先级表,并对每个主指针写出next-state。