1 目的
最近在研究伯克利的Sonicboom riscv CPU,其内部对于SFB(short-forwards branch)程序进行了优化操作,某些程序序列下能提升1.7倍的IPC;但是处理比较复杂,不是集中在一个模块处理,而是分散于各级pipline。而相应的描述资料又少,因此采用结合源代码和波形仿真的方式来进行一个学习研究。
2 SFB介绍
SFB翻译过来就是短向前跳分支,简单来说是一串代码序列,如下面一段C程序所示:
int max = 0 ;
int maxid = −1;
for ( i = 0 ; i < n ; i ++) {
if ( x [ i ] >= max ) {
max = x [ i ] ;
maxid = i ;
}
}
这段C代码实现的是最为常见的数组里面找最大值的功能,站在程序员的角度很容易理解。对应的risc-v的汇编程序如下,其中bge指令和后面的两条MV指令组成的就是典型的SFB结构。
loop :
lw x2 , 0( a0 )
bge x1 , x2 , skip
mv x1 , x2
mv a1 , t 0
skip :
addi a0 , a0 , 4
addi t0 , t0 , 0x1
j loop :
在汇编中可以看见每次循环(loop)都会有一条分支指令(bge),以及该分支下的两条MV指令,分支指令不跳转(not_taken)的话就会执行两条MV指令,跳转(taken)的话就会调到标记skip的标号(label)。功能很简单,但是对于当今的高性能CPU来说,不局限于RISC-V架构,都有着很深的流水线(pipline)深度,同时针对于分支指令(Branch)会有先进的分支预测技术来预测分支指令跳转的方向,而不用等到一般处于流水线很后的读寄存器阶段(rigister read)或功能单元的执行阶段(execute)来得到实际跳转方向。
而此时对于这种出现频率特别高的并且覆盖指令很少的分支指令来说,CPU每次都会进行预测,不可避免的会出现预测失败的情况,CPU预测失败就会刷流水线(flush pipline)并重新取指令执行,代价会不可避免的降低IPC。
3 SonicBoom中对于SFB的优化
SonicBoom在微结构上对SFB处理进行了优化,将SFB转化为CPU内部带有特殊标记的微编码(microOps),从而在CPU内部pipline中进行了处理,即使分支预测错误(mispredicted)也不再通过刷流水线的方式(flush pipline)来进行重定向(redirect)。微架构针对于SFB进行的调整具体如下:
- 在前端Frontend增加了SFB检测逻辑;
- 将检测到的SFB指令通过预解码单元(predecode)解析成set-flag和conditional-execute微编码,此时原来的branch微编码被替换了;(set-flag对应到上面例子就是bge,conditional-execute是两条MV指令。为方便描述并与code一致,set-flag称为sfb_br,conditional-execute称为sfb_shadow)
- 增加了单独的寄存器堆(a renamed predicate register file,pregfile)来存放sfb_br的实际计算结果(taken或者not_taken),该寄存器堆要支持多个写接口从而可以支持多个算中的SFB的处理;
- sfb_shadow指令会去读pregfile来得到其对应sfb_br指令的结果,sfb_br实际算出来是not_taken的话表示预测正确,sfb_shadow指令继续执行自己的操作;如果sfb_br实际算出来是taken的话,表示预测错误,那么sfb_shadow指令会完成修复工作:将指令中旧的目的物理寄存器的值(destination physical register)复制到自己的目的物理寄存器中。
根据伯克利的论文中,对于某些指令序列来说,这种针对于SFB的优化将IPC提升到了1.7倍。
下面就从实际chisel源码和EDA仿真波形来进行内部SFB处理逻辑的剖析。
后面涉及到BoomCore的pipline,为方便分析,上图:
3.1 BranchDecode
下面是FrontEnd中的BranchDecoder模块chisel源代码:
class BranchDecode(implicit p: Parameters) extends BoomModule
{
val io = IO(new Bundle {
val inst = Input(UInt(32.W))
val pc = Input(UInt(vaddrBitsExtended.W))
val out = Output(new BranchDecodeSignals)
})
val bpd_csignals =
freechips.rocketchip.rocket.DecodeLogic(io.inst,
List[BitPat](N, N, N, N, X),
is br?
| is jal?
| | is jalr?
| | |
| | | shadowable
| | | | has_rs2
| | | | |
Array[(BitPat, List[BitPat])](
JAL -> List(N, Y, N, N, X),
JALR -> List(N, N, Y, N, X),
BEQ -> List(Y, N, N, N, X),
BNE -> List(Y, N, N, N, X),
BGE -> List(Y, N, N, N, X),
BGEU -> List(Y, N, N, N, X),
BLT -> List(Y, N, N, N, X),
BLTU -> List(Y, N, N, N, X),
SLLI -> List(N, N, N, Y, N),
SRLI -> List(N, N, N, Y, N),
SRAI -> List(N, N, N, Y, N),
ADDIW -> List(N, N, N, Y, N),
SLLIW -> List(N, N, N, Y, N),
SRAIW -> List(N, N, N, Y, N),
SRLIW -> List(N, N, N, Y, N),
ADDW -> List(N, N, N, Y, Y),
SUBW -> List(N, N, N, Y, Y),
SLLW -> List(N, N, N, Y, Y),
SRAW -> List(N, N, N, Y, Y),
SRLW -> List(N, N, N, Y, Y),
LUI -> List(N, N, N, Y, N),
ADDI -> List(N, N, N, Y, N),
ANDI -> List(N, N, N, Y, N),
ORI -> List(N, N, N, Y, N),
XORI -> List(N, N, N, Y, N),
SLTI -> List(N, N, N, Y, N),
SLTIU -> List(N, N, N, Y, N),
SLL -> List(N, N, N, Y, Y),
ADD -> List(N, N, N, Y, Y),
SUB -> List(N, N, N, Y, Y),
SLT -> List(N, N, N, Y, Y),
SLTU -> List(N, N, N, Y