5. IF-QUEUE¶
IF-QUEUE是前端异步流水线的最后一级,用于对传来的指令进行译码、分支预测,以及暂存译码后的指令序列,用于传递给后端。
IF-QUEUE采用与其它流水级不同的就绪规则: 在队列即将满时,IF-QUEUE会停止接收上一级的指令(in_ready=0),使得最后一条解码的指令流入队列后,队列恰好装满。
IF-QUEUE作为分支预测的所在点,可以取消前面的流水线操作(保证了“在分支预测完全正确的情况下,流入指令队列的指令恰好是完全正确的”), 但是同时也可以被ROB带来的跳转取消并清空。具体的清空逻辑参见IF-CTRL。
5.1. Decoder¶
针对RISC-V指令集的解码器。该解码器的框架是由一个简单的工具生成的,它将RISC-V指令翻译成更方便使用的形式(例如,附加了当前PC、取指异常类型、分支预测的结果等字段,并且将Rs、Rt、Rd设置为正确的值)。 Decoder被设计一个组合逻辑,但是它可以触发分支预测。分支预测的操作就是在这里完成的。
5.2. 对Branch指令的预测¶
Branch指令的跳转地址可以由指令本身和指令的PC简单计算出来,关键在于Branch是否被采用。我们使用一个2bit的饱和计数器预测Branch指令,并且在Branch指令被提交的时候更新这个计数器。
5.3. 对Jal/Jalr对的处理¶
Jal指令的跳转地址是已知的,但是Jalr指令的跳转地址是由寄存器的值决定的。这意味着我们不能很方便地预测Jalr的目的地址。
幸运的是,RISC-V规定,使用x1/x5寄存器作为“函数调用和返回”的Hint。这允许我们在涉及到x1/x5寄存器的Jal和Jalr指令时,使用一个“返回地址栈”进行分支预测:当Jal指令被认为是一个函数调用(rd为x1/x5)时,PC+4被压入栈中;当Jalr被认为时一个返回(rs为x1/x5)时,栈顶被从栈中挪出,作为分支预测的地址(大致如此,具体操作参考RISC-V文档)。
我们使用一个固定大小的栈来实现RAS,并且规定当栈满时,push操作会丢弃栈底的地址,使得最近的几次return能够正确,而较远的return分支预测失败。 当栈为空,或者源寄存器不是x1/x5时,我们使用“当前时刻寄存器中的值”作为猜测的跳转值。
然而,返回地址预测要求我们为经过Decoder的所有指令,都要对返回地址栈进行操作, 而在分支预测错误时,经过Decoder的所有指令并非正确的指令序列。考虑如下的情形:
- 有一个Branch指令,taken时会跳过一个目标为x1的Jalr;Branch预测结果为not taken,而实际情况为taken。
- 错误指令流中的错误的Jalr从RAS里带走了一个元素。
- 发现Branch预测失败,但是最终返回地址栈错误地弹出了一个元素,导致接下来所有的返回操作都会因为错开了一个元素而预测错误。
为了避免错误的分支预测干扰我们的返回地址预测,我们借助于将返回地址栈分两份存储来解决这个问题,记作RAS和影子RAS。
- 初始化时,RAS和影子RAS都清空。
- 所有经过Decoder的指令会按照规则更新影子RAS并且从影子RAS中取得分支预测地址,但是不会更新RAS。
- 所有被提交的指令会按照规则更新RAS。
- 当指令提交导致PC变化时(例如中断、异常、分支预测失败),将影子RAS的状态刷新到和RAS一致。
通过维护两份RAS,我们较为精确地实现了返回地址预测:只要软件遵循x1/x5作为调用/返回的hint,我们便能精确地预测返回用Jalr的目标地址。
对于一般的Jalr,我们使用朴素的预测方法,寄希望于寄存器没有发生改变。