这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

子模块文档

本部分文档将会详细介绍香山分支预测单元的每一个模块,包括 BPU 顶层和五个子预测器。

本部分文档将会详细介绍香山分支预测单元的每一个模块,包括 BPU 顶层和五个子预测器。

在每一个模块的文档中,我们将会详细介绍该模块在香山分支预测单元中所发挥的作用,以及该模块的算法原理、模块结构和时序等。

负责一个模块验证的同学需要重点阅读对应文档并结合代码进行理解,其他文档亦可进行阅读以帮助你对香山分支预测单元整体功能的理解。理解过程中,你可能需要时刻回顾之前文档中所描述的基础设计思想及接口信号的含义。

1 - BPU 顶层模块

BPU 顶层整体的功能和结构已经在之前的文档中粗略的描述,对于验证 BPU 顶层的同学来说,可能还需要更加细致的描述。由于 BPU 顶层功能较多,本节将 BPU 划分为了几大功能点进行更进一步的描述。但由于 BPU 顶层细节过多,更进一步的细节需参照代码进行理解。

生成器维护方法

通过香山的基础设计文档,我们知道,BPU 顶层是通过一个个生成器来维护 s0 周期的各类变量的,例如 PC、分支历史等,并且其核心概念是,通过预测结果重定向信号决定采用哪个流水级的结果。

BPU 顶层中所有的生成器共有 6 个:

  • npcGen 维护 pc
  • ghistPtrGen 维护全局历史指针
  • ghvBitWriteGens 维护全局历史写入数据
  • foledGhGen 维护折叠历史
  • lastBrNumOHGen 维护上周期最后一个生效的分支指令位置
  • aheadFhObGen 维护分支历史最老位

其中,除了 npcGen 以外,其余生成器都会在本文档中进行介绍。本节中我们着重探讨一下生成器的产生下一次预测的方法。

在代码中你可以看到生成器用类似下面这种方式进行定义:

val npcGen = new PhyPriorityMuxGenerator[UInt]

接下来,代码通过多条语句对生成器的数据来源进行注册:

npcGen.register(true.B, reg ...)
npcGen.register(s1_valid, s1_target, ...)
npcGen.register(s2_redirect, s2_target, ...)
npcGen.register(s3_redirect, s3_target, ...)
npcGen.register(do_redirect.valid, do_redirect.bits.cfiUpdate.target, ...)

每一行被称作一次注册,在一次注册中第一个信号参数是数据有效信号,第二个信号参数包含具体的数据。 生成器的优先级也是按照注册的顺序来决定,越往后优先级越高,因此,同一时刻的优先级从低到高依次为:

  • s0 阻塞的数据
  • 根据 s1 预测结果更新后的数据
  • 根据 s2 预测结果更新后的数据
  • 根据 s3 预测结果更新后的数据
  • BPU 外部重定向中的数据

这样一来,我们就可以在预测结果重定向有效时,避免采用较早流水级的预测结果,而采用纠正后的预测结果。也使得我们可以将外部重定向请求作为最高优先级去处理。

我们可以得出所有生成器产生 s0 信号的方法:在所有数据有效信号中,如果只有一个有效的,则选取它对应的数据,如果有多个数据有效信号生效,则选取优先级最高的数据。

全局分支历史

我们知道,全局分支历史在 BPU 顶层进行维护,维护的策略与 PC 的维护策略一致。即在每个阶段流水级预测结果产生之后,会根据相应信号对全局分支历史进行更新。

顶层为了维护全局分支历史定义了两组信号

  • ghv 存储了全局分支历史(最大长度 256)
  • ghist_ptr 全局分支历史指针,指向全局分支历史当前的位置

s0_pc, s1_pc, s2_pc 一样,BPU 顶层为全局历史指针也维护了每一阶段的信号 s0_ghist_ptr, s1_ghist_ptr, s2_ghist_ptr ,但 ghv 中的内容是位置固定的,我们仅通过 ghist_ptr 来定位当前的全局分支历史从哪里开始。

通过 ghist_ptr 计算当前全局分支历史

ghist_ptr 的使用仅在 BPU 顶层可见,而我们向子预测器传入的,是全局历史寄存器中的数据根据 ghist_ptr 所移位之后的全局分支历史。在子预测器拿到的全局分支历史中,最低位对应全局分支历史的最新位,最高位对应全局分支历史的最老位。

那么是怎样进行移位的呢,我们首先来看一下全局历史是怎样在 ghv 中进行存储的。

|===== ghist =====>| =======>|
n                  ^         0
                   ghist_ptr

如上图所示,序列表示整个 ghv 寄存器,ghist_ptr 指向 ghv 中的某个位置,这个位置代表了全局分支历史的最新位。当需要添加一位新的全局历史记录时,首先将 ghist_ptr 减 1,然后将该位写在其所指向的位置。当 ghist_ptr 减到 0 后,又会循环回来指向最高位,因此会覆盖之前写入的全局分支历史。

但不管怎样,从 ghist_ptr 所指向的位置开始,指针越增加,历史越老。因此,当我们需要计算当前全局分支历史时,只需要将 ghv 寄存器循环右移 ghist_ptr 位即可。

全局分支历史的更新

全局分支历史的更新策略与 pc 更新的策略一致,在每一个流水级都需要根据当前流水级的预测结果生成一个 当前流水级的指针及 ghv 的更新说明,最终都送给相关生成器来处理。

ghv 的更新说明即 用于指导 ghv 寄存器的更新的某些信息。香山 BPU 中维护了两个信息来完成这一职责:

  • ghv_wdata 需要向 ghv 中写入的数据
  • ghv_wens 写入位掩码

最终更新时,只需要将 ghv_wens 所标识的位写入 ghv_wdata 的对应位即可。

因此每个流水级需要负责产生三组信息:ghist_ptr,ghv_wdata, ghv_wens

具体地,预测结果中最多含有两条分支指令,我们只需将实际情况来设置这几个信息即可,举几种情况的例子:

  • 只有第一个槽有效,并且其中条件分支指令被预测为不跳转。则将 ghv_wens 的下一个位置置 0 ,ghv_wens 的对应位置置 1, 同时 ghist_ptr 减一。
  • 两个槽都存放了条件分支指令,第一条被预测为不跳转, 第二条被预测为跳转。此时 ghist_ptr 应该减二,并且其他两个信息应该指示向 ghv 中写入 01。

此处在生成器中只维护了一个 ghv_wdata 信息(通过 ghvBitWriteGens 生成器维护),ghv_wens 并没有通过生成器来维护。这是因为此处使用了一个小技巧,使用了生成器的 ghv_wdata 最终输出的是被选中阶段的结果,而 ghv_wens 将所有阶段的 ghv_wens 进行按位或来使用。

这是基于如下考虑的:

  • 如果较晚的流水线阶段有效。全局历史指针被恢复到较老的位置,即便被早期流水的 ghv_wens 修改了较新位置的历史也没关系。
  • 如果较早的流水线阶段有效。全局历史指针继续向较新的位置更新,而后期流水线会因为 redirect 未生效而不把 ghv_wens 置位。

分支折叠历史

送入预测器的分支折叠历史也是由顶层 BPU 来维护的,BPU 为了缩短折叠历史的更新延迟,维护了很多变量,来支持分支折叠历史的快速更新,我们将会重点介绍这一策略,并介绍每一个变量的作用。

在开始之前,我们先来看一下分支折叠历史是怎样定义的,结构又是怎样的。

分支折叠历史

如果你查看了 BPU 全局接口的文档,你就会知道,子预测器拿到的是一个不同长度位向量的数组,代表了各种长度的折叠历史,而这些折叠历史都是由全局分支历史压缩而成

对于全局分支历史,我们有一个存放全局分支历史的寄存器,长度为 256。为了方便举例,我们假设全局分支历史的长度为 15 位,并且经过移位之后,我们可以拿到一个这样的分支历史:最低位是最新的历史记录,最高位是最老的历史记录。

此时如果我们需要用这 15 位,产生一个 6 位的折叠历史,会使用异或的策略进行压缩,具体过程是这样的:

    h[5]         h[4]       h[3]    h[2]   h[1]   h[0]
    h[11]        h[10]      h[9]    h[8]   h[7]   h[6]
^                                   h[14]  h[13]  h[12]
---------------------------------------------------------------
    h[5]^h[11]   h[4]^h[10]         ...           h[0]^h[6]^h[12]

即将其按照上面的方式排列之后,将每一位上的值进行异或,结果便是求出的长度为 6 的折叠历史。

分支折叠历史更新方法

此时我们想要对这一分支折叠历史进行更新,当我们向全局分支历史插入一位新历史时,是从最低位插入的,也就是说原来的 h[0] 变为了 h[1],如果我们想求此时的分支折叠历史,只需要再进行一遍异或运算。但这样的效率太低了,因为异或的操作有可能变得特别长,我们可以来探寻一下一次更新对分支折叠历史的影响。

上述例子中,插入一位新历史之前,6 位折叠历史的生成是按照下面这种排列生成的

h[5]   h[4]   h[3]  h[2]  h[1]  h[0]
h[11]  h[10]  h[9]  h[8]  h[7]  h[6]
                    h[14] h[13] h[12]

插入一位新历史之后变成了下面这样

h[4]   h[3]   h[2]  h[1]  h[0]  h[new]
h[10]  h[9]   h[8]  h[7]  h[6]  h[5]
           (h[14])  h[13] h[12] h[11]

我们可以发现一些规律

插入前:
h[5]   {h[4]   h[3]  h[2]  h[1]  h[0] }
h[11]  {h[10]  h[9]  h[8]  h[7]  h[6] }
       {             h[14] h[13] h[12]}
插入后:
{h[4]   h[3]   h[2]  h[1]  h[0] } h[new]
{h[10]  h[9]   h[8]  h[7]  h[6] } h[5]
{           (h[14])  h[13] h[12]} h[11]

大括号中的内容发生了整体的左移,h[5] 和 h[11],由最高位变到了最低位。那么表现在压缩后的历史上不就是我们常见的循环左移吗!

但其中有且仅有两个位的值发生了变化,一个是新加入的 h[new],一个是被舍弃掉的 h[14]。h[new] 肯定在第一位,被舍弃的位置也是固定的。因此我们想要完成一次更新,只需要知道 新插入历史的值 和 前一次历史的最老位即可。循环移位后,将这两个位置根据实际情况进行一次修改便可拿到更新后的折叠历史。

更新方法实现

BPU 顶层为了实现这种更新,正是通过维护最老位,这通过两个额外的变量来实现:

  • ahead_fh_oldest_bits 全局分支历史的最老位,还额外往前存储了若干位
  • last_br_num_oh 上一次预测最后一个生效的分支指令在第几个槽

在这里有一处为时序所优化的点,因为当流水级的预测结果出来时,全局历史指针才能通过跳转情况进行更新,等到全局历史指针更新完再来更新最老位会增加时延。因此我们将跳转情况维护起来,等到下一周期用的时候再来用跳转情况更新最老位。

此时的最老位也需要多往前维护几位,因为在使用时,利用跳转情况更新后,前面较新的几位就会变成最老位了。

所以与折叠历史相关的生成器共有三个:foldedGhGen, lastBrNumOhGen, aheadFhObGen

每次折叠历史更新时需要的信息分别是

  • 更新前的折叠历史信息
  • 全局分支历史最老位(ahead_fh_oldest_bits)
  • 上次预测的跳转情况(last_br_num_oh)
  • 本次更新是否有指令跳转
  • 本次更新的跳转情况:最后一个生效的分支指令在第几个槽

每次折叠历史更新时,都需要根据 last_br_num_ohahead_fh_oldest_bits 求出真正的最老位,然后通过最老位与本次更新的跳转情况将其中的若干位进行修改,最后进行循环左移,便完成了更新操作。

流水线控制方法

流水线控制是 BPU 功能的核心,逻辑也最为复杂,BPU 顶层中所有的流水线控制信号如下:

  • s1_valid, s2_valid, s3_valid 表示对应流水数据生效
  • s1_ready, s2_ready, s3_ready 表示对应流水已准备好继续上一流水级的预测
  • s1_component_ready, s2_component_ready, s3_component_ready 表示对应流水子预测器的 ready 情况
  • s0_fire, s1_fire, s2_fire, s3_fire 握手成功信号,表示该流水数据生效,并成功传递给了下一流水
  • s1_flush, s2_flush, s3_flush 表示当前流水是否需要冲刷
  • s2_redirect, s3_redirect 表示当前流水在 fire 的同时,是否预测结果不同,需要产生预测结果重定向

valid, ready 与 fire

我们会逐步来介绍每个信号的作用,首先我们来看 fire 信号,这一信号表示的含义是流水线握手成功,数据成功传给了下一流水。这标志着本周期结束时,本流水级的预测也随之结束,下周期开始时,下一流水级的预测即将开始。

这需要两个条件:

  1. valid 本流水级的数据是有效的。
  2. readycomponent_ready 分别指示了 BPU 顶层与预测器的下一流水级是否就绪。

当这两个信号同时置高时,fire 信号有效,表示握手成功。如果我们单独把一次预测拿出来,那么时序应该是这样的(实际中,大多数时间每个流水线都是一直有效的):

上文中提到的四组信号,除了 component_ready 是由预测器输出,其余信号皆需 BPU 顶层来维护,而最终暴露给子预测器的,只有 fire 一组信号。

我们接下来以 s2 为例分别来看每个信号是如何维护的。

ready 信号

s2_ready := s2_fire || !s2_valid

该赋值语句是一个组合电路赋值,也就是说,s2_ready 信号是与本周期的 s2_fires2_valid 直接相关联的,分为以下两种情况:

  • s2_valid 信号在本周期无效,说明 s2 流水级目前是空的,自然可以接受新的数据,则 s2_ready 有效
  • s2_valid 信号在本周期有效,说明 s2 流水级目前有数据还未传递给下一级,但如果 s2_fire ,那么本周期就会传递给下一级。此时 s2_ready 有效,刚好指示数据可以在下一拍流入。

valid 信号

s2_valid 信号目前为止维护是相对简单的,与 s1_fire 信号和 s2_ready 信号相关。其关系为:

  • s1_fire 有效,说明数据传进来,下一周期 s2_valid 有效。
  • s2_fire 有效,说明数据流出去,下一周期 s2_valid 无效。

fire 信号

fire 信号相对特殊,但对于中间的流水级来说,维护非常简单,例如

s2_fire := s2_valid && s3_components_ready && s3_ready

只需考虑当前流水级的 valid 和下一流水级的 ready 即可。

但对 s0_fire 来说,没有 valid 信号,因此其直接等于 s1_components_ready && s1_ready

对于 s3_fire 来说,没有下一级的 ready 信号,因此其直接等于 s3_valid

加入 flush 和 redirect

我们知道,当流水线出现预测结果不同时,需要产生预测结果重定向信号,并且将之前的流水线清空。flushredirect 正是在做这两项工作。redirect 表示当前流水级是否需要重定向,flush 则表示当前流水级是否需要冲刷。

redirect 信号

s2_redirect 的产生方式如下:

s2_redirect := s2_fire && s2_redirect_s1_last_pred

也就是说,当 s2_fire 时,并且 s2 的预测结果与上一周期保存的 s1 预测结果不同时,这个信号便有效。之后该信号将会连接到子预测器的输入,与 BPU 预测结果的输出,分别指导子预测器和 FTQ 的状态恢复。

flush 信号

flush 信号是用于指导流水线冲刷的,例如 s3 重定向有效时,说明错误的预测结果已经流入流水线, s1 和 s2 此时全都是基于错误的结果来预测的,因此需要进行流水线冲刷,使之前的流水级都暂停工作,等待新的预测结果流入。

具体地,他们之间有如下关系:

 s2_flush := s3_flush || s3_redirect
 s1_flush := s2_flush || s2_redirect

也就是说,某个流水级 redirect 有效,之前的流水级的 flush 全都会被置为有效。那么 flush 具有什么作用呢?答案是指导 valid 信号,如果本周期 valid 信号有效,但 fire 信号未生效,说明错误的数据没有被下一流水取走,此时 flush 有效后,在下一周期 valid 就会立即变为无效,以这种方式来避免错误数据长期存储在流水线中。

但 flush 信号对 valid 信号的影响,也根据每一个流水级的不同而有一定差异。例如

  • s1 流水级。虽然 flush 有效,但是如果此时 s0_fire 有效,说明新数据流入,那么下周期 valid 依然有效。
  • s2 流水级。flush 有效,那么必定下周期不会 valid(因为 s1 也肯定被 flush),此时就可以直接将 valid 置为无效。但还存在一种特殊情况,s2_redirect 发生时,s2_flush 并没有被置为有效,此时如果发生 s1_fire,s1 的错误预测结果也可能流入,此时还需根据 s1_flush 信号来决定 s2_valid 是否有效。

flush 的使用较复杂,更详细的细节还需参考代码进行理解。

重定向恢复逻辑

当 FTQ 发往 BPU 的重定向请求生效时就说明所有流水级的预测结果都是不正确的,此时应该将所有流水级进行冲刷,这可以通过将 s3_flush 置为有效来实现。因此有

 s3_flush := redirect_req.valid

在 BPU 中,重定向请求送入后被延迟一周期才正式使用,因此 s1_valid 的信号也需要对 flush 信号的响应做出一些改变。当重定向请求(延迟前)有效时,s1_valid 下周期立即被置为无效,不需要再去参考 s0_fire 信号了。

此时 npcGen 等生成器也需要直接去采用重定向请求中的数据来生成,这就相当于将 BPU 的状态重定向到出错之前状态的过程。但注意 BPU 默认的重定向等级为 flushAfter ,即重定向请求会对应一条预测错误的指令,而 BPU 会认为这条指令虽然预测错了,但是已经被纠正并且交由后端执行了,因此下一次预测可以直接从下一条指令开始。

所以在重定向恢复时,不仅需要将重定向接口中的信息恢复,还需要将这条预测错误的指令的执行情况也更新到历史中去。

2 - uFTB 分支预测器

uFTB 简介

uFTB 是香山所有预测器中第一个预测器,也是其他预测器产生预测结果的基石。uFTB 工作在 s1 阶段,它获取到 s1_pc 后能在本周期内产生预测结果,并在 s1 通道输出,其他通道不予更改。分支指令的位置、指令的跳转目标都是由它来提供,后续预测器也将会根据这一预测结果进行进一步的预测。

而其本质则是一个 FTB 项缓存,其中存储了 FTB 项,基础的预测结果将直接由读出的 FTB 项生成。

因此,在你开始文档的阅读之前,请确保你理解了 FTB 项和含义以及预测结果接口的具体细节。

uFTB 功能简介

  • 缓存FTB项,生成一周期预测结果 uFTB 中维护了一个小型的 FTB 项缓存,拿到 PC 之后,会在一周期之内读出 PC 所对应的 FTB 项,并从 FTB 项生成 s1 阶段预测结果。
  • 维护两比特饱和计数器,提供基础条件分支结果 uFTB 中为 FTB项缓存的每一行都维护了对应的两比特饱和计数器,其方向预测结果会反映在 uFTB 的预测结果输出中。
  • 根据更新请求更新 FTB 缓存和两比特饱和计数器

uFTB 缓存结构

如上所述,uFTB 本质上是一个存储了 FTB 项的小型缓存,其大致结构如下图所示。

在当前版本的香山中,uFTB 共有 32 个缓存行,每个缓存行被称作 FauFTBWay,一个缓存行中可以存储一个 FTB 项。

当 s1 流水有效时,uFTB 会使用 s1_pc 来决定读出 uFTB 缓存的哪一项。缓存是根据 PC 中的 tag 字段来索引,tag 字段被定义为 pc[16: 1],即在 PC 中截取了 16 比特作为标识来匹配缓存中的某一行。

缓存中的每一行,即 FauFTBWay 中的数据请求接口有三项:

  • req_tag 输入 pc 中截取的 tag 标识
  • resp 输出本行所存储的 FTB 项
  • resp_hit 输出指示本行的 FTB 项是否与 req_tag 匹配

uFTB 会将 tag 连接到缓存的每一行的数据请求接口,并根据 resp_hit 信号,选出命中的那一个 FTB 项。后续将会根据这一 FTB 项生成完整预测结果。

两比特饱和计数器

uFTB 中为 FTB 项缓存的每一行都维护了对应的两比特饱和计数器。我们知道,在一个 FTB 项最多可以存储两个条件分支指令,因此每一行对应的两比特饱和计数器也有两个,负责为其中的条件分支指令提供粗略的预测结果。

uFTB 在索引到 FTB 项时,也会索引到对应的两比特饱和计数器。

当预测结果生成时,会根据 FTB 项与其对应的两个两比特饱和计数器中的内容来产生。

预测结果生成

uFTB 通过 s1_pc 索引到对应的 FTB 项以及两个两比饱和计数器之后,需要根据他们产生预测结果。uFTB 产生的预测结果,将在 s1 流水有效时,通过 s1 通道进行输出,对于 s2 以及 s3 通道,uFTB 不予更改。

s1 预测结果中的信号产生方式可参考以下列表:

  • hit FTB表项是否命中

    • 生成方式:FauFTBWay 中的 resp_hit 信号,存在一个有效
  • slot_valids slot有效位,表示 ftb 项中的每个 slot 是否有效

  • targets 每个slot对应的跳转目标地址

  • offsets 每个slot中的指令相对于预测块起始地址的偏移

  • is_jal 预测块内包含jal指令

  • is_jalr 预测块内包含jalr指令

  • is_call 预测块内包含call指令

  • is_ret 预测块内包含ret指令

  • last_may_be_rvi_call 预测结果预测块末尾可能为一个RVI类型的call指令信号

  • is_br_sharing 最后一个slot (tailSlot) 中存储的是条件分支指令信号

    • 生成方式: FTB 项中对应字段导出
  • fallThroughErr FTB项中记录的 pftAddr 有误

    • 生成方式:比较 pftAddr 代表的预测块结束地址是否大于预测块的起始地址,如果小于,则代表出现错误,此信号置为有效。这种情况可能会发生在 pc 索引到错误的 FTB 项的情况。
  • fallThroughAddr 预测块的结束地址

    • 生成方式:如果 fallThroughErr 无效,则根据 pftAddr 生成,否则将其设置为起始地址 + 预测宽度。
  • br_taken_mask 分支预测结果,每个分支(slot)对应一个 bit,表示该分支是否被预测为 taken

    • 生成方式:根据 FTB 项中的 always_taken 字段和两比特饱和计数器指示结果生成。
  • jalr_target 本预测块中的 jalr 的跳转目标

    • 生成方式:FTB 项中 tailSlot 中的跳转目标。

uFTB 更新

uFTB 的更新涉及 FTB 项缓存的更新,以及两比特饱和计数器的更新,而更新的内容都是通过更新接口来获取的。

在 uFTB 预测器中,缓存和两比特饱和计数器的读与写并不冲突,因此我们无需考虑读和更新之间的时序冲突问题,可以将他们看做是独立的两部分。

FTB 缓存更新

FTB 缓存的更新过程很简单,更新通道中已经为我们指定好了 pc 以及新生成的 FTB 项,只需将其写入到缓存的指定位置即可。

FTB 缓存的更新需要两个周期:

  • 在第一周期,通过 update 中的信号计算出如下内容:
    • 更新请求对应的预测块在缓存的哪一行 更新请求中的 pc 截取 tag 后被发送到 FauFTBWay 的更新请求通道,根据每一行所返回的 hit 信号计算
  • 在第二周期,根据第一周期求出的需要更新的位置进行更新,如果无任何一行命中,则 uFTB 会使用 伪 LRU 替换算法 选出需要写入的行。
    • 具体的,更新通道中的 ftb_entry 信号组包含了新 FTB 项的完整信息,将该信息发送到需要更新的某一个缓存行即可。

两位饱和计数器更新

两位饱和计数器的更新则需要结合后续程序的真实执行情况,以及 FTB 项中记录的分支指令信息来进行更新了,而这些信息都可以从更新通道中进行获取。

两位饱和计数器的更新也需要两个周期:

  • 在第一周期,计算出槽中哪些条件分支指令 对应的两位饱和计数器 需要被更新,这需要满足以下条件:
    • 当前分支指令槽有效,并且存放了一条条件分支指令
    • 当前分支指令槽并没有被标识为 always_taken。(因为被标识为 always_taken 后不会采用两位饱和计数器的结果)
    • 当前分支指令槽不在实际发生了跳转的分支指令槽之后。
  • 在第二周期,更新饱和计数器
    • 根据第一周期生成的需要更新的掩码,以及更新通道中的 br_taken_mask 信息进行更新。

接口列表

信号类型 信号位 信号名 信号描述
input clock 输入时钟
input reset 复位信号
input [35:0] io_reset_vector 用于reset时,reset s1_pc_dup_0 提供的值
input [40:0] io_in_bits_s0_pc_0 输入位s0_pc 的 第0个复制
input [40:0] io_in_bits_s0_pc_1 同上 第1个
input [40:0] io_in_bits_s0_pc_2 同上 第2个
input [40:0] io_in_bits_s0_pc_3 同上 第3个
output [40:0] io_out_s1_pc_0 输出s1_pc 的 第0个复制
output [40:0] io_out_s1_pc_1 同上 第1个
output [40:0] io_out_s1_pc_2 同上 第2个
output [40:0] io_out_s1_pc_3 同上 第3个
output io_out_s1_full_pred_0_br_taken_mask_0 solt 0 是否被预测为 always taken
output io_out_s1_full_pred_0_br_taken_mask_1 solt 1 是否被预测为 always taken
output io_out_s1_full_pred_0_slot_valids_0 solt 0 是否启用
output io_out_s1_full_pred_0_slot_valids_1 solt 1 是否启用
output [40:0] io_out_s1_full_pred_0_targets_0 solt 0 对应的跳转目标地址
output [40:0] io_out_s1_full_pred_0_targets_1 solt 1 对应的跳转目标地址
output [3:0] io_out_s1_full_pred_0_offsets_0 solt 0 中分支指令相对于地址块起始pc的偏移
output [3:0] io_out_s1_full_pred_0_offsets_1 solt 1 中分支指令相对于地址块起始pc的偏移
output [40:0] io_out_s1_full_pred_0_fallThroughAddr 预测块的结束地址
output io_out_s1_full_pred_0_is_br_sharing solt 1(无条件跳转)是否被共享为有条件跳转指令
output io_out_s1_full_pred_0_hit
output io_out_s1_full_pred_1_br_taken_mask_0 类似 io_out_s1_pc_1 io_out_s1_full_pred_0的复制
output io_out_s1_full_pred_1_br_taken_mask_1
output io_out_s1_full_pred_1_slot_valids_0
output io_out_s1_full_pred_1_slot_valids_1
output [40:0] io_out_s1_full_pred_1_targets_0
output [40:0] io_out_s1_full_pred_1_targets_1
output [3:0] io_out_s1_full_pred_1_offsets_0
output [3:0] io_out_s1_full_pred_1_offsets_1
output [40:0] io_out_s1_full_pred_1_fallThroughAddr
output io_out_s1_full_pred_1_is_br_sharing
output io_out_s1_full_pred_1_hit
output io_out_s1_full_pred_2_br_taken_mask_0 同上
output io_out_s1_full_pred_2_br_taken_mask_1
output io_out_s1_full_pred_2_slot_valids_0
output io_out_s1_full_pred_2_slot_valids_1
output [40:0] io_out_s1_full_pred_2_targets_0
output [40:0] io_out_s1_full_pred_2_targets_1
output [3:0] io_out_s1_full_pred_2_offsets_0
output [3:0] io_out_s1_full_pred_2_offsets_1
output [40:0] io_out_s1_full_pred_2_fallThroughAddr
output io_out_s1_full_pred_2_is_br_sharing
output io_out_s1_full_pred_2_hit
output io_out_s1_full_pred_3_br_taken_mask_0 同上
output io_out_s1_full_pred_3_br_taken_mask_1
output io_out_s1_full_pred_3_slot_valids_0
output io_out_s1_full_pred_3_slot_valids_1
output [40:0] io_out_s1_full_pred_3_targets_0
output [40:0] io_out_s1_full_pred_3_targets_1
output [3:0] io_out_s1_full_pred_3_offsets_0
output [3:0] io_out_s1_full_pred_3_offsets_1
output [40:0] io_out_s1_full_pred_3_fallThroughAddr
output io_out_s1_full_pred_3_fallThroughErr
output io_out_s1_full_pred_3_is_br_sharing
output io_out_s1_full_pred_3_hit
output [222:0] io_out_last_stage_meta 输出最后阶段的元信息 io_out_last_stage_meta = {213’h0, resp_meta_pred_way_r_1, resp_meta_hit_r_1}
input io_ctrl_ubtb_enable 控制ubtb是否启用
input io_s0_fire_0 输入s0_fire_0,与 io_out_s1_pc_0 <= io_in_bits_s0_pc_0 的时钟门控相关
input io_s0_fire_1 输入s0_fire_1
input io_s0_fire_2 输入s0_fire_2
input io_s0_fire_3 输入s0_fire_3
input io_s1_fire_0 输入s1_fire_0
input io_s2_fire_0 输入s2_fire_0
input io_update_valid 更新有效性
input [40:0] io_update_bits_pc 传回的预测块pc(用于指示更新的预测块)
input [3:0] io_update_bits_ftb_entry_brSlots_0_offset solt 0 中分支指令相对于地址块起始pc的偏移
input [11:0] io_update_bits_ftb_entry_brSlots_0_lower 跳转目标地址的低位
input [1:0] io_update_bits_ftb_entry_brSlots_0_tarStat 跳转后的 pc 高位是否进退位
input io_update_bits_ftb_entry_brSlots_0_valid 是否启用
input [3:0] io_update_bits_ftb_entry_tailSlot_offset solt 1 中分支指令相对于地址块起始pc的偏移
input [19:0] io_update_bits_ftb_entry_tailSlot_lower 跳转目标地址的低位
input [1:0] io_update_bits_ftb_entry_tailSlot_tarStat 跳转后的 pc 高位是否进退位
input io_update_bits_ftb_entry_tailSlot_sharing 无条件跳转指令槽中存储条件分支指令
input io_update_bits_ftb_entry_tailSlot_valid 是否启用
input [3:0] io_update_bits_ftb_entry_pftAddr Partial Fallthrough Addr 如果预测块中没有跳转,那么程序将会顺序执行到达的地址,预测块的结束地址。
input io_update_bits_ftb_entry_carry pc+pft时是否产生进位
input io_update_bits_ftb_entry_always_taken_0 是否预测为总是跳转
input io_update_bits_ftb_entry_always_taken_1 是否预测为总是跳转
input io_update_bits_br_taken_mask_0 是否跳转
input io_update_bits_br_taken_mask_1 是否跳转

3 - TAGE-SC 分支预测器

功能介绍

TAGE-SC 是南湖架构条件分支的主预测器,属于精确预测器(Accurate Predictor,简称 APD)。TAGE-SC 可以看作两个功能相对独立的组件:预测部分 TAGE 和 校验部分 SC。

  • 标记几何历史长度预测器 TAGE (Tagged Geometric History Length Predictor)利用历史长度不同的多个预测表,可以挖掘极长的分支历史信息。TAGE功能是预测一个跳转指令是跳转(Taken),还是不跳转(不Taken), 它由一个基预测表和多个历史表组成,首先通过多个历史表进行分支预测,如果没有预测结果,则再采用基础预测表的预测结果。
  • SC (Statistical Corrector) 是统计校正器。当 SC 会参考 TAGE 的预测结果和统计偏向的结果。并根据这两个结果,矫正最终的预测结果。

在昆明湖中,由于每个预测块,最多可以有2条跳转指令,因此TAGE在每次预测最多同时预测 2 条条件分支指令。在访问 TAGE 的各个历史表时,用预测块的起始地址作为 PC,同时取出两个预测结果,并基于相同的全局历史进行预测。

昆明湖Tage分支预测器

基本功能

img

TAGE预测器的核心思路是提供不同历史长度的预测结果,并选择最合适的结果进行反馈。在TAGE预测器中,一共有 1+N 个表历史记录表,N 为可配置选项。以昆明湖为例,N=4。

基于 T0 表的预测器为基础预测器。在预测时,直接通过预测块的 PC 地址在 T0 表中查找到该地址对应的 “2 bit 饱和计数器表示的跳转历史信息” ,然后根据历史信息做出预测结果。T0表的表项只有2bit,所以能记录的历史状态有限。

对于 T0 以外的表,我们用 Tn 进行表示。在查表时除了 PC 外,还需要用到全局跳转历史信息H进行查找。查找命中后,根据 “3bit饱和预测器” 进行跳转或者不跳转的预测。Tn 表的n越大,其用到的H中的位数越多。

对于每一次预测,TAGE 从所有 Tn 命中表项中选择全局跳转历史最长的表项。

  1. 如果该表项存在,且预测结果置信度高就用该项作为最终预测结果。
  2. 如果置信度低,就通过另外一个内部计数器来判断选择该项还是 T0 作为最后预测。

在实现上,为了节约空间,检索 Tn 表时,输入的跳转历史信息H需要进行压缩,该过程也称为历史折叠

各预测表的表项包含如下元素:

  1. T0 表项通过 pc 直接索引
    1. 2 bit pred 无符号饱和计数器(给出预测方向和信心强度)
  2. Tn 表项通过 异或 pc 与 折叠全局历史 索引
    1. 1 bit valid 有效位
    2. 3 bit pred 无符号饱和计数器
    3. 8 bit tag (基于Hash校验是否命中,而非巧合)
    4. 1 bit useful 用于控制有效期

对于一个预测块,有可能所有的表都会产生预测结果,此时就需要进行选择。如上图所示,一般情况下,Tn表的编号越大,优先级越高。

流水线

TAGE 内含两个流水级,第一级计算索引,第二级通过索引读出SRAM表中的结果

  1. 第0拍,s0:第一个流水级的输入,一般是pc和折叠历史。

第一流水级运行:计算index。通过寄存器输出到s1

  1. 第1拍,s1:第二个流水级的输入,为第一级阶段计算好的index等数据。

第二流水级运行:访存SRAM,读取预测结果。通过寄存器输出到s2

  1. 第2拍,s2:实际预测结果。TAGE用了2拍进行预测,在2拍之后的第3拍预测结果就绪可用。

数据结构

  1. 在昆明湖的实现中,T0与Tn的表结构如下所示:

    1. 预测器 作用 表项构成 项数
      基准预测器T0 用于在其他预测器的预测结果都无效时输出预测结果 2 bit ctr 饱和计数器最高位决定跳转方向 2路各2048项,每路对于一条分支指令
      预测表T1-T4 对每个预测块的输入,所有Tn表都进行预测,在所有预测有效的结果中,选择历史记录最长的结果作为最后预测结果。历史记录长度由输入的H决定 1 bit valid 有效位 3 bit ctr 饱和计数器8 bit tag 校验命中1 bit us 作为usefulness计数器 4096项、奇数项对应第一条分支指令,偶数项对应第二条分支指令

对于每个表Tn,在查询时,其输入“全局分支历史数据H”的长度是不同的。假如总的预测历史长度为S,Tn、Tn+1可能用S的低x,低y位 (越低的位是越新的历史)作为查询输入。一般情况下,Tn表的n越大,其用到的历史信息越长,即 x<y 。

在进行 Tn 表的查询时,由于历史数据H进行了“压缩”,可能导致一个PC1^H1的结果与另一个PC2^H2的结果相同(类似Hash表的Hash冲突),进而导致索引到无效数据(预测PC1索引到预测PC2的数据)。所以TAGE为每个表提供了一个tag标识,昆明湖中采用了8bit tag,来减小冲突发生的概率,tag的计算方法和查询的索引方法不同,只有当tag计算相同时查询结果才有效

在Tn表项中,除了饱和计数器ctr和tag外,还有1bit的usefulness计数器,该计数器为0时,作弱表项,表示可以当作该项可以被重新分配当作他用;不为0时,作强表项,该项不能被分配当作他用。

为了尽力避免所有表项全为1,无法分配新表项的情况发生,TAGE预期使用计数器bankTickCtrs 对所有usefulness进行清零操作。

T0,Tn表的检索方法

  • 对于T0表,在查找时用 PC[11:1] 位进行索引2048表项,因此对于T0来说,不可能存在检索不到的情况。
  • 对于表Tn,除了 PC[11:1] 外,还需要基于全局分支历史进行检索。在昆明湖中,顶层分支预测器维护了一个256bit的全局历史记录GH,可以根据子预测器需要将GH的最近n位历史信息,按目标位数x进行折叠。 即把n,以长度x为基本单元,拆分成ceil(x/n)个长为x的数,然后按位进行异或得到。通常用FH(Folded History)表示,具体流程请参考 分支折叠历史章节。TAGE预测器查找Tn的表项时,用到了index和tag,他们的计算公式如下表所示:
计算方法 公式
index = FH ^ ((pc»1)低位)
tag = FH1 ^ FH2 ^ ((pc»1)低位)

其中FH、FH1、FH2表示按一定规范折叠后的全局分支历史。对于Tn而言,其FH、FH1、FH2都有各自的对于折叠位数,不一定相同。在昆明湖实现中,T0和Tn表的配置如下:

表名称 FH长度 FH1长度 FH2长度 最近历史长度(用到GH中的位数)
T1 8比特 8比特 7比特 低8位,即把最新8位历史,折叠成FH、FH1、FH2
T2 11比特 8比特 7比特 低13位,即把最新13位历史,折叠成FH、FH1、FH2
T3 11比特 8比特 7比特 低32位,即把最新32位历史,折叠成FH、FH1、FH2
T4 11比特 8比特 7比特 低119位,即把最新119位历史,折叠成FH、FH1、FH2

注:pc»1是因为使用了RISC-C拓展,2Byte对齐,而PC本身已经省略了1B对齐,所以只用1b

替代预测器

由于 Tn 表使用饱和计数器进行预测,因此其输出结果可能存在“信心不足”的情况。例如在昆明湖中,对3比特饱和计数器,100、011时都是弱预测。为了为该状态提供更多的选择参考,TAGE 预测器还提供了一个“替代预测器”机制,他的作用是告诉 TAGE 在 Tn 表预测的结果 “信心不足” 时,是该选择 Tn 的预测结果,还是选择 T0 的预测结果。

在昆明湖的具体实现中,“替代预测器” 基于 useAltOnNaCtrs 的寄存器组实现。它由两路128个 4-bit饱和计数器 构成,每个计数器都被初始化为 0b1000。在TAGE进行预测时,使用 PC(7,1) 进行索引得到对应的饱和计数器。如果该计数器的值大于等于预设值,且Tn预测结果信心不高,则选择T0的结果,否则选择Tn的结果。

预测过程

综上所述,昆明湖中TAGE预测器的预测步骤如下:

  1. 并行索引 T0 表以及 Tn 表,根据命中结果选择使用哪一个表给出预测结果
    1. 如果有命中到tag匹配的Tn表,由历史记录最长Tn表的ctr饱和计数器给出潜在预测结果。
    2. 如果没有命中Tn表,则由T0表的饱和计数器给出最终预测结果。
  2. 如果匹配到的Tn表的潜在预测结果为弱预测(100,011),且替代预测中PC对应4-bit计数器的值大于等于阈值,则采用T0表的结果为最终结果,否则采用Tn表的预测结果为最终预测结果。

训练过程

由于TAGE的预测过程用到了很多计数器和tag,他们需要根据一定的规则进行更新,该过程称为训练。该训练过程发生在BPU的update阶段,该阶段会输入PC,分支历史、以及否预测正确等信息。香山昆明湖的分支预测进行训练的流程根据不同情况,分为如下几个步骤:

  1. T0作为最终使用的预测结果时更新:发生跳转(即taken)则 pc 索引的 ctr 饱和计数器 +1,否则 -1
  2. 只有T0命中时,进行如下操作
    1. T0预测正确则不额外更新
    2. T0预测错误则尝试随机在Tn中的某个表申请一个新表项 申请新表项时需要对应index位置的原表项useful为0 新表项默认是弱预测,useful为0,并设置其tag为计算出来的新tag
  3. T0和Tn同时命中时,进行如下操作
    1. Tn 始终更新:taken 则 pc 索引的 ctr 饱和计数器 +1,否则 -1 需要注意,”命中“ 表示 index 索引到的表项的 tag 要和计算出的 tag 匹配
    2. T0和Tn结果相同
      1. 预测正确则不额外更新
      2. 预测错误则尝试在比Tn对应历史更长的表中随机申请一个新表项 申请新表项时需要对应index位置的原表项useful为0 新表项默认是弱预测,useful为0,tag设置为用新历史信息计算出来的tag
    3. T0和Tn结果不同
      1. Tn正确则表项 useful +1
        1. 若结果还同时为弱预测,则选用T0的替代预测计数器 -1
      2. Tn错误则表项 useful -1,同时如 3.2.2 在更长历史表中申请新表项
        1. 若结果还同时为弱预测,则选用T0的替代预测计数器 +1
  4. 每次需要分配表时,进行动态重置usefulness标志位
    1. 使用 7bit 的 bankTickCtrs 寄存器,并计算
      1. 可分配的表数 a(历史长度比当前更长,且对应索引的useful为 0 )
      2. 不可分配的表数 b(历史长度比当前更长,且对应索引的useful 不为 0 )
    2. 更新 bankTickCtrs += Δ(饱和计数), Δ = b - a
    3. 当 bankTickCtrs当为最大值时, 重置所有useful=0

昆明湖SC分支预测器

基本功能介绍

SC(Statistics counter)分支预测器是一种基于历史统计信息的分支预测器。

与TAGE类似,在SC中通常有多个表Tn,他们对应了不同历史长度的跳转统计。不同点在于,在预测按PC检索时,每个表Tn都会命中,然后SC把每个命中的表项进行求和,计算总的“饱和计数器”跳转信息,最后根据总的跳转信息判断是否跳转。一般情况下,SC采用“有符号饱和计数器”,计数器值大于0时表示跳转,小于0时表示不跳转。计数器的绝对值越大,表示其预测置信度越高。

在SC预测器中,SC也由多个表组成(例如T1,T2,T3,T4),但相对TAGE预测器少了基础预测表T0。SC中Tn表有6 bit的有符号饱和计数器。SC表的索引方式如下:

计算方式
Index = (FH) ^ ((pc»1)低位)

对应每个表,其表项数和用到的折叠历史长度如下:

表名 表项数 FH长度 折叠历史范围
T1 512 0 不折叠
T2 512 4 把历史信息的低4位,折叠成FH
T3 512 8 把历史信息的低10位,折叠成FH
T4 512 8 把历史信息的低16位,折叠成FH

在计算统计预测结果时,总得统计结果计算公式如下:

$$scCtrSum=\sum_{i=0}^{i<4}( (ctr_{sc} << 1) +1)$$

其中ctr_sc表示每个表的有符号饱和计数器。对其进行左移加一是进行权重调整。累加后的scCtrSum就是SC的最终预测结果,如果为该值大于零,则预测跳转,小于零则预测不跳转。其绝对值越大,表示预测置信度越高。

典型数据转换结果如下(在计算时为了不溢出,扩展到了9位):

  1. 均为 6b100000(强不跳转) ,求得 9b100000100,值为 -252
  2. 均为 6b011111(强跳转),求得 9b011111100,值为 252
  3. 均为 6b000000(弱跳转),求得 9b000000100,值为 4
  4. 均为 6b111111(弱不跳转),求得 9b111111100,值为 -4

预测过程

  1. 通过PC和历史信息结算表Tn的索引 Index
  2. 根据Index查询,得到所有表对应的饱和计数器
  3. 所有所有表查询到的饱和计数器进行求和得到最终预测结果(大于0跳转,小于0不跳转)

训练过程

在update阶段进行饱和计数器的更新。

  1. 如果PC对应的真实指令跳转,这所有表中对应的饱和计数器+1
  2. 如果PC对应的真实指令不跳转,这所有表中对应的饱和计数器-1

昆明湖TAGE-SC分支预测器

有了TAGE为何还需要SC

一些应用上,一些分支行为与分支历史或路径相关性较弱,表现出一个统计上的预测偏向性。对于这些分支,相比基于历史的分支预测,使用计数器捕捉统计偏向的方法更为有效。

TAGE在预测与历史非常相关的分支时非常有效,但对有统计偏向的分支则支持不佳。例如只对一个方向有小偏差,但与历史路径没有强相关性的分支。为了避免该问题,可以在传统TAGE预测器上增加SC预测器。

TAGE-SC功能介绍

在昆明湖 TAGE-SC 预测器中,会同时得到 TAGE 和 SC 的预测结果 P1 和 P2,然后对他们的结果进行累加 P = P1+P2 ,如果 P 的绝对值大于 8bit 的阈值 sc_bank_thres,则采用预测器结果 P,否则则采用 P1 作为最终预测结果。

为了进行动态自适应,阈值sc_thres是需要是动态变化的。为此在实现上TAGE-SC使用了一个5bit的sc_bank_ctr计数器对阈值sc_bank_thres进行调整。另外,由于昆明湖支持同时预测2条分支指令,因此阈值寄存器和对应的控制计数器也是两份。

流水线

TAGE-SC 内含 3 个流水级,TAGE的2级流水已经介绍过,SC部分的流水线如下:

  1. 第0拍,s0 读入 pc 和折叠历史

第一流水级:通过pc和FH计算出index,并输出到s0_idx寄存器

  1. 第1拍,s1 从s0寄存器读出s0_idx作为s1_idx

第二流水级:找到SCTable中s1_idx对应的计数器数据,输出到s1_scResps寄存器

  1. 第2拍,s2 从s1寄存器读出s1_scResps作为s2_scResps

第三流水级:根据s2_scResps选择是否需要反转预测结果,输出到s2_disagree寄存器

  1. 第3拍,s3 从s2_disagree中读出结果,作为s3_disagree输出

预测过程

在TAGE-SC预测时,TAGE的预测结果P1用tage_ctr表示,SC的预测结果P2用scCtrSum表示。在预测时,分为如下四步。

  1. 执行SC预测器,得到预测结果scCtrSum

  2. 并行得到TAGE预测器的预测结果 tage_ctr。

    1. 由于TAGE的预测结果是无符号饱和计数器,而SC的预测结果是有符号饱和计数器,如果对他们进行相加,需要进行数据转换。

    2. 昆明湖在实现上,采用了对TAGE的结果进行转换。转换后的结果用 tageCtrCentered 表示,具体转换过程如下:

    $$tageCtrCentered=((((ctr_{tage} -4)<<1)+1)<<3) $$
    1. 3比特的无符号饱和计数器转换为8比特的有符号饱和计数器结果举例如下所示:

      • 3b100 弱跳转 => 8b00001000 = 8

      • 3b011 弱不跳转 => 8b11111000 = -8

      • 3b111 强跳转 => 8b00111000 = 56

      • 3b000 强不跳转 => 8b11001000 = -56

  3. 对TAGE和SC的预测结果进行相加得到最终预测结果P,在实现上用totalSum进行表示。

$$totalSum = scCtrSum + tageCtrCentered$$
  1. 根据 totalSum 以及 sc_bank_thres 决定最终预测方向

    1. totalSum > 0 且绝对值超过阈值则跳转: 如果 scCtrSum > sc_bank_thres - tageCtrCentered 也可以理解成 totalSum > sc_bank_thres 上面的写法可以降低最大位宽(把保证不溢出需要10bit变成9bit)
    2. totalSum < 0 且绝对值超过阈值则不跳转: 如果 scCtrSum < -sc_bank_thres - tageCtrCentered 也可以理解成 |totalSum| > sc_bank_thres

训练过程

在对 TAGE 和 SC 进行组合后,TAGE-SC 添加了 sc_bank_ctr 计数器用来控制阈值sc_bank_thres。因此在训练时,除了 TAGE 和 SC 本身的训练外,还需要对新增加的计数器进行更新。

在update阶段,其具体更新流程如下:

  1. TAGE-SC采用了预测结果P(即TAGE+SC后的预测结果),如果 |totalSum| 在 [sc_bank_thres -4, sc_bank_thres -2] 的范围内,则对阈值相关寄存器组进行更新
    1. 更新 sc_bank_ctr,饱和计数 若预测正确,则 sc_bank_ctr +=1 若预测错误,则 sc_bank_ctr -=1
    2. 更新 sc_bank_thres ,受限制的饱和运算,sc_bank_ctr 更新后的值已达 0b11111 且 sc_bank_thres <= 31,则 sc_bank_thres +=2sc_bank_ctr 更新后的值为 0 且 sc_bank_thres >=6,则 sc_bank_thres -=2 其余情况thres不变。
    3. sc_bank_thres 更新判断结束后,会对 sc_bank_ctr 再做一次判断 若更新后的sc_bank_ctr若为0b11111或0,则thres_ctr会被置回初始值0b10000。
  2. TAGE-SC采用了预测结果P1(即TAGE的预测结果)不进行任何操作

接口列表

TageSC

信号类型 信号宽度 信号名 信号描述
input * clock
input * reset
input *[35:0] io_reset_vector 用于reset时,reset s1_pc_dup_0 提供的值
input *[40:0] io_in_bits_s0_pc_0 复制的s0_pc的dup数组的第1个,给顶层BPU的PC
input *[40:0] io_in_bits_s0_pc_1 复制的s0_pc第2个,给Tage的PC
input *[40:0] io_in_bits_s0_pc_3 复制的s0_pc的第4个,给SC的PC
input *[10:0] io_in_bits_folded_hist_1_hist_17_folded_hist TageTable 2 用到的11bits 折叠历史 从多长历史范围折叠到11bit见前文所述的表 注意TageTable下标+1,此处 T2 是前文 T3
input *[10:0] io_in_bits_folded_hist_1_hist_16_folded_hist TageTable 3 用到的11bits 折叠历史
input *[6:0] io_in_bits_folded_hist_1_hist_15_folded_hist TageTable 1 用到的7bits 折叠历史
input *[7:0] io_in_bits_folded_hist_1_hist_14_folded_hist TageTable 0 用到的8bits 折叠历史
input *[6:0] io_in_bits_folded_hist_1_hist_9_folded_hist TageTable 2 用到的7bits 折叠历史
input *[7:0] io_in_bits_folded_hist_1_hist_8_folded_hist TageTable 3 用到的8bits 折叠历史
input *[6:0] io_in_bits_folded_hist_1_hist_7_folded_hist TageTable 0 用到的7bits 折叠历史
input *[6:0] io_in_bits_folded_hist_1_hist_5_folded_hist TageTable 3 用到的7bits 折叠历史
input *[7:0] io_in_bits_folded_hist_1_hist_4_folded_hist TageTable 1 用到的8bits 折叠历史
input *[7:0] io_in_bits_folded_hist_1_hist_3_folded_hist TageTable 2 用到的8bits 折叠历史
input *[10:0] io_in_bits_folded_hist_1_hist_1_folded_hist TageTable 1 用到的11bits 折叠历史
input *[3:0] io_in_bits_folded_hist_3_hist_12_folded_hist SCTable 1 用到的 4bit 折叠历史
input *[7:0] io_in_bits_folded_hist_3_hist_11_folded_hist SCTable 2 用到的 8bit 折叠历史
input *[7:0] io_in_bits_folded_hist_3_hist_2_folded_hist SCTable 3 用到的 8bit 折叠历史
output * io_out_s2_full_pred_0_br_taken_mask_0 io_out_s2_full_pred_{i}br_taken_mask{j} Tage 在 s2流水级输出的,复制4份 预测块中第 j 条分支指令TAGE预测结果 这里不该叫mask吧
output * io_out_s2_full_pred_0_br_taken_mask_1
output * io_out_s2_full_pred_1_br_taken_mask_0
output * io_out_s2_full_pred_1_br_taken_mask_1
output * io_out_s2_full_pred_2_br_taken_mask_0
output * io_out_s2_full_pred_2_br_taken_mask_1
output * io_out_s2_full_pred_3_br_taken_mask_0
output * io_out_s2_full_pred_3_br_taken_mask_1
output * io_out_s3_full_pred_0_br_taken_mask_0 io_out_s3_full_pred_{i}br_taken_mask{j} Tage 在 s3流水级输出的,复制4份 预测块中第 j 条分支指令SC预测结果
output * io_out_s3_full_pred_0_br_taken_mask_1
output * io_out_s3_full_pred_1_br_taken_mask_0
output * io_out_s3_full_pred_1_br_taken_mask_1
output * io_out_s3_full_pred_2_br_taken_mask_0
output * io_out_s3_full_pred_2_br_taken_mask_1
output * io_out_s3_full_pred_3_br_taken_mask_0
output * io_out_s3_full_pred_3_br_taken_mask_1
output *[222:0] io_out_last_stage_meta 见附表
input * io_ctrl_tage_enable
input * io_ctrl_sc_enable
input * io_s0_fire_0 s0 阶段流水线控制 相同信号复制多份,0给BPU,1给Tage,3给SC
input * io_s0_fire_1
input * io_s0_fire_3
input * io_s1_fire_0 s1 阶段流水线控制
input * io_s1_fire_1
input * io_s1_fire_2
input * io_s1_fire_3
input * io_s2_fire_0 s2 阶段流水线控制
input * io_s2_fire_1
input * io_s2_fire_2
input * io_s2_fire_3
output * io_s1_ready tage的所有表,可以执行读取结果的操作
input * io_update_valid 从FTQ发向BPU的后端执行结果(更新信号)是否有效
input *[40:0] io_update_bits_pc (后端执行过的)预测块的PC
input *[10:0] io_update_bits_spec_info_folded_hist_hist_17_folded_hist TageTable 2 用到的11bits 折叠历史 预测时使用的分支历史结果,没有更新,转了一圈回来了
input *[10:0] io_update_bits_spec_info_folded_hist_hist_16_folded_hist TageTable 3 用到的11bits 折叠历史
input *[6:0] io_update_bits_spec_info_folded_hist_hist_15_folded_hist TageTable 1 用到的7bits 折叠历史
input *[7:0] io_update_bits_spec_info_folded_hist_hist_14_folded_hist TageTable 0 用到的8bits 折叠历史
input *[3:0] io_update_bits_spec_info_folded_hist_hist_12_folded_hist SCTable 1 用到的 4bit 折叠历史
input *[7:0] io_update_bits_spec_info_folded_hist_hist_11_folded_hist SCTable 2 用到的 8bit 折叠历史
input *[6:0] io_update_bits_spec_info_folded_hist_hist_9_folded_hist TageTable 2 用到的7bits 折叠历史
input *[7:0] io_update_bits_spec_info_folded_hist_hist_8_folded_hist TageTable 3 用到的8bits 折叠历史
input *[6:0] io_update_bits_spec_info_folded_hist_hist_7_folded_hist TageTable 0 用到的7bits 折叠历史
input *[6:0] io_update_bits_spec_info_folded_hist_hist_5_folded_hist TageTable 3 用到的7bits 折叠历史
input *[7:0] io_update_bits_spec_info_folded_hist_hist_4_folded_hist TageTable 1 用到的8bits 折叠历史
input *[7:0] io_update_bits_spec_info_folded_hist_hist_3_folded_hist TageTable 2 用到的8bits 折叠历史
input *[7:0] io_update_bits_spec_info_folded_hist_hist_2_folded_hist SCTable 3 用到的 8bit 折叠历史
input *[10:0] io_update_bits_spec_info_folded_hist_hist_1_folded_hist TageTable 1 用到的11bits 折叠历史
input * io_update_bits_ftb_entry_brSlots_0_valid FTB 表项的第一个slot是否有效(存储了跳转指令)
input * io_update_bits_ftb_entry_tailSlot_sharing FTB 表项的最后一个slot是否存储了条件分支而非无条件跳转
input * io_update_bits_ftb_entry_tailSlot_valid FTB 表项的最后一个slot是否有效
input * io_update_bits_ftb_entry_always_taken_0 历史上slot 0 指令总是跳转
input * io_update_bits_ftb_entry_always_taken_1 历史上slot 1 指令总是跳转
input * io_update_bits_br_taken_mask_0 solt 0 是否 taken
input * io_update_bits_br_taken_mask_1 solt 1 是否 taken
input * io_update_bits_mispred_mask_0 solt 0 是否预测正确
input * io_update_bits_mispred_mask_1 solt 1 是否预测正确
input *[222:0] io_update_bits_meta 见附表

io_out_last_stage_meta

需要设计参与优化!

信号类型 信号位 信号名 信号描述
output [218:88] 0 占位,全为0,传递到composer时会忽略
87 resp_meta_providers_1_valid_r
[86:85] resp_meta_providers_1_bits_r
84 resp_meta_providers_0_valid_r
[83:82] resp_meta_providers_0_bits_r
[81:79] resp_meta_providerResps_1_r_ctr
78 resp_meta_providerResps_1_r_u
77 resp_meta_providerResps_1_r_unconf
[76:74] resp_meta_providerResps_0_r_ctr
73 resp_meta_providerResps_0_r_u
72 resp_meta_providerResps_0_r_unconf
71 resp_meta_altUsed_1_r
70 resp_meta_altUsed_0_r
69 resp_meta_altDiffers_1_r
68 resp_meta_altDiffers_0_r
[67:66] resp_meta_basecnts_1_r
[65:64] resp_meta_basecnts_0_r
[63:60] resp_meta_allocates_1_r
[59:56] resp_meta_allocates_0_r
55 resp_meta_takens_1_r
54 resp_meta_takens_0_r
53 resp_meta_scMeta_tageTakens_1_r
52 resp_meta_scMeta_tageTakens_0_r
51 resp_meta_scMeta_scUsed_1_r
50 resp_meta_scMeta_scUsed_0_r
49 resp_meta_scMeta_scPreds_1_r
48 resp_meta_scMeta_scPreds_0_r
[47:42] r_1_3 scMeta(预测时的状态)中第2路的第4个sc_ctr的值
[41:36] r_1_2 scMeta中第2路的第3个sc_ctr的值
[35:30] r_1_1 scMeta中第2路的第2个sc_ctr的值
[29:24] r_1_0 scMeta中第2路的第1个sc_ctr的值
[23:18] r_3 scMeta中第1路的第4个sc_ctr的值
[17:12] r_2 scMeta中第1路的第3个sc_ctr的值
[11:6] r_1 scMeta中第1路的第2个sc_ctr的值
[5:0] r_0 scMeta中第1路的第1个sc_ctr的值

io_update_bits_meta

信号类型 信号位 信号名 信号描述
input [218:94] FTB, ITAGE, RAS 模块传给 FTQ 的 META 信息,忽略
[93:6] io_out_last_stage_meta[87:0] 偏移 6bit 后的结果 TAGE 输出给 FTQ 的 META
[5:0] uFTB 输出给 FTQ 的 META

4 - FTB 分支预测器

FTB 简介

FTB 是香山 BPU 的第三个子预测器,它也能一并获取到 uFTB 和 TAGE-SC 的输出。在 FTB 的输入接口中,s1 通道含有 uFTB 的基础预测结果,s2 通道和 s3 通道中仅有 br_taken_mask 一组信号被 TAGE-SC 填充,并无 FTB 项生成的基础预测结果。FTB 的工作便是为 s2 和 s3 通道提供基础预测结果。

FTB 在功能和结构上都与 uFTB 类似,其主要区别就是 FTB 能够容纳更多的 FTB 项,并且 FTB 的预测结果是在 s2 与 s3 通道输出。正是由于容量大,其读出的速度上会比 uFTB 慢,无法被放置在第一周期产生预测结果,但大容量也使它能够获得更加精准的预测结果。

uFTB 功能简介

  • 缓存更多 FTB 项,为 s2 和 s3 通道提供基础预测结果。 FTB 预测器的本质是一个较大容量的存储器,其会根据当前预测的 PC 读出对应的 FTB 项,并在 s2 阶段产出预测结果。与此同时该 FTB 项还会被再保存一个周期,生成 s3 阶段预测结果。生成结果需要注意的点是要考虑到上一预测器输入的 br_taken_mask 字段,避免在生成时丢失。
  • 根据更新请求,更新存储中的 FTB 项。

FTB 存储结构

FTB 预测器中 FTB 项被放置在了一个专门的存储结构中,叫做 FTBBank ,在进一步查看 FTBBank 的结构之前,我们先来看一下 FTBBank 是怎样使用的。

FTB 读请求

FTBBank 的读请求接口如下:

  • req_pc 请求的 pc
    • 接口类型:Flipped(DecoupledIO(UInt(VAddrBits.W)))
  • read_resp 读出来的FTB项
    • 接口类型:FTBEntry
  • read_hits 命中了哪一路 (way)
    • 接口类型:Valid(UInt(log2Ceil(numWays).W))

其中 req_pc 的接口是 Decoupled 的,也就是说含有 valid 和 ready 信号。 FTB 需要在 s1 阶段开始之前便获取到 pc,因此 s0_pc 被送入 req_pc 接口,s0_fire 信号被连接至 req_pc 的 valid 信号,ready 信号被连接至预测器的流水控制信号 s1_ready

s0_fire 之后进入 s1 阶段,s0_fire 的下一个周期,也就是在 s1_fire 的同时,FTBBank 已经将读出的 FTB 项输出至 read_resp 接口,并计算好了 read_hits 。但此时因为读出已经浪费了过多时延,无法在 s1 阶段进行输出了,因此该读出结果被保存在内部寄存器中。在 s2 和 s3 阶段会在寄存器中读出该 FTB 项并生成预测结果。

FTBBank

FTBBank 中定义了一个存储器来存储所有的 FTB 项,存储器采用组相联的结构(如果你不清楚组相联的含义,请自行 RTFM),共含 512 个组(Set),每组有 4 路(Way),最多能存储 2048 个 FTB 项,存储 FTB 的同时还会存储与 FTB 项对应的 tag 用于匹配 FTB 项。

具体地,tag 被定义为 pc[29:10],即在 pc 中截取了 20 位用于标识 FTB 项,pc 的具体划分方式如下:

  pc: | ... |<-- tag(20 bits) -->|<-- idx(9 bits) -->|<-- instOffset(1 bit) -->|

读出时,向存储器提供组序号(idx),读出该组中所有路,然后检测是否有某一路的 tag 与当前 tag 匹配,如果匹配,则表示命中,读出的 FTB 项通过 read_resp 接口送出,并把命中的路编号通过 read_hits 接口送出。

预测结果生成

正如我们之前提到的,对于 FTB 预测器,它需要向 s2, s3 通道提供由 FTB 项转化来的基础预测结果,FTB 项已经在 s1 阶段被读出并保存。在 s2 和 s3 阶段只需直接读出便生成即可,但需要注意的一点就是保护 TAGE-SC 在 s2 及 s3 通道中产生的预测结果 br_taken_mask 字段,这一字段为条件分支指令提供了精确的预测结果。对于 s1 通道,FTB 预测器不予更改。

s2 和 s3预测结果中的信号产生方式可参考以下列表:

  • hit FTB表项是否命中

    • 生成方式:FTBBank 输出的 read_hits 信号 valid 位有效。
  • slot_valids slot有效位,表示 ftb 项中的每个 slot 是否有效

  • targets 每个slot对应的跳转目标地址

  • offsets 每个slot中的指令相对于预测块起始地址的偏移

  • is_jal 预测块内包含jal指令

  • is_jalr 预测块内包含jalr指令

  • is_call 预测块内包含call指令

  • is_ret 预测块内包含ret指令

  • last_may_be_rvi_call 预测结果预测块末尾可能为一个RVI类型的call指令信号

  • is_br_sharing 最后一个slot (tailSlot) 中存储的是条件分支指令信号

    • 生成方式: FTB 项中对应字段导出
  • fallThroughErr FTB项中记录的 pftAddr 有误

    • 生成方式:比较 pftAddr 代表的预测块结束地址是否大于预测块的起始地址,如果小于,则代表出现错误,此信号置为有效。这种情况可能会发生在 pc 索引到错误的 FTB 项的情况。
  • fallThroughAddr 预测块的结束地址

    • 生成方式:如果 fallThroughErr 无效,则根据 pftAddr 生成,否则将其设置为起始地址 + 预测宽度。
  • br_taken_mask 分支预测结果,每个分支(slot)对应一位,表示该分支是否被预测为 taken

    • 生成方式:若 FTB 项命中并且 always_taken 字段有效,则预测为 taken;否则由相应通道输入接口中提供的 br_taken_mask 信号来决定。
  • jalr_target 本预测块中的 jalr 的跳转目标

    • 生成方式:FTB 项中 tailSlot 中的跳转目标。

FTB meta

在预测的第三周期,FTB 预测器会将本次预测的一些辅助信息输出至 last_stage_meta 中,还会将读出的 FTB 项送到 last_stage_ftrb_entry 接口中。

FTB meta 中含有 hitwriteWay 两个信息,分别表示本次预测是否命中,以及在哪一路读出。之后更新通道中会产生本次预测的更新信息,这两个信息也会随之送入,来指导更新后的 FTB 项写入的位置。

FTB 更新

update 通道中已经为我们指定好了 pc 以及新 FTB 项,并且还有 meta 信息中的 hitwriteWay 。如果 meta 中的 hit 有效,说明之前这一 pc 对应的 FTB 项在存储器中是有存储的,我们只需将它写入到 writeWay 对应的路中即可。

如果无效,说明以前没有存储,但现在是否存储我们并不知道,有可能在此更新请求之前,该 pc 对应的 FTB 项被另一个更新请求写入了。因此我们还需要给 FTBBank 发送一个读请求,来判断其中是否存在对应的 FTB 项。如果存在,下周期可直接写入到该位置,否则,通知 FTBBank 为其重新分配一个位置。

因此 FTB 项的更新需要的周期数需随 hit 情况而定。

我们首先来看一下 FTBBank是怎样处理更新的。

FTBBank 更新

FTBBank 的更新接口如下,分为更新读接口和更新写接口两部分。

  • u_req_pc 更新读请求 pc

    • Flipped(DecoupledIO(UInt(VAddrBits.W)))
  • update_hits 读出的命中情况

    • Valid(UInt(log2Ceil(numWays).W))
  • update_access 有更新请求但 meta 信息指示未命中

    • Bool()
  • update_pc 更新写请求 pc

    • UInt(VAddrBits.W))
  • update_write_data 更新请求写入的数据,valid 有效的时候写

    • Flipped(Valid(new FTBEntryWithTag))
  • update_write_way 更新请求写入的路索引

    • UInt(log2Ceil(numWays).W))
  • update_write_alloc 是否需要分配FTB项(以前未命中)

    • Bool()

对于更新读接口,FTBBank 通过 u_req_pc 信号获取更新读请求,该请求要比预测时的读请求优先级更高,在下一个周期,FTBBank 会将命中情况通过 update_hits 接口送出。update_access 仅用于 FTBBank 内部某些状态判断。

对于更新写接口,FTBBank 通过 update_pc 信号获取更新写请求的 pc,并在 update_write_data 有效时向 update_write_way 对应的位置写入其中的数据。如果 update_write_alloc 有效,说明不能直接写入请求中指定的位置,而是需要重新分配一个位置。

分配的策略如下:

  • 如果所有路均已填充,则使用伪 LRU 替换算法选取要替换的路
  • 若存在空路,否则选取空路。

更新请求时序

  • meta hit 有效 若更新请求中 meta 指示为 hit,则我们直接根据更新请求中的信息,指定好写入的地址和数据即可,写入仅需一周期
  • meta hit 无效 此时,在接收到更新请求后,我们将请求中的 pc 连接至 FTBBank 的读端口,读端口在下一周期将结果返回。由于时延问题,我们将这一结果保存,并在下一周期使用。下一周期根据结果中的命中情况指定是否需要设置 update_write_alloc,并发出写请求。这时的更新过程总共花费了三个周期。

接口列表

信号类型 信号位 信号名 信号描述
input clock 输入时钟
input reset 复位信号
input [35:0] io_reset_vector 用于reset时,reset s1_pc_dup_0 提供的值
input [40:0] io_in_bits_s0_pc_0 输入位s0_pc 的 第0个复制
input [40:0] io_in_bits_s0_pc_1 同上 第1个
input [40:0] io_in_bits_s0_pc_2 同上 第2个
input [40:0] io_in_bits_s0_pc_3 同上 第3个
input io_in_bits_resp_in_0_s2_full_pred_0_br_taken_mask_0 预测结果输入
input io_in_bits_resp_in_0_s2_full_pred_0_br_taken_mask_1
input io_in_bits_resp_in_0_s2_full_pred_1_br_taken_mask_0
input io_in_bits_resp_in_0_s2_full_pred_1_br_taken_mask_1
input io_in_bits_resp_in_0_s2_full_pred_2_br_taken_mask_0
input io_in_bits_resp_in_0_s2_full_pred_2_br_taken_mask_1
input io_in_bits_resp_in_0_s2_full_pred_3_br_taken_mask_0
input io_in_bits_resp_in_0_s2_full_pred_3_br_taken_mask_1
input io_in_bits_resp_in_0_s3_full_pred_0_br_taken_mask_0
input io_in_bits_resp_in_0_s3_full_pred_0_br_taken_mask_1
input io_in_bits_resp_in_0_s3_full_pred_1_br_taken_mask_0
input io_in_bits_resp_in_0_s3_full_pred_1_br_taken_mask_1
input io_in_bits_resp_in_0_s3_full_pred_2_br_taken_mask_0
input io_in_bits_resp_in_0_s3_full_pred_2_br_taken_mask_1
input io_in_bits_resp_in_0_s3_full_pred_3_br_taken_mask_0
input io_in_bits_resp_in_0_s3_full_pred_3_br_taken_mask_1
output io_out_s2_full_pred_0_br_taken_mask_0 s2 阶段输出的完整预测结果
output io_out_s2_full_pred_0_br_taken_mask_1
output io_out_s2_full_pred_0_slot_valids_0
output io_out_s2_full_pred_0_slot_valids_1
output [40:0] io_out_s2_full_pred_0_targets_0
output [40:0] io_out_s2_full_pred_0_targets_1
output [40:0] io_out_s2_full_pred_0_jalr_target
output [3:0] io_out_s2_full_pred_0_offsets_0
output [3:0] io_out_s2_full_pred_0_offsets_1
output [40:0] io_out_s2_full_pred_0_fallThroughAddr
output io_out_s2_full_pred_0_is_br_sharing
output io_out_s2_full_pred_0_hit
output io_out_s2_full_pred_1_br_taken_mask_0 同上
output io_out_s2_full_pred_1_br_taken_mask_1
output io_out_s2_full_pred_1_slot_valids_0
output io_out_s2_full_pred_1_slot_valids_1
output [40:0] io_out_s2_full_pred_1_targets_0
output [40:0] io_out_s2_full_pred_1_targets_1
output [40:0] io_out_s2_full_pred_1_jalr_target
output [3:0] io_out_s2_full_pred_1_offsets_0
output [3:0] io_out_s2_full_pred_1_offsets_1
output [40:0] io_out_s2_full_pred_1_fallThroughAddr
output io_out_s2_full_pred_1_is_br_sharing
output io_out_s2_full_pred_1_hit
output io_out_s2_full_pred_2_br_taken_mask_0 同上
output io_out_s2_full_pred_2_br_taken_mask_1
output io_out_s2_full_pred_2_slot_valids_0
output io_out_s2_full_pred_2_slot_valids_1
output [40:0] io_out_s2_full_pred_2_targets_0
output [40:0] io_out_s2_full_pred_2_targets_1
output [40:0] io_out_s2_full_pred_2_jalr_target
output [3:0] io_out_s2_full_pred_2_offsets_0
output [3:0] io_out_s2_full_pred_2_offsets_1
output [40:0] io_out_s2_full_pred_2_fallThroughAddr
output io_out_s2_full_pred_2_is_jalr
output io_out_s2_full_pred_2_is_call
output io_out_s2_full_pred_2_is_ret
output io_out_s2_full_pred_2_last_may_be_rvi_call
output io_out_s2_full_pred_2_is_br_sharing
output io_out_s2_full_pred_2_hit
output io_out_s2_full_pred_3_br_taken_mask_0 同上
output io_out_s2_full_pred_3_br_taken_mask_1
output io_out_s2_full_pred_3_slot_valids_0
output io_out_s2_full_pred_3_slot_valids_1
output [40:0] io_out_s2_full_pred_3_targets_0
output [40:0] io_out_s2_full_pred_3_targets_1
output [40:0] io_out_s2_full_pred_3_jalr_target
output [3:0] io_out_s2_full_pred_3_offsets_0
output [3:0] io_out_s2_full_pred_3_offsets_1
output [40:0] io_out_s2_full_pred_3_fallThroughAddr
output io_out_s2_full_pred_3_fallThroughErr
output io_out_s2_full_pred_3_is_br_sharing
output io_out_s2_full_pred_3_hit
output io_out_s3_full_pred_0_br_taken_mask_0 s3 阶段输出的完整预测结果
output io_out_s3_full_pred_0_br_taken_mask_1
output io_out_s3_full_pred_0_slot_valids_0
output io_out_s3_full_pred_0_slot_valids_1
output [40:0] io_out_s3_full_pred_0_targets_0
output [40:0] io_out_s3_full_pred_0_targets_1
output [40:0] io_out_s3_full_pred_0_jalr_target
output [40:0] io_out_s3_full_pred_0_fallThroughAddr
output io_out_s3_full_pred_0_fallThroughErr
output io_out_s3_full_pred_0_is_br_sharing
output io_out_s3_full_pred_0_hit
output io_out_s3_full_pred_1_br_taken_mask_0 同上
output io_out_s3_full_pred_1_br_taken_mask_1
output io_out_s3_full_pred_1_slot_valids_0
output io_out_s3_full_pred_1_slot_valids_1
output [40:0] io_out_s3_full_pred_1_targets_0
output [40:0] io_out_s3_full_pred_1_targets_1
output [40:0] io_out_s3_full_pred_1_jalr_target
output [40:0] io_out_s3_full_pred_1_fallThroughAddr
output io_out_s3_full_pred_1_fallThroughErr
output io_out_s3_full_pred_1_is_br_sharing
output io_out_s3_full_pred_1_hit
output io_out_s3_full_pred_2_br_taken_mask_0 同上
output io_out_s3_full_pred_2_br_taken_mask_1
output io_out_s3_full_pred_2_slot_valids_0
output io_out_s3_full_pred_2_slot_valids_1
output [40:0] io_out_s3_full_pred_2_targets_0
output [40:0] io_out_s3_full_pred_2_targets_1
output [40:0] io_out_s3_full_pred_2_jalr_target
output [40:0] io_out_s3_full_pred_2_fallThroughAddr
output io_out_s3_full_pred_2_fallThroughErr
output io_out_s3_full_pred_2_is_jalr
output io_out_s3_full_pred_2_is_call
output io_out_s3_full_pred_2_is_ret
output io_out_s3_full_pred_2_is_br_sharing
output io_out_s3_full_pred_2_hit
output io_out_s3_full_pred_3_br_taken_mask_0 同上
output io_out_s3_full_pred_3_br_taken_mask_1
output io_out_s3_full_pred_3_slot_valids_0
output io_out_s3_full_pred_3_slot_valids_1
output [40:0] io_out_s3_full_pred_3_targets_0
output [40:0] io_out_s3_full_pred_3_targets_1
output [40:0] io_out_s3_full_pred_3_jalr_target
output [3:0] io_out_s3_full_pred_3_offsets_0
output [3:0] io_out_s3_full_pred_3_offsets_1
output [40:0] io_out_s3_full_pred_3_fallThroughAddr
output io_out_s3_full_pred_3_fallThroughErr
output io_out_s3_full_pred_3_is_br_sharing
output io_out_s3_full_pred_3_hit
output [222:0] io_out_last_stage_meta 最后一个阶段输出的 meta 信息
output io_out_last_stage_ftb_entry_valid 最后一个阶段输出的 FTB 项
output [3:0] io_out_last_stage_ftb_entry_brSlots_0_offset
output [11:0] io_out_last_stage_ftb_entry_brSlots_0_lower
output [1:0] io_out_last_stage_ftb_entry_brSlots_0_tarStat
output io_out_last_stage_ftb_entry_brSlots_0_sharing
output io_out_last_stage_ftb_entry_brSlots_0_valid
output [3:0] io_out_last_stage_ftb_entry_tailSlot_offset
output [19:0] io_out_last_stage_ftb_entry_tailSlot_lower
output [1:0] io_out_last_stage_ftb_entry_tailSlot_tarStat
output io_out_last_stage_ftb_entry_tailSlot_sharing
output io_out_last_stage_ftb_entry_tailSlot_valid
output [3:0] io_out_last_stage_ftb_entry_pftAddr
output io_out_last_stage_ftb_entry_carry
output io_out_last_stage_ftb_entry_isCall
output io_out_last_stage_ftb_entry_isRet
output io_out_last_stage_ftb_entry_isJalr
output io_out_last_stage_ftb_entry_last_may_be_rvi_call
output io_out_last_stage_ftb_entry_always_taken_0
output io_out_last_stage_ftb_entry_always_taken_1
input io_ctrl_btb_enable 使能信号
input io_s0_fire_0 s0 阶段流水线控制信号
input io_s0_fire_1
input io_s0_fire_2
input io_s0_fire_3
output io_s1_ready s1 阶段流水线控制信号
input io_s1_fire_0
input io_s1_fire_1
input io_s1_fire_2
input io_s1_fire_3
input io_s2_fire_0 s2 阶段流水线控制信号
input io_s2_fire_1
input io_s2_fire_2
input io_s2_fire_3
input io_update_valid 更新有效性
input [40:0] io_update_bits_pc 传回的预测块pc(用于指示更新的预测块)
input io_update_bits_ftb_entry_valid 是否启用
input [3:0] io_update_bits_ftb_entry_brSlots_0_offset solt 0 中分支指令相对于地址块起始pc的偏移
input [11:0] io_update_bits_ftb_entry_brSlots_0_lower 跳转目标地址的低位
input [1:0] io_update_bits_ftb_entry_brSlots_0_tarStat 跳转后的 pc 高位是否进退位
input io_update_bits_ftb_entry_brSlots_0_sharing 无条件跳转指令槽中存储条件分支指令
input io_update_bits_ftb_entry_brSlots_0_valid 是否启用
input [3:0] io_update_bits_ftb_entry_tailSlot_offset solt 1 中分支指令相对于地址块起始pc的偏移
input [19:0] io_update_bits_ftb_entry_tailSlot_lower 跳转目标地址的低位
input [1:0] io_update_bits_ftb_entry_tailSlot_tarStat 跳转后的 pc 高位是否进退位
input io_update_bits_ftb_entry_tailSlot_sharing 无条件跳转指令槽中存储条件分支指令
input io_update_bits_ftb_entry_tailSlot_valid 是否启用
input [3:0] io_update_bits_ftb_entry_pftAddr Partial Fallthrough Addr 如果预测块中没有跳转,那么程序将会顺序执行到达的地址,预测块的结束地址。
input io_update_bits_ftb_entry_carry pc+pft时是否产生进位
input io_update_bits_ftb_entry_isCall 是否是函数调用
input io_update_bits_ftb_entry_isRet 是否是函数返回
input io_update_bits_ftb_entry_isJalr 是否是 jalr 指令
input io_update_bits_ftb_entry_last_may_be_rvi_call 最后一个指令槽存储的可能是 rvi 的 call 指令
input io_update_bits_ftb_entry_always_taken_0 是否预测为总是跳转
input io_update_bits_ftb_entry_always_taken_1 是否预测为总是跳转
input io_update_bits_old_entry 是否是旧的 FTB 项
input [222:0] io_update_bits_meta meta 信息

