FTB项与完整预测结果接口
Categories:
FTB 项
FTB 项是香山中分支预测块的核心数据结构,它存储了生成一个分支预测块所需的信息。在BPU进行预测时,初始的分支预测块首先由一个读出的 FTB 项生成。然后,这个分支预测块会传递到后续的预测器中,后续的预测器会读取其中的信息并加以修改,生成最终的预测结果。
因此,要理解分支预测块的结构,首先需要理解FTB项的结构。一个FTB项对应着一个分支预测块,其大致结构如下:
首先,需要明确一个信息:无论是分支预测块还是FTB项,它们可以容纳的指令数量都被设定为一个特定的上限(在香山当前版本中为16条RVC指令),被称为最大预测长度。这意味着,如果我们需要记录分支预测块内某一条指令的位置,可以使用一个固定长度的位向量,来指定这条指令相对于预测块起始地址的偏移量。
分支预测块执行流程的决定因素是其中的分支指令信息。其余的指令都被视为普通指令,不影响程序的执行流。因此,在一个分支预测块中,我们只需记录下其中的分支指令位置即可,而普通指令的位置我们并不关心。
因此,FTB项中定义了两种类型的分支指令槽(slot)——brSlots
和tailSlot
,用于存储分支预测块中的分支指令。目前香山版本中,brSlots
只含有一个 slot,而 tailSlot
则是一个单独的 slot,总计含有两个 slot。
在最大预测长度内的指令中,如果出现了分支指令,FTB项会将其记录在对应的slot内,并将该slot置为有效。如果分支指令出现过多,达到了FTB项的容纳上限,那么超出上限的分支指令则会交给下一个FTB项来存储。在每个 slot中,我们会记录一条分支指令相对于预测块起始地址的偏移量(offset),同时还会记录其跳转目标地址等信息。
唯一的 tailSlot
在RISC-V中,分支指令主要分为两种类型:条件分支和无条件跳转。因此,对于一个分支预测块来说,最多只会包含一条无条件跳转指令。因为一旦执行到这条指令,程序的执行流就会发生改变,后续的指令将不再执行。因此,我们定义了一种类型的 slot 叫做 tailSlot
,专门用来存储这条无条件跳转指令。而对于条件分支指令,则将它们存储在brSlots
中。
tailSlot
如其名,位于整个预测块中的最后一个 slot。这也是因为一旦填充了无条件跳转指令,程序必定跳转,之后的指令就由其他预测块进行处理,因此后续的指令情况我们无需关心。但在无条件跳转指令之前的指令中,我们需要关心是否存在条件分支指令,因为条件分支指令既有可能跳转,也有可能不跳转。因此,我们需要记录下条件分支指令的相关信息。
tailSlot 共享
考虑一种情况:如果从预测块起始PC开始直到最大预测长度,都没有出现无条件跳转指令,反而出现了两条条件分支指令,这样一来,tailSlot
就会空闲,而第二条条件分支指令又无法存储,造成空间的浪费。
为了解决这个问题,香山采用了一种方法:设置一个 sharing
标记。我们可以直接将第二条分支指令存储到tailSlot
中,并将sharing标记设置为真,表示第二条条件分支指令共享了无条件跳转指令的 tailSlot
。这样,就有效利用了tailSlot的空间。
预测块中的 isCall
、isRet
和isJalr
字段是为tailSlot
服务的。如果tailSlot中记录的是无条件跳转指令,这几个字段会进一步指示该跳转指令的类型。在FTB项中还有一个字段 always_taken
,用来记录每个Slot中存储的条件分支指令是否总是被预测为跳转。如果是的话,后续的预测器可以直接采用这一预测结果。
通过FTB项,我们可以得知一个分支预测块中的指令情况,包括分支指令的位置、类型等。这些信息会交给后续的预测器,由后续的预测器来预测更为精确的跳转目标、是否跳转等信息。
FTB 项完整结构 (FTBEntry)
接口定义: src/main/scala/xiangshan/frontend/FTB.scala
本节中描述了 FTB 项的完整结构定义,其所含的信号如下:
- valid: FTB表项是否有效。
- 接口类型:
Bool
- 接口类型:
- brSlots: 条件分支指令槽。
- 接口类型:
Vec(numBrSlot, new FtbSlot(BR_OFFSET_LEN))
(接口列表详见FtbSlot)
- 接口类型:
- tailSlot: 无条件跳转指令槽。
- 接口类型:
new FtbSlot(JMP_OFFSET_LEN, Some(BR_OFFSET_LEN))
(接口列表详见FtbSlot)
- 接口类型:
- pftAddr: 预测块的结束地址。
- 接口类型:
UInt(log2Up(PredictWidth).W)
- 接口类型:
- carry: 预测块的结束地址进位。
- 接口类型:
Bool()
- 接口类型:
- isCall: 无条件跳转指令槽中的指令为 call 指令。
- 接口类型:
Bool()
- 接口类型:
- isRet: 无条件跳转指令槽中的指令为 ret 指令。
- 接口类型:
Bool()
- 接口类型:
- isJalr: 无条件跳转指令槽中的指令为 jalr 指令。
- 接口类型:
Bool()
- 接口类型:
- last_may_be_rvi_call: 无条件跳转指令槽中的指令可能为一个 RVI 类型的 call 指令信号。
- 接口类型:
Bool()
- 接口类型:
- always_taken: 该预测块中的每个分支指令是否总是被预测为 Taken。
- 接口类型:
Vec(numBr, Bool())
- 接口类型:
说明:pftAddr 和 carry
在这里,pftAddr
是Partial Fallthrough Addr的缩写。Fallthrough Addr 表示,如果预测块中没有跳转,那么程序将会顺序执行到达的地址。换句话说,如果一条无条件跳转指令的偏移为5,那么 Fallthrough Addr 对应的偏移便是6。这一信号主要用于获取函数调用后程序的返回地址,可以将这一概念理解为预测块的结束地址。
Partial 表示部分地址,这是由地址的表示方法决定的。在这里,地址的表示方法如下:
pc: | ... |<-- log(predictWidth) -->|<-- log(instBytes) -->|
^ ^
| |
carryPos instOffsetBits
pftAddr
便是只记录了中间的偏移部分(即长度为 log(predictWidth) 的部分),之后结合当前 PC 才能生成完整 PC。但由于有可能产生进位,因此单独记录一个 carry
位,carryPos 是预测块中的指令地址中 会产生进位的位置
另外,last_may_be_rvi_call
也是对该地址的一个辅助信号,表示无条件跳转指令槽中的指令是一个RVI类型的 call 指令。由于pftAddr在计算时默认认为指令长度为压缩指令长度,因此计算结束地址只会加 2 字节。但如果实际的call指令并不是压缩指令,就会导致返回地址计算错误。RAS会根据这一信号修正这个错误。
分支预测槽 (FTBSlot)
接口定义: src/main/scala/xiangshan/frontend/FTB.scala
该接口定义了 FTB 表项中 slot:
- offset: 该 slot 中指令相对于预测块起始地址的偏移
- 接口类型:
UInt(log2Ceil(PredictWidth).W)
- 接口类型:
- lower: 跳转目标地址的低位。
- 接口类型:
UInt(offsetLen.W)
- 说明:这里设置 lower 为 12 位或 20 位,因为分支指令的寻址能力为 12 位,而跳转指令的寻址能力为 20 位
- 接口类型:
- tarStat: 跳转后的 pc 高位是否进退位
- 接口类型:
UInt(TAR_STAT_SZ.W)
(TAR_STAT_SZ = 2) - 说明:跳转目标地址由当前 PC 的高位、tarState 以及 lower 字段共同计算得出,其中,lower 字段直接存储了跳转目标地址的低位。当前的 PC 高位经过 tarStat 指导进行进退位之后,直接与 lower 拼接便可得到真正的跳转目标地址。tarStat 共有三个可能的取值:0 - 不进不退, 1 - 进位, 2 - 退位。
- 接口类型:
- sharing: 无条件跳转指令槽中存储了一条条件分支指令。
- 接口类型:
Bool()
- 接口类型:
- valid: slot 有效位。
- 接口类型:
Bool()
- 接口类型:
完整分支预测结果 (FullBranchPrediction)
- 接口定义:
src/main/scala/xiangshan/frontend/FrontendBundle.scala
该接口定义了完整的分支预测结果,每流水级预测结果中都会包含该接口。
完整分支预测结果接口与 FTB 项相差无几,并最初由 FTB 项生成。两个Slot被拆分成了单独的信号:slot_valids
, targets
, offsets
, is_br_sharing
等,另外增加了若干字段例如 br_taken_mask
, jar_target
便于后续预测器提供精确预测结果,另外 hit
表示 FTB 项是否命中,即本轮预测中的 PC 索引到了某个 FTB 项。
完整接口列表如下:
-
hit FTB表项是否命中
- 接口类型:
Bool()
- 接口类型:
-
slot_valids slot有效位,表示 ftb 项中的每个 slot 是否有效
- 接口类型:
Vec(totalSlot, Bool())
- 接口类型:
-
targets 每个 slot 对应的跳转目标地址
- 接口类型:
Vec(totalSlot, UInt(VAddrBits.W))
- 接口类型:
-
offsets 每个 slot 中的指令相对于预测块起始地址的偏移
- 接口类型:
Vec(totalSlot, UInt(log2Ceil(PredictWidth).W))
- 接口类型:
-
fallThroughAddr 预测块的结束地址
- 接口类型:
UInt(VAddrBits.W)
- 接口类型:
-
fallThroughErr FTB项中记录的
pftAddr
有误- 接口类型:
Bool()
- 接口类型:
-
is_jal 预测块内包含jal指令
- 接口类型:
Bool()
- 接口类型:
-
is_jalr 预测块内包含jalr指令
- 接口类型:
Bool()
- 接口类型:
-
is_call 预测块内包含call指令
- 接口类型:
Bool()
- 接口类型:
-
is_ret 预测块内包含ret指令
- 接口类型:
Bool()
- 接口类型:
-
last_may_be_rvi_call 无条件跳转指令槽中的指令可能为一个 RVI 类型的 call 指令信号。
- 接口类型:
Bool()
- 接口类型:
-
is_br_sharing 最后一个slot (tailSlot) 中存储的是条件分支指令信号
- 接口类型:
Bool()
- 接口类型:
-
br_taken_mask 分支预测结果,每个分支(slot)对应一位,表示该分支是否被预测为 taken
- 接口类型:
Vec(numBr, Bool())
- 接口类型:
-
jalr_target 本预测块中的 jalr 的跳转目标
- 接口类型:
UInt(VAddrBits.W)
- 接口类型: