本文是基于SonicBOOM官方文档整理的概述性质文章,具体的实现请参考官方代码。

Overview

下图是SonicBoom微架构的详细block框图:

SonicBOOM

BOOM系列乱序处理器受MIPS R10000以及Alpha 21264微架构的大量启发,如使用同样的统一物理寄存器组设计(显式寄存器重命名,explicit register renaming)。

BOOM流水线

SonicBOOM_pipeline

从概念上来说,BOOM可以分解为10个级流水线结构:Fetch → Decode → Register Rename → Dispatch → Issue → Register Read → Execute → Memory → Writeback → Commit。然后在实现当中,有多个流水级是合并在一起的,最终微架构可以划分为7个流水级:Fetch → Decode/Rename → Rename/Dispatch → Issue/RegisterRead → Execute → Memory → Writeback。其中Commit是异步发生的,因此它不算在流水线的组成当中。由于本文只专注于BOOM的前端实现,因此除前端外的内容将不再赘述。

取指

下图是BOOM的前端微架构图:

SonicBOOM_frontend

BOOM的前端主要功能就是取指并对分支做出预测并在需要时在前端各个流水级进行重定向操作(F0, F1…)。如果后端执行发现了预测错误或者某一个预测器要将前端流水线重定向,则一个重定向的请求会发送前端并从新的指令路径重新取指。前端从ICache中取出的最小单元是一个Fetch Packet,里面包含了若干连续的指令、以及一些其他的信息,包括有效位以及一些需要在后端中使用的分支预测信息等等。此外,PC以及分支预测的信息还会存放到取值目标队列(Fetch Target Queue)当中,给流水线后端所使用。

Rocket Core ICache

BOOM直接使用Rocket中的ICache架构,其使用VIPT来索引。为了节省功耗,ICache一次会读取对齐的固定数量的字节并存放到取指寄存器当中,后续的取指可以直接从这个寄存器中获取。ICache只有在取指寄存器中的指令用完或者分支预测跳转到其他PC地址时才会重新启动。BOOM的ICache有几点需要注意:

  • ICache不支持跨CL取指,且不支持非对齐的取指(与XiangShan有明显区别)
  • ICache不支持hit-under-miss。如果ICache miss,ICache在当前miss处理完之前将不再接受任何的请求。

压缩指令取指

BOOM的前端实现中,从ICache中获取一个Fetch Packet,并译码来得到分支预测所需要的信息,并将Fetch Packet存入到Fetch Buffer当中。而当启用压缩指令后,则需要处理以下的问题:

  • 增加了译码的复杂度
  • 需要找到每条指令从哪里开始
  • 需要移除原先不支持C扩展代码中,一些默认+4的假定,特别是在分支的处理当中
  • 非对齐指令的处理,尤其是在跨CL以及虚页中的指令

在SonicBoom当中,包含RVC支持的微架构处理方式为:

  • 前端将会从ICache中获取 fetchWidth * 16 比特宽度的若干个Fetch Packet
  • 在前端的F3周期中,若干Fetch Packet会从ICache响应队列中出队并入队到Fetch Buffer中
  • F3周期还会跟踪最后一个Fetch Packet中的结尾的16-bit指令、PC以及指令边界,同时,将所有压缩指令转换成32位的指令,并进行预译码操作,入队给Fetch Buffer。预译码可以获取每个指令的起始地址、有效指令等信息
  • 现在,Fetch Buffer可以摒弃无效的指令并将有效的指令进行存储

下面给出一些具体的实现细节信息:

  • 如果一个指令跨越了取指边界,则需要将其锁存住并将其作为其高16位指令所属的Fetch Packet处理
  • 还需要跟踪每条指令是否是RVC,用于计算其PC地址(+2还是+4)

Fetch Buffer

从ICache中取得的Fetch Packet在进行一定处理后会存入Fetch Buffer中。Fetch Buffer用于将前后端解耦。

Fetch Buffer是可配置的,如大小以及是否支持flow-through机制。

分支预测