5 - ITTAGE 分支预测器

功能介绍

对于一般条件分支指令,只需要预测跳转(taken),或者不跳转(no taken),但是对于间接跳转,如call/jump等指令,需要预测跳转到哪里去(Target)。为了让TAGE支持预测跳转地址,ITTAGE(Indirect Target TAGE)应运而生。

ITTAGE 与 TAGE 的主要区别在于,在T0和Tn表中,多了 Target PC 数据。预测时,ITTAGE选择匹配到的、历史最长的表项中的 Target 作为预测结果,并利用 2bit 饱和计数器决定是否输出该结果,或选用替代预测结果。TAGE 预测器说明,请参考 TAGE-SC分支预测器

昆明湖 ITTAGE 分支预测器

由于在昆明湖的BPU设计中,采用多预测器级联的方式进行预测,因此在子预测器实现上,会与原始预测器有所不同,其中多数表现在默认预测结果上。

基本功能

ITTAGE基本功能类似 TAGE 分支预测器,但存在以下不同点:

  1. 在表项中增加了 Target 作为跳转的目标地址项,用于预测跳转目标地址。
  2. 饱和计数器ctr也不再提供预测方向,而改为决定是否输出结果(只是预测信息)。
  3. 由于每个分支预测块中只有一个间接跳转指令,所以ITTAGE也只考虑一条指令。

流水线

ITTAGE 内含三个流水级,第一级计算索引,第二级通过索引读出SRAM表中的结果

  1. 第0拍,s0:第一个流水级的输入,一般是pc和折叠历史。

第一流水级运行:计算index。通过寄存器输出到s1

  1. 第1拍,s1:第二个流水级的输入,为第一级阶段计算好的index等数据。

第二流水级运行:访存SRAM,读取预测用信息。通过寄存器输出到s2

  1. 第2拍,s2:第三个流水级的输入,为第二级从SRAM中读出的原始预测信息

**第三流水级运行:**处理原始预测信息,决定是否输出预测结果。

  1. 第3拍,s3:预测结果就绪,此时已经可以使用预测结果

数据结构

在昆明湖的实现中,T0与Tn的表结构如下所示:

预测器 作用 表项构成 项数
基准预测器T0 用于在其他预测器的预测结果都无效时输出预测结果 虚表,不存在。 直接将上级预测器FTB 的预测结果作为表项结果 虚表,不存在。 直接将上级预测器FTB结果作为索引到的结果
预测表T1-T2 对每个预测块的输入,所有Tn表都进行预测,在所有预测有效的结果中,选择历史记录最长的结果作为 原始预测信息。历史记录长度由输入的H决定 target: 41 bits; valid: 1bit; tag: 9bits; ctr: 2bits; us: 1bit(usefulness计数器) 256项
预测表T3-T5 512项

T0,Tn表的检索方法

检索方法与 TAGE 分支预测器一致,只是各表的配置选项不同。

表名称 FH长度 FH1长度 FH2长度 最近历史长度(用到GH中的位数)
T1 4比特 4比特 4比特 低4位,即把最新4位历史,折叠成FH、FH1、FH2
T2 8比特 8比特 8比特 低8位,即把最新8位历史,折叠成FH、FH1、FH2
T3 9比特 9比特 8比特 低13位,即把最新13位历史,折叠成FH、FH1、FH2
T4 9比特 9比特 8比特 低16位,即把最新16位历史,折叠成FH、FH1、FH2
T5 9比特 9比特 8比特 低32位,即把最新32位历史,折叠成FH、FH1、FH2

其他过程(计算方式计算公式)类似 TAGE-SC分支预测器

替代预测器

当Tn表给出的预测结果,“预测信心”不足时,需要对预测结果进行跳转,成为“替代预测器”。该过程与 TAGE 类似,具体请先阅读TAGE的对应部分。与 TAGE 不同,ITTAGE 的 ctr 并不给出预测方向,只决定是否输出该结果(预测信心)。当 ctr 为 2b00 的时候,即认定为弱信心。选择替代预测结果:

  1. 如果命中多个个表,输出长度第二长的表中表项的 Target
  2. 否则输出 T0 Target(FTB Target)

预测过程

预测过程与 TAGE 类似,但与 TAGE 不同的是ITTAGE 多了一步根据 ctr 决定是否输出预测结果。具体过程如下:

  1. 当 ITTAGE 表项的 ctr 不为 2b00 时,输出 Target
  2. 当 ITTAGE 表项的 ctr 为 2b00 时,输出替代预测结果
    1. 如果有次长的历史(第二个表也命中),则输出 次长的 Target
    2. 否则输出 FTB Target
  3. 当 ITTAGE 表项没命中时,输出 T0 Target(FTB Target)

训练过程

该过程与 TAGE 类似,具体不同点如下:

  1. 表项更新(原始预测数据):
    1. ctr:
      1. 若预测地址与实际一致,则将对应provider(提供原始预测数据的表)表项的ctr计数器自增1
      2. 若预测地址与实际不一致,则将对应provider表项的ctr计数器自减1
      3. ITTAGE中,会根据ctr判断是否采取这个预测的跳转目标结果。 如果多个表均命中,且历史最长表的ctr为0,则采取替代预测逻辑(历史次长的表 或 T0)。 更新时始终更新历史最长的表,如果采用了替代预测,也同时更新替代预测表。
    2. target:
      1. 当待更新表项在进行本次预测时的ctr为0时,直接将实际的最终跳转结果存入target,覆盖
      2. 如果是在申请新表项,直接将实际的最终跳转结果存入target
      3. 否则不修改target
    3. usefulness:
      1. 当provider预测正确替代预测错误时provider的usefulness置1
      2. 如果替代预测是弱信心,并且预测正确,则provider的usefulness置1。 如果替代预测是弱信心,并且预测错误,则provider的usefulness置0。
    4. 新表项:
      1. 每次由于历史最长表有信心的预测出错时(不是由于使用替代预测出错),会尝试随机在历史更长表中申请一个表项。能申请到的条件是对应项的useful为0
      2. 如果更长的useful均不为0,则分配失败
  2. 重置useful位:
    1. 每次预测出错时会申请新表项,如果分配失败则tickCtr(用于重置全部useful的8bit饱和计数器) +1,成功则 -1
    2. 当tickCtr为最大值时,设置ITTAGE中的所有useful为0,并设置tickCtr为0。

接口列表

接口类型 位宽 信号名 备注
input clock
input reset
input [40:0] io_in_bits_s0_pc_3 用于预测的PC
input [7:0] io_in_bits_folded_hist_3_hist_14_folded_hist T2 折叠历史
input [8:0] io_in_bits_folded_hist_3_hist_13_folded_hist T3 折叠历史
input [3:0] io_in_bits_folded_hist_3_hist_12_folded_hist T1 折叠历史
input [8:0] io_in_bits_folded_hist_3_hist_10_folded_hist T5 折叠历史
input [8:0] io_in_bits_folded_hist_3_hist_6_folded_hist T4 折叠历史
input [7:0] io_in_bits_folded_hist_3_hist_4_folded_hist T3 折叠历史
input [7:0] io_in_bits_folded_hist_3_hist_3_folded_hist T5 折叠历史
input [7:0] io_in_bits_folded_hist_3_hist_2_folded_hist T4 折叠历史
input [40:0] io_in_bits_resp_in_0_s2_full_pred_3_jalr_target FTB项输入,提供替代预测
output [40:0] io_out_s3_full_pred_0_jalr_target
output [40:0] io_out_s3_full_pred_1_jalr_target
output [40:0] io_out_s3_full_pred_2_jalr_target
output [40:0] io_out_s3_full_pred_3_jalr_target
output [222:0] io_out_last_stage_meta [100:0] 有效,是ITTAGE的Meta信息
input io_s0_fire_3 s0阶段使能信号
input io_s1_fire_3 s1阶段使能信号
input io_s2_fire_0 s2阶段使能信号,相同
input io_s2_fire_1
input io_s2_fire_2
input io_s2_fire_3
input io_update_valid 是否进行更新
input [40:0] io_update_bits_pc 待更新的预测块pc索引
input [7:0] io_update_bits_spec_info_folded_hist_hist_14_folded_hist T2 更新时传入的历史
input [8:0] io_update_bits_spec_info_folded_hist_hist_13_folded_hist T3 更新时传入的历史
input [3:0] io_update_bits_spec_info_folded_hist_hist_12_folded_hist T1 更新时传入的历史
input [8:0] io_update_bits_spec_info_folded_hist_hist_10_folded_hist T5 更新时传入的历史
input [8:0] io_update_bits_spec_info_folded_hist_hist_6_folded_hist T4 更新时传入的历史
input [7:0] io_update_bits_spec_info_folded_hist_hist_4_folded_hist T3 更新时传入的历史
input [7:0] io_update_bits_spec_info_folded_hist_hist_3_folded_hist T5 更新时传入的历史
input [7:0] io_update_bits_spec_info_folded_hist_hist_2_folded_hist T4 更新时传入的历史
input [3:0] io_update_bits_ftb_entry_tailSlot_offset 待更新的FTB项offset
input io_update_bits_ftb_entry_tailSlot_sharing 待更新的FTB项是否是有条件跳转
input io_update_bits_ftb_entry_tailSlot_valid 待更新的tailSlot是否启用
input io_update_bits_ftb_entry_isRet tailSlot是否是Ret指令
input io_update_bits_ftb_entry_isJalr tailSlot是否是Jalr指令
input io_update_bits_cfi_idx_valid 控制流指令在预测块中的索引.valid信号
input [3:0] io_update_bits_cfi_idx_bits 控制流指令在预测块中的索引
input io_update_bits_jmp_taken 预测块内无条件跳转指令被触发
input io_update_bits_mispred_mask_2 是否预测错误
input [222:0] io_update_bits_meta 预测时传出 meta 信息的[222:25] 即{25h0, _ubtb_io_out_last_stage_meta[5:0] ,_tage_io_out_last_stage_meta[87:0] ,_ftb_io_out_last_stage_meta[2:0], _ittage_io_out_last_stage_meta[100:0]}
input [40:0] io_update_bits_full_target 预测块的跳转目标(下一个预测块的起始地址)

不产生影响的透传信号

