跳到主要内容

uFTB 与 FTB:控制流结构和目标地址

处理器前端必须连续地产生取指地址。对于顺序代码,下一个地址容易生成;一旦当前取指区域 包含分支或跳转,前端就需要在指令真正返回和执行以前,提前知道控制流可能从哪里离开。

昆明湖 V2 使用两级 Fetch Target Buffer:小而快的 uFTB(代码中为 FauFTB), 以及容量更大的主 FTB。它们描述预测块中的控制流结构,并提供分支或跳转的候选目标。 条件分支方向由uFTB计数器、TAGE-SC等部件预测,间接跳转和返回地址还会由ITTAGE、RAS 进一步修正。

本章按照硬件数据流依次介绍预测块、FTB层级、表项格式、地址编码、查表流水线、预测器 融合、训练更新和错误恢复。

1. FTB在前端中的位置​

从一个取指PC出发,BPU需要形成下一次取指使用的PC。相关信息来自多个部件:

各部件的职责并不相同:

部件主要职责
uFTB尽早识别预测块中的控制流,并给出快速方向和目标
主FTB以更大容量保存预测块结构、目标和fall-through信息
TAGE-SC使用多种历史长度预测条件分支方向
ITTAGE预测普通间接跳转的动态目标
RAS预测函数返回指令的动态返回地址
FTQ保存每个在途预测块的预测结果、表项和训练meta

FTB的核心职责可以概括为:

给定预测块起始PC
→ 描述块中有哪些CFI以及它们的位置
→ 生成各CFI的候选taken target
→ 生成预测块的fall-through地址

其中CFI是 Control-Flow Instruction,包括条件分支、JAL、JALR和RET等。

2. 预测块模型​

FTB不是以“每条指令”为粒度,也不是简单记录“一个PC对应一个target”。它以一个 start PC描述一个预测块。一个预测块中可能包含多个控制流位置:

start PC
│
├── 普通指令
├── 条件分支 B0 ─taken─→ target0
├── 普通指令
├── 条件分支 B1 ─taken─→ target1
├── 普通指令
└── 尾部跳转 J ───────→ targetJ

没有控制流离开该块 → fallThroughAddr

程序按照地址顺序执行,所以最终控制流遵循“最早taken”规则:

  1. 如果B0被预测taken,立即选择target0,后面的B1和J不再参与本次路径;
  2. 如果B0不跳而B1跳,选择target1;
  3. 如果条件分支都不跳但存在无条件尾部跳转,选择targetJ;
  4. 如果没有taken CFI,选择fallThroughAddr。

预测块不要求与ICache cache line边界重合。它表达的是前端预测和取指组织使用的控制流 区域,而不是存储系统的cache line。

3. uFTB与主FTB的层级关系​

uFTB和主FTB保存相似的表项语义,但在延迟与容量之间采用不同取舍。

特征uFTB(FauFTB)主FTB(FTB)
目标最低延迟更大覆盖容量
当前组织32项、寄存器、全相联tag比较2048项、512组×4路、SRAM
方向能力每个way、每个分支槽有2位计数器保存结构与strong_bias,方向由后续预测器补充
典型结果阶段S1S2,并在S3形成稳定结果和meta
替换PLRU组相联替换策略

两个结构并行工作,而不是先等uFTB失败再访问主FTB:

S0:同一个PC同时启动uFTB、FTB和其他预测器
S1:uFTB先给出结果,前端可以据此继续取指
S2:主FTB完成tag匹配并展开表项
S3:较深方向/目标预测器完成,形成更晚、更可靠的结果

如果后一级结果与前一级不同,BPU在预测流水线内部修正取指路径。这种分级设计让前端既 有低延迟的早期答案,又不会受限于很小的表容量。

4. 主FTB的表组织和查找​

主FTB容量由参数给出:

FtbSize = 2048
FtbWays = 4
numSets = 2048 / 4 = 512

每个set包含4个way。预测请求PC经过FTBTableAddr形成set index和tag:

  1. S0把请求PC送给FTB SRAM;
  2. SRAM读出该set的4路表项和tag;
  3. S2将请求tag与4路tag比较;
  4. total_hits形成命中向量;
  5. 正常情况下由hit_way选择唯一命中表项;
  6. 选中的FTBEntry被展开成完整预测信息。

主FTB还保留multi-hit检测。如果同一set出现多个合法tag命中,普通one-hot选择假设已经 被破坏,硬件会选取一个可用表项并在后续阶段标记和修正,而不是静默把结果当作可靠预测。

5. FTBEntry的数据结构​

FTBEntry可以看成一张紧凑的块内控制流地图:

FTBEntry
├── valid
├── brSlots[]
│ ├── valid
│ ├── offset
│ ├── lower
│ ├── tarStat
│ └── sharing
├── tailSlot
│ ├── valid / offset / lower / tarStat / sharing
├── pftAddr
├── carry
├── isCall / isRet / isJalr
├── last_may_be_rvi_call
└── strong_bias[]

5.1 brSlots​

brSlots保存预测块中靠前的条件分支。每个槽包含:

  • valid:这个槽是否存在有效分支;
  • offset:分支位于预测块中的指令位置;
  • lower:压缩target的低位;
  • tarStat:target高位相对当前PC区域的关系;
  • sharing:槽位编码方式是否使用共享的短target格式。

offset不是target偏移量。它用于形成cfiIndex、确定块内指令有效范围,以及按程序顺序 选择第一条taken CFI。

5.2 tailSlot​

tailSlot是表项中的最后一个槽。其解释由sharing决定:

validsharing语义
0任意没有尾部CFI
11作为额外的条件分支槽使用
10保存JAL、JALR或RET等尾部跳转

isCall/isRet/isJalr进一步说明非共享尾槽的控制流类型。isJalr=0的有效非共享尾槽对应 直接JAL;isJalr=1表示间接跳转,isRet和isCall供RAS等部件判断调用/返回语义。

5.3 fall-through字段​

pftAddr + carry压缩保存预测块的fall-through地址。它表示顺序执行越过这个预测块后, 下一块从哪里开始,而不是某条taken分支的目标。

重建逻辑为:

higher = startPC的高位

fallThroughAddr = {
carry ? higher + 1 : higher,
pftAddr,
指令对齐低位0
}

5.4 strong_bias​

strong_bias(i)是条件分支的强taken提示。主FTB展开表项时,可以把它与前级已有的 br_taken_mask(i)合并。它是可训练状态,不代表该指令在ISA上无条件跳转。

6. 直接target的压缩编码​

一条表项可能包含多个target。如果每个槽都存完整虚拟地址,SRAM宽度、功耗和读延迟都会 显著增加。直接分支和JAL的立即数范围有限,因此FTB只保存target低位和区域关系。

条件分支:BR_OFFSET_LEN  = 12
尾部跳转:JMP_OFFSET_LEN = 20
区域状态:TAR_FIT / TAR_OVF / TAR_UDF

三种区域状态表示:

TAR_FIT:target高位区域与PC相同
TAR_OVF:target位于PC的下一个区域
TAR_UDF:target位于PC的上一个区域

6.1 条件分支​

条件分支槽保存target[12:1],地址bit 0由指令对齐隐含为0。重建时:

pcRegion = PC[VAddrBits-1 : 13]