SonicBoom使用两级预测机制:

  • 快速的下一行预测器(Next-Line Predictor,NLP),其组成就是一个BTB
  • 延迟更高的但更为复杂的主预测器(Backing Predictor,BPD)

NLP

SonicBoom的下一行预测器每个周期都会预测下一条取指的地址。如果后端或者BPD要重定向则会发起请求,NLP会从新的地址重新进行预测。

NLP使用SRAM(?)来搭建,并根据当前的Fetch PC来预测下个周期的Fetch PC。如果预测正确,则不存在任何的空泡。

NLP的实现包括一个全相联的BTB、Bi-Modal Table(BIM)以及RAS。

NLP预测

根据当前的Fetch PC,在index读出的entry中比对PC tag。如果命中,则根据命中entry中是否为条件分支,无条件跳转,或者return并结合BIM以及RAS来决定最终的预测结果以及对应发生跳转的指令。BIM用于决定条件分支是否taken,RAS则用于预测return指令的地址。BTB的entry中还包含了一个预测目标PC,用于决定下一个周期的Fetch PC。

SonicBOOM_btb

上图展示了NLP的基本组成。根据当前的Fetch PC,比对BTB中的每一个tag。若hit且该entry是valid的,BIM以及RAS会根据该entry的类型来决定最终的跳转目标地址。如果该entry是ret,则跳转目标地址由RAS给出。如果该entry是一个无条件跳转,则BIM将不会参与预测。bidx,表示分支跳转的index,标记哪个branch导致该Fetch Packet发生跳转。这样预测跳转的branch往后的指令可以舍弃掉。

BIM只会在BTB entry hit且为branch的情况下介入到预测当中。

NLP更新

需要注意的是,每个branch往余下的流水线传递的信息不单止是它自己的PC,还包括该Fetch Packet的Fetch PC。

BTB更新

BTB只会在BRU或者BPD重定向且分支跳转或无条件跳转时进行更新。如果没有对应的entry,则需要allocate一个新的entry。

RAS更新

RAS在Fetch Packet预译码后,若其中包含一条call指令,则它的返回地址会被push到RAS当中。如果其中包含一条ret指令,则RAS会pop栈顶的返回地址。

超标量预测

NLP做出的预测都是基于Fetch PC的,而不是会造成跳转的具体指令地址。这是因为一个Fetch Packet中可能包含多条分支指令,因此进行tag比对时使用的是Fetch PC。

BPD

NLP可以提供一个快速的,单周期的预测流,但这是建立在较为昂贵的空间以及功耗代价上的,且其大小非常小(只有很少的条件分支可以覆盖到),并且也非常的简单(BIM无法学习到比较复杂的涉及到较长分支历史长度的跳转模式)。BPD的目标就是在提供一个非常高的预测准确度的前提下使用密集型的存储结构搭建(SRAM),从而达到降低面积的效果。BPD只提供taken/not-taken的预测,因此它依赖于其他部件提供的信息如哪条指令是分支跳转以及其跳转的目标地址。因此BPD可以节省存储PC tag以及分支目标地址的空间。

BPD在整个Fetch的各个阶段都会进行访问,且BPD的访问与BTB以及ICache的访问都是并行的。BPD的存储都使用单口的SRAM来实现。

从前端的流水线架构图中可以有,从ICache中获取的指令都会尽快的进行译码,任何被预测为taken的条件分支都会在F4阶段进行重定向的操作。预测快照(prediction snapshots)以及相关的元数据(metadata)都将会被存放在条件分支重命名快照(branch rename snapshots,用于错误预测后的恢复)以及取值目标队列(Fetch Target Queue,FTQ,用于commit时的更新)当中。

进行预测

当BPD进行预测时,必须提供以下的信息:

  • 是否需要提供预测信息?
  • 一个比特流包含taken/not-taken的预测

对于第一点,BPD可能会决定不去进行预测,这包含了两种情况:

  • tag没有命中
  • 存在结构冒险(读写冲突)

对于第二点,BPD提供的比特流的宽度等于Fetch Width。当对应的Fetch Packet从ICache中取出来的时候,则会计算其跳转的目标地址,以及根据预测结果来决定是否需要重定向。