这里的信号不产生影响,不重要
接口类型 位宽 信号名 备注
input io_in_bits_resp_in_0_s2_full_pred_0_br_taken_mask_0 从FTB输入 完全透传到输出 包括jalr_target
input io_in_bits_resp_in_0_s2_full_pred_0_br_taken_mask_1
input io_in_bits_resp_in_0_s2_full_pred_0_slot_valids_0
input io_in_bits_resp_in_0_s2_full_pred_0_slot_valids_1
input [40:0] io_in_bits_resp_in_0_s2_full_pred_0_targets_0
input [40:0] io_in_bits_resp_in_0_s2_full_pred_0_targets_1
input [40:0] io_in_bits_resp_in_0_s2_full_pred_0_jalr_target
input [3:0] io_in_bits_resp_in_0_s2_full_pred_0_offsets_0
input [3:0] io_in_bits_resp_in_0_s2_full_pred_0_offsets_1
input [40:0] io_in_bits_resp_in_0_s2_full_pred_0_fallThroughAddr
input io_in_bits_resp_in_0_s2_full_pred_0_is_br_sharing
input io_in_bits_resp_in_0_s2_full_pred_0_hit
input io_in_bits_resp_in_0_s2_full_pred_1_br_taken_mask_0
input io_in_bits_resp_in_0_s2_full_pred_1_br_taken_mask_1
input io_in_bits_resp_in_0_s2_full_pred_1_slot_valids_0
input io_in_bits_resp_in_0_s2_full_pred_1_slot_valids_1
input [40:0] io_in_bits_resp_in_0_s2_full_pred_1_targets_0
input [40:0] io_in_bits_resp_in_0_s2_full_pred_1_targets_1
input [40:0] io_in_bits_resp_in_0_s2_full_pred_1_jalr_target
input [3:0] io_in_bits_resp_in_0_s2_full_pred_1_offsets_0
input [3:0] io_in_bits_resp_in_0_s2_full_pred_1_offsets_1
input [40:0] io_in_bits_resp_in_0_s2_full_pred_1_fallThroughAddr
input io_in_bits_resp_in_0_s2_full_pred_1_is_br_sharing
input io_in_bits_resp_in_0_s2_full_pred_1_hit
input io_in_bits_resp_in_0_s2_full_pred_2_br_taken_mask_0
input io_in_bits_resp_in_0_s2_full_pred_2_br_taken_mask_1
input io_in_bits_resp_in_0_s2_full_pred_2_slot_valids_0
input io_in_bits_resp_in_0_s2_full_pred_2_slot_valids_1
input [40:0] io_in_bits_resp_in_0_s2_full_pred_2_targets_0
input [40:0] io_in_bits_resp_in_0_s2_full_pred_2_targets_1
input [40:0] io_in_bits_resp_in_0_s2_full_pred_2_jalr_target
input [3:0] io_in_bits_resp_in_0_s2_full_pred_2_offsets_0
input [3:0] io_in_bits_resp_in_0_s2_full_pred_2_offsets_1
input [40:0] io_in_bits_resp_in_0_s2_full_pred_2_fallThroughAddr
input io_in_bits_resp_in_0_s2_full_pred_2_is_jalr RAS 模块使用的信息,透传
input io_in_bits_resp_in_0_s2_full_pred_2_is_call
input io_in_bits_resp_in_0_s2_full_pred_2_is_ret
input io_in_bits_resp_in_0_s2_full_pred_2_last_may_be_rvi_call
input io_in_bits_resp_in_0_s2_full_pred_2_is_br_sharing 从FTB输入 完全透传到输出 包括jalr_target fallThroughErr 表示 FTB项 中记录的 pftAddr 有误 生成方式:比较 pftAddr 代表的预测块结束地址是否大于预测块的起始地址,如果小于,则代表出现错误,此信号置为有效。这种情况可能会发生在 pc 索引到错误的 FTB 项的情况。 FTQ使用这个变量,与ITTAGE无关
input io_in_bits_resp_in_0_s2_full_pred_2_hit
input io_in_bits_resp_in_0_s2_full_pred_3_br_taken_mask_0
input io_in_bits_resp_in_0_s2_full_pred_3_br_taken_mask_1
input io_in_bits_resp_in_0_s2_full_pred_3_slot_valids_0
input io_in_bits_resp_in_0_s2_full_pred_3_slot_valids_1
input [40:0] io_in_bits_resp_in_0_s2_full_pred_3_targets_0
input [40:0] io_in_bits_resp_in_0_s2_full_pred_3_targets_1
input [3:0] io_in_bits_resp_in_0_s2_full_pred_3_offsets_0
input [3:0] io_in_bits_resp_in_0_s2_full_pred_3_offsets_1
input [40:0] io_in_bits_resp_in_0_s2_full_pred_3_fallThroughAddr
input io_in_bits_resp_in_0_s2_full_pred_3_fallThroughErr
input io_in_bits_resp_in_0_s2_full_pred_3_is_br_sharing
input io_in_bits_resp_in_0_s2_full_pred_3_hit
input io_in_bits_resp_in_0_s3_full_pred_0_br_taken_mask_0 除了 jalr_target 可能被修改,其他都是透传
input io_in_bits_resp_in_0_s3_full_pred_0_br_taken_mask_1
input io_in_bits_resp_in_0_s3_full_pred_0_slot_valids_0
input io_in_bits_resp_in_0_s3_full_pred_0_slot_valids_1
input [40:0] io_in_bits_resp_in_0_s3_full_pred_0_targets_0
input [40:0] io_in_bits_resp_in_0_s3_full_pred_0_targets_1
input [40:0] io_in_bits_resp_in_0_s3_full_pred_0_fallThroughAddr
input io_in_bits_resp_in_0_s3_full_pred_0_fallThroughErr
input io_in_bits_resp_in_0_s3_full_pred_0_is_br_sharing
input io_in_bits_resp_in_0_s3_full_pred_0_hit
input [40:0] io_in_bits_resp_in_0_s3_full_pred_0_jalr_target
input io_in_bits_resp_in_0_s3_full_pred_1_br_taken_mask_0 同上
input io_in_bits_resp_in_0_s3_full_pred_1_br_taken_mask_1
input io_in_bits_resp_in_0_s3_full_pred_1_slot_valids_0
input io_in_bits_resp_in_0_s3_full_pred_1_slot_valids_1
input [40:0] io_in_bits_resp_in_0_s3_full_pred_1_targets_0
input [40:0] io_in_bits_resp_in_0_s3_full_pred_1_targets_1
input [40:0] io_in_bits_resp_in_0_s3_full_pred_1_fallThroughAddr
input io_in_bits_resp_in_0_s3_full_pred_1_fallThroughErr
input io_in_bits_resp_in_0_s3_full_pred_1_is_br_sharing
input io_in_bits_resp_in_0_s3_full_pred_1_hit
input [40:0] io_in_bits_resp_in_0_s3_full_pred_1_jalr_target
input io_in_bits_resp_in_0_s3_full_pred_2_br_taken_mask_0 同上
input io_in_bits_resp_in_0_s3_full_pred_2_br_taken_mask_1
input io_in_bits_resp_in_0_s3_full_pred_2_slot_valids_0
input io_in_bits_resp_in_0_s3_full_pred_2_slot_valids_1
input [40:0] io_in_bits_resp_in_0_s3_full_pred_2_targets_0
input [40:0] io_in_bits_resp_in_0_s3_full_pred_2_targets_1
input [40:0] io_in_bits_resp_in_0_s3_full_pred_2_fallThroughAddr
input io_in_bits_resp_in_0_s3_full_pred_2_fallThroughErr
input io_in_bits_resp_in_0_s3_full_pred_2_is_jalr
input io_in_bits_resp_in_0_s3_full_pred_2_is_call
input io_in_bits_resp_in_0_s3_full_pred_2_is_ret
input io_in_bits_resp_in_0_s3_full_pred_2_is_br_sharing
input io_in_bits_resp_in_0_s3_full_pred_2_hit
input [40:0] io_in_bits_resp_in_0_s3_full_pred_2_jalr_target
input io_in_bits_resp_in_0_s3_full_pred_3_br_taken_mask_0 同上
input io_in_bits_resp_in_0_s3_full_pred_3_br_taken_mask_1
input io_in_bits_resp_in_0_s3_full_pred_3_slot_valids_0
input io_in_bits_resp_in_0_s3_full_pred_3_slot_valids_1
input [40:0] io_in_bits_resp_in_0_s3_full_pred_3_targets_0
input [40:0] io_in_bits_resp_in_0_s3_full_pred_3_targets_1
input [3:0] io_in_bits_resp_in_0_s3_full_pred_3_offsets_0
input [3:0] io_in_bits_resp_in_0_s3_full_pred_3_offsets_1
input [40:0] io_in_bits_resp_in_0_s3_full_pred_3_fallThroughAddr
input io_in_bits_resp_in_0_s3_full_pred_3_fallThroughErr
input io_in_bits_resp_in_0_s3_full_pred_3_is_br_sharing
input io_in_bits_resp_in_0_s3_full_pred_3_hit
input [40:0] io_in_bits_resp_in_0_s3_full_pred_3_jalr_target
input io_in_bits_resp_in_0_last_stage_ftb_entry_valid 透传到output,不做修改 来源是FTB
input [3:0] io_in_bits_resp_in_0_last_stage_ftb_entry_brSlots_0_offset
input [11:0] io_in_bits_resp_in_0_last_stage_ftb_entry_brSlots_0_lower
input [1:0] io_in_bits_resp_in_0_last_stage_ftb_entry_brSlots_0_tarStat
input io_in_bits_resp_in_0_last_stage_ftb_entry_brSlots_0_sharing
input io_in_bits_resp_in_0_last_stage_ftb_entry_brSlots_0_valid
input [3:0] io_in_bits_resp_in_0_last_stage_ftb_entry_tailSlot_offset
input [19:0] io_in_bits_resp_in_0_last_stage_ftb_entry_tailSlot_lower
input [1:0] io_in_bits_resp_in_0_last_stage_ftb_entry_tailSlot_tarStat
input io_in_bits_resp_in_0_last_stage_ftb_entry_tailSlot_sharing
input io_in_bits_resp_in_0_last_stage_ftb_entry_tailSlot_valid
input [3:0] io_in_bits_resp_in_0_last_stage_ftb_entry_pftAddr
input io_in_bits_resp_in_0_last_stage_ftb_entry_carry
input io_in_bits_resp_in_0_last_stage_ftb_entry_isCall
input io_in_bits_resp_in_0_last_stage_ftb_entry_isRet
input io_in_bits_resp_in_0_last_stage_ftb_entry_isJalr
input io_in_bits_resp_in_0_last_stage_ftb_entry_last_may_be_rvi_call
input io_in_bits_resp_in_0_last_stage_ftb_entry_always_taken_0
input io_in_bits_resp_in_0_last_stage_ftb_entry_always_taken_1
output io_out_s2_full_pred_0_br_taken_mask_0 完全透传传入值 prefix: io_in_bits_resp_in_
output io_out_s2_full_pred_0_br_taken_mask_1
output io_out_s2_full_pred_0_slot_valids_0
output io_out_s2_full_pred_0_slot_valids_1
output [40:0] io_out_s2_full_pred_0_targets_0
output [40:0] io_out_s2_full_pred_0_targets_1
output [40:0] io_out_s2_full_pred_0_jalr_target
output [3:0] io_out_s2_full_pred_0_offsets_0
output [3:0] io_out_s2_full_pred_0_offsets_1
output [40:0] io_out_s2_full_pred_0_fallThroughAddr
output io_out_s2_full_pred_0_is_br_sharing
output io_out_s2_full_pred_0_hit
output io_out_s2_full_pred_1_br_taken_mask_0
output io_out_s2_full_pred_1_br_taken_mask_1
output io_out_s2_full_pred_1_slot_valids_0
output io_out_s2_full_pred_1_slot_valids_1
output [40:0] io_out_s2_full_pred_1_targets_0
output [40:0] io_out_s2_full_pred_1_targets_1
output [40:0] io_out_s2_full_pred_1_jalr_target
output [3:0] io_out_s2_full_pred_1_offsets_0
output [3:0] io_out_s2_full_pred_1_offsets_1
output [40:0] io_out_s2_full_pred_1_fallThroughAddr
output io_out_s2_full_pred_1_is_br_sharing
output io_out_s2_full_pred_1_hit
output io_out_s2_full_pred_2_br_taken_mask_0
output io_out_s2_full_pred_2_br_taken_mask_1
output io_out_s2_full_pred_2_slot_valids_0
output io_out_s2_full_pred_2_slot_valids_1
output [40:0] io_out_s2_full_pred_2_targets_0
output [40:0] io_out_s2_full_pred_2_targets_1
output [40:0] io_out_s2_full_pred_2_jalr_target
output [3:0] io_out_s2_full_pred_2_offsets_0
output [3:0] io_out_s2_full_pred_2_offsets_1
output [40:0] io_out_s2_full_pred_2_fallThroughAddr
output io_out_s2_full_pred_2_is_jalr
output io_out_s2_full_pred_2_is_call
output io_out_s2_full_pred_2_is_ret
output io_out_s2_full_pred_2_last_may_be_rvi_call
output io_out_s2_full_pred_2_is_br_sharing
output io_out_s2_full_pred_2_hit
output io_out_s2_full_pred_3_br_taken_mask_0
output io_out_s2_full_pred_3_br_taken_mask_1
output io_out_s2_full_pred_3_slot_valids_0
output io_out_s2_full_pred_3_slot_valids_1
output [40:0] io_out_s2_full_pred_3_targets_0
output [40:0] io_out_s2_full_pred_3_targets_1
output [40:0] io_out_s2_full_pred_3_jalr_target
output [3:0] io_out_s2_full_pred_3_offsets_0
output [3:0] io_out_s2_full_pred_3_offsets_1
output [40:0] io_out_s2_full_pred_3_fallThroughAddr
output io_out_s2_full_pred_3_fallThroughErr
output io_out_s2_full_pred_3_is_br_sharing
output io_out_s2_full_pred_3_hit
output io_out_s3_full_pred_0_br_taken_mask_0 见对应prefix的输入
output io_out_s3_full_pred_0_br_taken_mask_1
output io_out_s3_full_pred_0_slot_valids_0
output io_out_s3_full_pred_0_slot_valids_1
output [40:0] io_out_s3_full_pred_0_targets_0
output [40:0] io_out_s3_full_pred_0_targets_1
output [40:0] io_out_s3_full_pred_0_fallThroughAddr
output io_out_s3_full_pred_0_fallThroughErr
output io_out_s3_full_pred_0_is_br_sharing
output io_out_s3_full_pred_0_hit
output io_out_s3_full_pred_1_br_taken_mask_0 见对应prefix的输入
output io_out_s3_full_pred_1_br_taken_mask_1
output io_out_s3_full_pred_1_slot_valids_0
output io_out_s3_full_pred_1_slot_valids_1
output [40:0] io_out_s3_full_pred_1_targets_0
output [40:0] io_out_s3_full_pred_1_targets_1
output [40:0] io_out_s3_full_pred_1_fallThroughAddr
output io_out_s3_full_pred_1_fallThroughErr
output io_out_s3_full_pred_1_is_br_sharing
output io_out_s3_full_pred_1_hit
output io_out_s3_full_pred_2_br_taken_mask_0 见对应prefix的输入
output io_out_s3_full_pred_2_br_taken_mask_1
output io_out_s3_full_pred_2_slot_valids_0
output io_out_s3_full_pred_2_slot_valids_1
output [40:0] io_out_s3_full_pred_2_targets_0
output [40:0] io_out_s3_full_pred_2_targets_1
output [40:0] io_out_s3_full_pred_2_fallThroughAddr
output io_out_s3_full_pred_2_fallThroughErr
output io_out_s3_full_pred_2_is_jalr
output io_out_s3_full_pred_2_is_call
output io_out_s3_full_pred_2_is_ret
output io_out_s3_full_pred_2_is_br_sharing
output io_out_s3_full_pred_2_hit
output io_out_s3_full_pred_3_br_taken_mask_0 见对应prefix的输入
output io_out_s3_full_pred_3_br_taken_mask_1
output io_out_s3_full_pred_3_slot_valids_0
output io_out_s3_full_pred_3_slot_valids_1
output [40:0] io_out_s3_full_pred_3_targets_0
output [40:0] io_out_s3_full_pred_3_targets_1
output [3:0] io_out_s3_full_pred_3_offsets_0
output [3:0] io_out_s3_full_pred_3_offsets_1
output [40:0] io_out_s3_full_pred_3_fallThroughAddr
output io_out_s3_full_pred_3_fallThroughErr
output io_out_s3_full_pred_3_is_br_sharing
output io_out_s3_full_pred_3_hit
output io_out_last_stage_ftb_entry_valid 完全透传传入的值
output [3:0] io_out_last_stage_ftb_entry_brSlots_0_offset
output [11:0] io_out_last_stage_ftb_entry_brSlots_0_lower
output [1:0] io_out_last_stage_ftb_entry_brSlots_0_tarStat
output io_out_last_stage_ftb_entry_brSlots_0_sharing
output io_out_last_stage_ftb_entry_brSlots_0_valid
output [3:0] io_out_last_stage_ftb_entry_tailSlot_offset
output [19:0] io_out_last_stage_ftb_entry_tailSlot_lower
output [1:0] io_out_last_stage_ftb_entry_tailSlot_tarStat
output io_out_last_stage_ftb_entry_tailSlot_sharing
output io_out_last_stage_ftb_entry_tailSlot_valid
output [3:0] io_out_last_stage_ftb_entry_pftAddr
output io_out_last_stage_ftb_entry_carry
output io_out_last_stage_ftb_entry_isCall
output io_out_last_stage_ftb_entry_isRet
output io_out_last_stage_ftb_entry_isJalr
output io_out_last_stage_ftb_entry_last_may_be_rvi_call
output io_out_last_stage_ftb_entry_always_taken_0
output io_out_last_stage_ftb_entry_always_taken_1