TAR_FIT → {pcRegion, lower[11:0], 1'b0}
TAR_OVF → {pcRegion + 1, lower[11:0], 1'b0}
TAR_UDF → {pcRegion - 1, lower[11:0], 1'b0}

低13位形成8 KiB区域。RISC-V B-type分支范围约为±4 KiB,因此target只可能位于当前区域 或相邻区域。

6.2 JAL和非共享尾槽​

非共享尾槽保存target[20:1]:

pcRegion = PC[VAddrBits-1 : 21]

target = {pcRegion / pcRegion±1, lower[19:0], 1'b0}

低21位形成2 MiB区域,对应JAL约±1 MiB的立即数范围。

6.3 跨区域示例​

条件分支PC     = 0x1ff0
条件分支target = 0x2020

PC靠近8 KiB区域末尾,而target进入下一个区域。表项记录tarStat=TAR_OVF和 lower=target[12:1]。读出时使用pcRegion+1与lower拼接,恢复0x2020。

完整重建逻辑位于FtbSlot.getTarget()和FTBEntry.getTargetVec()。

7. 从FTBEntry到FullBranchPrediction​

命中表项通过FullBranchPrediction.fromFtbEntry()展开:

slot_valids          := entry.brSlots.map(_.valid) :+ entry.tailSlot.valid
targets := entry.getTargetVec(pc, ...)
jalr_target := targets.last
offsets := entry.getOffsetVec
is_jal := entry.tailSlot.valid && entry.isJal
is_jalr := entry.tailSlot.valid && entry.isJalr
is_call := entry.tailSlot.valid && entry.isCall
is_ret := entry.tailSlot.valid && entry.isRet
fallThroughAddr := entry.getFallThrough(...)

展开后的targets是所有slot的完整候选地址向量。它还不是唯一的最终next PC;最终地址需要 结合各slot方向和有效位选择。

FullBranchPrediction.target(pc)的逻辑可以抽象为:

按程序顺序检查每个有效slot:
第一条预测taken的slot → 返回targets[slot]

FTB命中但没有taken slot → 返回fallThroughAddr
FTB未命中 → 返回PC + FetchWidth × 4

cfiIndex由最终选中的slot offset形成,用于标记当前预测块在哪一条CFI处结束。

8. 条件方向与target的融合​

FTB和方向预测器通过FullBranchPrediction组合:

FTB:     slot_valids + offsets + targets + fallThroughAddr
方向预测:br_taken_mask
选择逻辑:real_slot_taken_mask → cfiIndex → target

TAGE-SC对条件方向的修正通常不改变FTB重建出的直接target数值,而是改变最终选择的slot。 例如:

FTB候选:slot0→A,slot1→B,tail JAL→C

方向结果一:slot0 not-taken,slot1 taken → 选择B
方向结果二:slot0 not-taken,slot1 not-taken → 继续到JAL,选择C

地址A/B/C没有被方向预测器重新计算;变化的是控制流路径选择。

uFTB拥有自己的2位方向计数器,可以先产生快速br_taken_mask。主FTB的strong_bias可 补充强taken信息,TAGE-SC再使用更长历史给出较晚方向。后一级与前一级不一致时,BPU 覆盖早期结果并修正取指路径。

9. 不同CFI类型的处理​

9.1 条件直接分支​

条件分支target由PC和指令立即数唯一确定。FTB提前保存并重建target,TAGE-SC等预测器 判断taken/not-taken。

taken     → 使用对应FTB target
not-taken → 检查后续slot,最终可能走fall-through

9.2 JAL​

JAL是无条件直接跳转,执行到它时必然taken。其target由PC和立即数唯一确定,但前端在指令 尚未取回时还无法现场计算,所以FTB提前提供target。

如果JAL前面存在条件分支,较早的条件分支taken会截断路径;只有前面的条件分支都 not-taken时,JAL才成为本次预测块的最终CFI。

9.3 普通JALR​

JALR同样是无条件跳转,但target来自运行时寄存器:

target = (rs1 + immediate) & ~1

FTB保存的尾槽target可以作为早期基础值。ITTAGE根据PC和历史预测动态间接目标,并在更晚 阶段覆盖jalr_target。

9.4 RET​

RET是特定形式的JALR。同一静态RET在不同调用链中会返回不同地址,因此RAS根据推测调用栈 提供动态返回地址。FTB负责识别isRet和提供早期结构信息,RAS负责更合适的返回目标。

整体优先关系可以概括为:

直接条件分支 / JAL target → 主要由FTB提供
普通JALR target → ITTAGE可覆盖FTB
RET target → RAS可覆盖FTB
条件分支方向 → TAGE-SC可覆盖uFTB早期方向

10. S0到S3的数据流​

下面按逻辑预测阶段描述一次主FTB访问:

阶段数据处理主要输出
S0请求PC生成FTB索引和tag,同时启动各预测器查表请求
S1uFTB完成快速命中;主FTB SRAM返回候选ways早期next PC、FTB原始读数据
S2主FTB比较tag、选择way、展开FTBEntrytargets、offsets、slot类型、fall-through
S3稳定主FTB结果并合并较深方向/目标预测较晚next PC和训练meta

主FTB的关键路径是:

FTBBank.req_pc
→ SRAM set read
→ read_entries/read_tags
→ total_hits与hit_way
→ s2_ftb_entry_dup
→ FullBranchPrediction.fromFtbEntry
→ S2/S3 targets与fallThroughAddr

每个阶段的预测结果都可以与前一级比较。结果变化时,BPU产生内部redirect;FTQ最终保存 采用的预测结果及各预测器meta。

11. 表项生成与训练​

预测只读取状态,真正更新发生在获得可靠结果之后。完整闭环如下:

FTBEntryGen综合以下信息:

  • 预测块startAddr;
  • 旧FTB表项;
  • IFU预译码得到的CFI类型和位置;
  • 后端产生的真实方向和真实target;
  • FTQ保存的命中way和其他meta。

11.1 结构更新​

下列情况需要修改FTB表项结构:

  • 新发现表项中没有记录的条件分支;
  • 分支或跳转的offset、类型错误;
  • 直接target或JALR历史target需要修正;
  • 预测块fall-through发生变化;
  • 旧表项布局与实际预译码不一致。

11.2 方向训练​

如果CFI位置和target都正确,只是条件分支方向预测错误,则主要训练uFTB计数器和 TAGE-SC。主FTB结构没有变化时,可以避免不必要的整项写入。

11.3 分配与替换​

更新PC在当前set命中时,使用保存的命中way更新;未命中时优先选择无效way,否则由替换 策略选择牺牲way。uFTB采用类似逻辑,但在32个全相联way中查找和替换。

12. 错误检测和恢复​

FTB属于预测状态,允许出错,但错误不能影响体系结构正确性。常见错误包括:

  • FTB miss;
  • tag alias或false hit;
  • 同一set多路命中;
  • 表项中的slot布局陈旧;
  • 直接target陈旧;
  • 条件方向错误;
  • JALR或RET动态target错误。

恢复按流水线逐层进行:

  1. 主FTB可以覆盖uFTB的早期结果;
  2. TAGE-SC可以覆盖早期条件方向;
  3. ITTAGE和RAS可以覆盖间接/返回target;
  4. IFU预译码使用真实指令检查CFI类型和直接target;
  5. 后端执行得到最终方向和target,发现错误后redirect;
  6. 错误路径指令被清除,正确结果在提交时训练预测器。

主FTB还检查fall-through编码是否合法,并检测multi-hit。预测正确性依赖这些恢复机制,而 不是假设FTB内容永远正确。

13. 完整预测示例​

考虑下面的预测块:

0x1000: add  x3, x4, x5
0x1004: beq x1, x2, A # 条件分支槽0
0x1008: sub x6, x7, x8
0x100c: bne x9, x0, B # 条件分支槽1
0x1010: jal ra, C # 非共享尾槽

FTB读出后形成:

slot0.valid  = 1, offset = 1, target = A
slot1.valid = 1, offset = 3, target = B
tail.valid = 1, offset = 4, target = C, sharing = 0

不同方向组合产生不同next PC:

slot0方向slot1方向最终CFInext PC
taken任意beqA
not-takentakenbneB
not-takennot-takenjalC

这个例子展示了FTB和方向预测器的边界:FTB准备A/B/C并描述三个CFI的位置,方向预测器决定 两条条件分支是否截断路径,JAL在程序真正执行到它时恒taken。

14. 设计取舍​

FTB位于前端关键路径,需要同时权衡容量、延迟和准确率:

  • uFTB用寄存器和全相联比较换取S1快速结果,但容量有限;
  • 主FTB用SRAM和组相联组织扩大覆盖,但结果更晚;
  • target压缩显著降低表项宽度,但需要高位重建逻辑;
  • 一项保存多个CFI提高块级预测能力,但增加slot选择和训练复杂度;
  • 分阶段预测允许后级提高准确率,代价是覆盖发生时需要内部redirect;
  • 保存meta支持精确训练,但会增加FTQ和预测流水线状态量。

性能分析时应分别观察uFTB覆盖率、主FTB覆盖率、方向覆盖、target覆盖和各阶段redirect,不能 只用一个“FTB命中率”解释全部前端行为。

15. 波形和源码阅读路径​

观察一次FTB预测时,建议按数据依赖顺序追踪:

  1. S0请求PC、FTB set和tag;
  2. uFTB s1_hit_oh、命中way和早期br_taken_mask;
  3. 主FTB read_entries/read_tags、total_hits和hit_way;
  4. 选中entry的brSlots/tailSlot/pftAddr/carry;
  5. getTargetVec()生成的targets[];
  6. slot_valids、offsets与fallThroughAddr;
  7. 各方向预测器输出的br_taken_mask;
  8. real_slot_taken_mask、cfiIndex和最终target();
  9. S1、S2、S3结果之间的redirect;
  10. FTQ保存的entry/meta以及提交时update。

对应源码:

  • frontend/FauFTB.scala
    • 32路寄存器表、全相联tag匹配、2位方向计数器和PLRU;
  • frontend/FTB.scala
    • FtbSlot:target压缩和重建;
    • FTBEntry:预测块结构;
    • FTBBank:SRAM、tag比较和way选择;
    • S2/S3:把entry展开到预测结果;
  • frontend/FrontendBundle.scala
    • FullBranchPrediction.fromFtbEntry();
    • allTarget()、target()和cfiIndex;
  • frontend/NewFtq.scala
    • FTQ中的预测保存、真实target和FTBEntryGen更新;
  • frontend/Tage.scala、frontend/ITTAGE.scala、frontend/RAS.scala
    • 条件方向、间接目标和返回地址如何覆盖早期结果。

下一步可阅读预测流水线理解S1/S2/S3覆盖和redirect,再阅读 训练、别名与meta理解FTB、TAGE和ITTAGE各自保存的训练状态。