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”规则:
- 如果B0被预测taken,立即选择
target0,后面的B1和J不再参与本次路径; - 如果B0不跳而B1跳,选择
target1; - 如果条件分支都不跳但存在无条件尾部跳转,选择
targetJ; - 如果没有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,方向由后续预测器补充 |
| 典型结果阶段 | S1 | S2,并在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:
- S0把请求PC送给FTB SRAM;
- SRAM读出该set的4路表项和tag;
- S2将请求tag与4路tag比较;
total_hits形成命中向量;- 正常情况下由
hit_way选择唯一命中表项; - 选中的
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决定:
valid | sharing | 语义 |
|---|---|---|
| 0 | 任意 | 没有尾部CFI |
| 1 | 1 | 作为额外的条件分支槽使用 |
| 1 | 0 | 保存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,同时启动各预测器 | 查表请求 |
| S1 | uFTB完成快速命中;主FTB SRAM返回候选ways | 早期next PC、FTB原始读数据 |
| S2 | 主FTB比较tag、选择way、展开FTBEntry | targets、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错误。
恢复按流水线逐层进行:
- 主FTB可以覆盖uFTB的早期结果;
- TAGE-SC可以覆盖早期条件方向;
- ITTAGE和RAS可以覆盖间接/返回target;
- IFU预译码使用真实指令检查CFI类型和直接target;
- 后端执行得到最终方向和target,发现错误后redirect;
- 错误路径指令被清除,正确结果在提交时训练预测器。
主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方向 | 最终CFI | next PC |
|---|---|---|---|
| taken | 任意 | beq | A |
| not-taken | taken | bne | B |
| not-taken | not-taken | jal | C |
这个例子展示了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预测时,建议按数据依赖顺序追踪:
- S0请求PC、FTB set和tag;
- uFTB
s1_hit_oh、命中way和早期br_taken_mask; - 主FTB
read_entries/read_tags、total_hits和hit_way; - 选中entry的
brSlots/tailSlot/pftAddr/carry; getTargetVec()生成的targets[];slot_valids、offsets与fallThroughAddr;- 各方向预测器输出的
br_taken_mask; real_slot_taken_mask、cfiIndex和最终target();- S1、S2、S3结果之间的redirect;
- FTQ保存的entry/meta以及提交时update。
对应源码:
frontend/FauFTB.scala- 32路寄存器表、全相联tag匹配、2位方向计数器和PLRU;
frontend/FTB.scalaFtbSlot:target压缩和重建;FTBEntry:预测块结构;FTBBank:SRAM、tag比较和way选择;- S2/S3:把entry展开到预测结果;
frontend/FrontendBundle.scalaFullBranchPrediction.fromFtbEntry();allTarget()、target()和cfiIndex;
frontend/NewFtq.scala- FTQ中的预测保存、真实target和
FTBEntryGen更新;
- FTQ中的预测保存、真实target和
frontend/Tage.scala、frontend/ITTAGE.scala、frontend/RAS.scala- 条件方向、间接目标和返回地址如何覆盖早期结果。
下一步可阅读预测流水线理解S1/S2/S3覆盖和redirect,再阅读 训练、别名与meta理解FTB、TAGE和ITTAGE各自保存的训练状态。