其他Meta见对应子预测其文档

_ubtb_io_out_last_stage_meta

_tage_io_out_last_stage_meta

_ftb_io_out_last_stage_meta

ittage_io_out_last_stage_meta[100:0]

位宽 信号名 备注
100 s3_provided 是否有结果
[99:97] s3_provider 提供结果的表项
96 s3_altProvided 是否有替代预测表项
[95:93] s3_altProvider 提供结果的替代预测表项
92 resp_meta_altDiffers 替代预测的结果是否和主预测的结果不同
91 s3_providerU 主预测的useful bit
[90:89] s3_providerCtr 主预测给出的置信度
[88:87] s3_altProviderCtr 替代预测给出的置信度
86 resp_meta_allocate_valid_r 有空余的表项可供申请
[85:83] resp_meta_allocate_bits_r 申请哪个表中的表项
82 s3_tageTaken_dup_3 在不使用FTB的情况下始为true,使用FTB也为true
[81:41] s3_providerTarget 主预测给出的跳转地址
[40:0] s3_altProviderTarget 替代预测给出的跳转地址

6 - RAS 分支预测器

RAS介绍

RAS 指的是 “Return Address Stack”,即返回地址栈。它通过跟踪程序的返回地址,帮助确定程序中的分支行为。由前所述,在程序中存在很多分支:if/else、 switch/case、while/for loop、iteration、call/return

等。RAS分支预测器则专门针对 call/return类型。

function _add(a, b){
    return (a > 0 ? a : 0)  + (b > 0? b : 0);
}

function add(a, b){
    return a + b;
}

function sub(a, b){
    return a - b;
}

function main(){
    a = 1;
    b = 2;
    c = add(a, b);
    d = sub(a, b);
}

如上图所示,在main函数中调用了add和sub,add又调用了函数_add。在该过程中,每次调用(call)的跳转地址和返回的地址固定,且在call的时候,就可以同时得到返回地址。函数的调用过程是一个“栈的出入”过程,因此可以通过“栈”结构进行分支预测:每遇到call指令,把当前PC+4(压缩指令和普通指令的偏移不同)进行压栈push操作;遇到return指令,则进行进行pop操作,得到的地址即为目标跳转地址。在基于“预测块”的BPU中,RAS无法知道当前块是否是call或者ret,因此需要依赖其他预测器,利用前级预测器的结果进行RAS操作。

具体的,在香山的RAS预测器中,其s2阶段,需要判断上一级s2的输出是否预测为call或者ret(即输入信号io.s2_full_pred.hit_taken_on_call/ret有效 ),如果是call则push其后续指令地址入栈,是ret则从栈中pop出地址作为预测结果。因为在BPU的预测器中,人为假定s3阶段得到的结果比s2阶段好,所以RAS预测器在s3阶段需要进行检查,如果上一级的s3预测结果与s2不一致,则采信s3的结果,并按需求判断是否需要撤销或者补齐之前s2阶段的栈操作。例如s2阶段预测为call指令,进行了push操作,而s3为普通分支指令,不需要进行任何操作,此时就需要撤销push;如果s2预测为普通分支指令,s3预测为call,则需要进行push操作补齐。

RAS的栈操作

在普通的RAS设计中,通过栈进行预测函数返回地址。在理想情况下,本节假定RAS可进行随时备份,栈顶指针用sp表示,预测地址用paddr表示。RAS的基本操作有以下4种:

  1. PUSH

由于预测可能会出错,出错时需要回到原始状态,因此在push时需要对当前栈的状态进行备份(在软件领域,通常称为“快照”,本文在后继内容中也用快照进行称呼),标记为s。当遇到call指令时,获取call指令的返回地址 addr = 当前pc + 4(如果是压缩指令则addr = pc+2),然后压栈:sp = addr;sp += 1。

  1. POP

原因同上,对当前栈进行快照,标记为s。当遇到ret指令时,预测的跳转地址 paddr = sp,然后进行出栈, sp = sp - 1。对当前栈进行快照,标记为s。

  1. 重定向操作

由于BPU是对程序的分支进行预测,因此就有“预测对”和“预测错”两种情况,当CPU后端发现分支预测错误,就会进行重定向(redirect)操作,告诉BPU哪个地方预测错误,正确结果是多少。重定向时RAS模块会收到正确的分支和当时预测时RAS栈信息。根据正确分支指令类型不同,首先恢复快照s有如下情形:

(1)之前被预测的指令,实际是 call指令,根据redirect中给的addr地址,执行push操作;

(2)之前被预测的指令,实际是 ret指令,执行pop操作;

  1. 提交操作

提交(Commit)操作即后端告诉前端,之前预测的结果正确。理想情况下,RAS预测器此时不需要进行任何操作。

昆明湖中RAS的实现

由于处理器在执行过程中可能会进行错误的猜测,导致数据污染,RAS需要通过恢复机制来确保数据的准确性。特别是在昆明湖的实际电路设计中,不可能有无限大的栈,也无法随时备份数据。因此,昆明湖的RAS实现中,使用了基于持久化栈的返回地址预测器。具体来说,将RAS分为提交栈(commit stack)和推测栈(spec queue)两个部分,推测栈利用了香山BPU的预测结果来进行预测,然后根据后端返回的信息将数据压入提交栈中。下面详细介绍昆明湖的RAS设计:

每次预测时,RAS栈的快照如何获得?

为了实现对RAS栈进行快照的功能,昆明湖的RAS推测栈采用了基于循环数组的 “链式表示“ 。设计具体如下:

在这个设计中,数据管理是通过一个循环数组来实现的。循环数组有一个起始地址(BOS)和一个尾指针(TOSW),两者之间的数据是有效数据,其他部分为空闲数据。使用链式结构表示“Sepc栈”,每个栈元素记录其上一个数据的编号,进行栈操作时可以通过该编号获取上一个元素。RAS栈的栈底指针与BOS共用。

如图所示的初始状态S,RAS栈的元素为0、1、4、5。元素5记录了其上一个元素的位置4,元素4记录了其上一个元素的位置1。在状态S下进行push操作时,RAS栈顶指针TOSR等于TOSW,在新的TOSR位置7存入新元素,并记录其上一个元素的位置5,然后TOSW后移(TOSW = TOSW + 1)。进行pop操作时,RAS栈顶指针TOSR根据栈顶元素保存的索引移动到上一个元素的位置4,但不会修改内存分配指针TOSW,因此Spec栈的元素不一定是连续的。

在栈不溢出的情况下,RAS栈每次通过TOSW在数组上分配新数据,因此在正常的Push/Pop操作中,所有过程状态和中间数据都被保存。当需要恢复到状态S时,只需复位相应的栈指针。因此,在每次RAS预测时,需要将操作前的栈指针(BOS、TOSR、TOSW)保存于预测结果中,以便用于commit栈操作和在重定向时恢复状态。该结构的优点是可以保存完整的过程数据,但频繁的push操作会导致较大的空间资源消耗。

链式RAS浪费存储空间?

当预测结果被确认(commit)后,表示预测是正确的,不会再进行“栈”回滚。也就是说,RAS预测器在收到预测块“P”的commit消息后,不会再收到P块的重定向消息,因此在对P块进行push操作时的快照将不会被用到。

为了优化,RAS栈中的元素被分类处理:未commit的元素存储在链式结构的推测栈中,已commit的元素存储在基于循环数组的提交栈中。这样,原始的RAS栈被拆分成两部分:未commit的部分用链式结构保存,以便需要时可以恢复快照;已commit的部分用普通栈结构保存,因为它们不需要快速恢复。优化后的RAS结构如下所示:

如上图所示,根据普通预测的call/ret和commit提交的call/ret,把原始的RAS栈可以拆分成两个独立的栈,分别称为“预测栈”( spec_stack,链式结构)和“提交栈”(commit_stack,普通结构),由于RASStack对外表现为当拍内就能完成任务,因此在Stack内需要进行数据旁路,对数据进行缓存,由于栈结构发生了变化,具体的Pop/Push操作如下:

1、遇到普通call和ret:

(1)预测块为call正确,在spec_stack上进行push操作,具体为上述链式栈Push过程,并将产生的快照信息送到FTQ存储;

(2)预测块为ret正确,利用spec_stack的栈顶作为预测值,然后进行pop操作,并将产生的快照信息送到FTQ存储,如果spec_stack为空,则使用commit_stack的栈顶元素作为预测结果(不进行pop操作)。

2、Commit操作:

(1)FTQ执行结果为call正确,通过FTQ传回来的快照信息对commit_stack进行push操作,同时更新spec栈的BOS指针,将此次commit的数据及其之前的数据设置为无效数据。

(2)FTQ执行结果为ret正确,通过FTQ传回来的快照信息对commit_stack上进行普通pop操作,注意到在commit pop之前会先对spec栈进行pop操作,所以commit栈弹出来的地址不需要使用。

3、Redirect操作:

(1)从redirect消息中获取之前预测时的栈指针(BOS、TOSR、TOSW,ssp),覆盖当前值指针值完成预测栈状态回滚,若此时为Call指令或者Ret指令,则再对Spec栈进行对应的操作。

(2)该操作不影响提交栈。

输入端,S3的预测结果和S2不一致时该如何处理?

由于昆明湖 BPU 的假定S3的结果比S2好,因此发生不一致时,需要再次对RAS栈进行修复。具体不一致情况和对应的修复操作如下表所示:

S2预测结果 S3预测结果 修复操作
push keep pop
keep pop pop
pop keep push
keep push push

因为SC预测器的特性,S2和S3的操作不存在pop/push或者push/pop情况

其他优化

  1. 每个RAS的spec栈和commit栈的元素都有一个counter计数器,用于节约重复值(递归调用)。例如:当第一次push地址为0xff00,第二次push的也为0xff00时,则只需要把栈顶元素的 counter 进行+1,不需真的进行push操作。

接口说明

在RAS预测器中,核心组件为 RASStack ,其接口说明如下:

接口名称 功能描述 接口名称 功能描述
in.spec_push_valid 预测有Call指令,Spec压栈 in.s2_fire s2信号有效
in.spec_pop_valid 预测有Ret指令,Spec出栈 in.s3_fire s3信号有效
in.spec_push_addr Ret地址 in.s3_cancel s3和s2的预测结果不一样,需要撤销s2的操作
out.spec_pop_addr RAS的栈顶数据 in.s3_meta s3需要的s2时的现场信息
out.ssp commit栈顶指针 in.s3_missed_pop s3判断需要再次进行pop
out.sctr commit栈顶重复元素计数器 in.s3_missed_push s3判断需要再次进行push
out.nsp commit栈顶,会被ssp覆盖 in.s3_pushAddr 需要再次push时的地址
out.TOSR spec栈栈顶指针 in.redirect_valid 需要重定向
out.TOSW spec栈数据分配指针 in.redirect_isCall 真实执行情况是Call
out.BOS spec栈栈底指针 in.redirect_isRet 真实执行情况是Return
in.commit_push_valid FTQ执行结果为Call正确 in.redirect_meta_ssp 之前预测时的现场信息ssp
in.commit_pop_valid FTQ执行结果为Ret正确 in.redirect_meta_sctr 之前预测时的现场信息sctr
in.commit_push_addr 更新信息中的Ret地址 in.redirect_meta_TOSW 之前预测时的现场信息TOSW
in.commit_meta_TOSW 更新信息中的TOSW
in.redirect_meta_TOSR 之前预测时的现场信息TOSR
in.commit_meta_TOSR 更新信息中的TOSR in.redirect_meta_NOS 之前预测时的现场信息NOS
in.commit_meta_ssp 更新信息中的现场信息SSP in.redirect_callAddr 重定向地址
in.commit_meta_sctr 更新信息中的现场信息SCTR

RASStack模块与BasePredictor的接口关系如下:

stack接口 转换过程 描述
s.spec_push_valid io.s2_fire(2) && s2_full_pred.hit_taken_on_call && !io.s3_redirect(2) s2输入有效,且上级预测为call跳转
s.spec_pop_valid io.s2_fire(2) && s2_full_pred.hit_taken_on_ret && !io.s3_redirect(2) s2输入有效,且上级预测为ret跳转
s.spec_push_addr s2_full_pred.fallThroughAddr + Mux(s2_full_pred.last_may_be_rvi_call, 2.U, 0.U) 上级预测器s2预测的fallThroughAddr(即PC+2),判断是否压缩指令是否需要 +2
s.redirect_isCall redirect.bits.level === 0.U && recover_cfi.pd.isCall
s.redirect_isRet redirect.bits.level === 0.U && recover_cfi.pd.isRet
s.redirect_meta_* redirect.bits.cfiUpdate.*
s.commit_push_valid io.update.is_call_taken call指令预测正确
s.commit_push_valid io.update.is_ret_taken ret指令预测正确
s.commit_push_addr update.ftb_entry.getFallThrough(update.pc) + Mux(update.ftb_entry.last_may_be_rvi_call, 2.U, 0.U) 根据是否为压缩指令,进行地址+2或者+0
s.commit_meta_* io.update.bits.meta.asTypeOf(new RASMeta)
io.out.last_stage_spec_info.* s3_meta.* s3_meta = RegEnable(s2_meta, io.s2_fire(2))由s2_meta延迟一怕得到
io.out.last_stage_meta s3_meta
io.out.s2.full_pred.*.jalr_target :=stack.spec_pop_addr 预测地址(栈顶地址,只预测ret)
io.out.s3.full_pred.*.jalr_target :=RegEnable(stack.spec_pop_addr, io.s2_fire(2)) 由s2延迟一拍得到
io.out.s2/3.full_pred.targets.last :=Mux(s2/3_is_jalr, s2/3_jalr_target, s2/3_last_target_in) 如果时call执行,更新targets.last的结果

时序说明

在RAS中,只涉及到2拍,S2和S3。

S2中的主要工作:

1、根据上一个预测器的S2预测结果,完成通过Push/PoP过程完成预测,并得到结果sepc_pop_addr。

2、根据commit信号执行更新操作

S3中的主要工作:

1、根据上一个预测器S3中的预测结果,以及S2进行的操作,判断是否需要进行Pop/Push的撤销操作。预测器假定S3的预测结果比S2好,如果发生S2和S3预测不一致时,RAS预测器采信S3结果进行栈操作。

2、S3和S2的预测过程一致,只是数据不同。

3、执行重定向(重定向信息由前一拍获得)操作