无条件跳转

BPD不会对JAL以及JALR指令进行预测,无条件跳转的预测由NLP来进行。

更新BPD

BPD的更新只会发生在以下的场景中:

  • 对应的条件分支指令被commit时
  • 对应的条件分支指令在execute阶段判定预测错误时

BPD每次预测都会往FTQ中存入一个BPD相应信息包(response info packet),这个info packet一直存在FTQ中直到commit。这个info packet用于更新BPD,也可以称之为预测的快照信息(snapshot)。此外,还有一个条件分支重命名快照(branch rename snapshot)用于在execute阶段发现预测错误时回滚。

管理全局分支历史寄存器

全局分支历史寄存器(GHR)包含了最近的N次分支预测的结果(N是GHR的大小)。GHR的更新是预测更新的,而不是在commit的时候才更新,因此GHR也需要有恢复的机制,也就是当预测错误的时候需要回滚到正确的GHR,因此,每次BPD做出的预测也要把当前GHR的状态快照保存下来。

这里需要注意一点是异常发生时的行为。由于每个branch预测都会包含一份GHR的快照,当异常发生时,都会产生一次flush,也就是重定向,这会导致GHR受到污染(在user的眼中):GHR中将会包含异常处理程序中的分支历史。然而,一些异常的处理包含pipeline replay的机制,也就是在异常处理完成后会重新从导致flush的位置重新执行。

FTQ

FTQ与ROB类似,只是FTQ只维护flying的branch,因此FTQ的大小相比ROB来说可以更小一些。FTQ中包含有每个Fetch Packet在整个fetch阶段中的所有需要记录的预测信息,如需要TAGE index等。当对应的Fetch Packet完全commit之后,其对应的FTQ entry可以被释放。

重命名快照状态

即Rename Snapshot State,用于当execute阶段发现预测错误时恢复。当branch进入decode以及rename阶段时,一个branch tag会被赋予给每个branch并保存当时的rename snapshot。若发生预测错误,则使用该snapshot来进行恢复,如果没有发生预测错误,则直接释放即可。

BPD实现

下图展示了Fetch阶段中BPD以及NLP的时序图:

SonicBOOM_BPD

OS可见全局分支历史

BOOM支持OS可见的全局分支历史。普通的GHR会记录所有特权级别下的分支历史信息,而user-only GHR只会记录用户级指令中包含的分支历史信息。

两位饱和计数器表

两位饱和计数器表(Two-bit Counter Table,2BC)是BPD预测的基本组成。该两位饱和计数器可以分为两个部分:

  • 高位的预测位(P-bit),1表示预测跳转,0表示预测不跳转
  • 低位的信心位(H-bit),1表示强信心,0表示弱信心

SonicBOOM_2btable

在BOOM当中,为了解决SRAM density以及读写冲突问题,使得可以很好的使用单口读写SRAM来实现TAGE/gshare,BOOM对两位饱和计数器的FSM进行的一定的改动:

SonicBOOM_2bcntfsm

P-bit:

  • :每周期需要预测时读出作为预测的结果
  • :只有当出现预测错误时,才会进行写入,写入的是H-bit的值

H-bit:

  • 读:只有当预测错误时才会读出
  • :每条branch完成生命周期时(BRU redirect、commit),写入的是真实的分支跳转结果

通过分离P-bit以及H-bit,使用不同的单口读写SRAM来实现,可以最大程度的减少读写冲突的发生。因为错误预测实际上概率是很低的,且不一定每个周期都会resolve一个branch。同时,写可以被推迟或者干脆直接合并在一起,当没有读写冲突时,可以进行写操作。因此,BOOM的TAGE是读优先于写的。

GShare与TAGE

在最新的SonicBoom实现中,已经摒弃了GShare实现,一律使用TAGE。BOOM的TAGE实现与普适的实现没有明显区别,其使用折叠存储的策略也与XiangShan相似,因此在这里不再赘述,更多的可以参考XiangShan TAGE

RISC-V CPU design engineer.