1. 指令提交
高性能CPU是乱序(out of order)执行指令的,为了保持程序执行结果的串行性,在流水线中增加最后一级,称为提交(Commit)阶段。当指令到达提交阶段,会将指令在重排序缓存(Re-order buffer, ROB)中标记为已完成的(complete)状态,需要注意,complete状态只表示这条指令已经计算完毕,并不表示它可以离开流水线。
流水线中所有的指令都按照进入流水线的顺序在ROB中进行了记录,只有ROB中最旧的那条指令变为已完成的状态时,这条指令才允许离开流水线,并使用它的结果对处理器的状态进行更新,此时称这条指令退休(retire)了。
因此,一条指令可能要在提交阶段等待一段时间,才可以退休,ROB负责完成这个功能,它是提交阶段最重要的部件。指令到达提交阶段并不代表这条指令一定正确,由于分支预测失败(mis-prediction) 和异常(exception)等原因,一条处于已完成状态的指令可能会从流水线中被抹掉,只有在这条指令之前进入到流水线中的所有指令都已经retire了,并且这条指令处于已完成状态时,它才可以退休而离开流水线,这样使程序在处理器中执行时有串行的效果 (乱序执行,顺序提交)。
当指令没有退休时,它的状态都是推测的(speculative),只有当指令真正退休而离开流水线时,才可以将它的结果更新到处理器的状态中,这样即使有分支预测失败或者异常,也不会将错误的状态暴露给程序员。流水线的分发(Dispatch)阶段是处理器从顺序执行(in order)到乱序执行(out-of-order)的分界点,流水线的提交阶段又将处理器从乱序状态拉回到顺序状态。从处理器外部看,它总是按照程序中指定的顺序执行,任何预测技术所产生的错误,在处理器内部都会解决掉。因此,异常处理是提交阶段的一个重要任务。
对于一个N-way的超标量处理器,流水线的提交阶段每周期至少需要将N条指令退休,才能保证流水线不会被堵塞。
2. ROB介绍
ROB的结构
在提交阶段,将乱序执行的指令变回程序中指定的顺序状态,主要通过Reorder Buffer, 即ROB来实现,ROB本质上是一个FIFO。ROB中存储了指令的相关信息,例如这条指令的类型、结果、目的寄存器和异常的类型等,如下图。
Reorder Buffer
ROB的entry数决定了流水线中最多可以乱序执行的指令个数,即乱序窗口OoO(Out-of-Order) window的大小,上图中每个ROB的表项包括的内容如下。
Complete:表示一条指令是否已经执行完毕;
Areg:指令在原始程序中指定的目的寄存器,它以逻辑寄存器的形式给出;
Preg:指令的Areg经过寄存器重命名之后,对应的物理奇存器的编号;
OPreg:指令的Areg被重命名为新的Preg之前,对应的旧的Preg,当指令发生异常而进行状态恢复时,会使用到这个值;
PC:指令对应的PC值,当指令发生中断或者异常时,需要保存这条指令的PC值,以便能够重新执行程序;
Exception:如果指令发生异常,会将异常类型写到这里,当指令要退休的时候,会对这个异常进行处理;
Type:指令的类型。当指令退休的时候,不同类型的指令会有不同的动作,例如store指令要写D-Cache、分支指令要释放Checkpoint 资源等。
在分发(Dispatch)阶段,指令会按照进入流水线的顺序写到ROB中,同时ROB中对应的complete状态位会被置0,表示这些指令没有执行完毕。当某条指令执行结束,它就变为complete状态,此时会将 ROB中对应的complete状态位置为 1,这条指令的计算结果可以放在ROB 中,也可以放在物理寄存器堆(PRF)中,取决于架构实现。指令在执行过程中如果发生异常,也会将异常类型记录在ROB中,异常的处理会统一放在提交阶段。指令一旦在流水线的分发阶段占据了ROB中的一个表项,这个表项的编号(ROB ID)会一直随着这条指令在流水线中流动,这样指令在之后的任何时刻,都可以在ROB中找到自己。
一条指令一旦变为ROB中最旧的指令(上图中,使用head pointer来指示最旧的指令),并且它的complete状态位为1,并且这条指令在之前没有发生过异常,则这条指令可以顺利地离开流水线,它的结果可以对处理器的状态进行更新;如果这条指令发生过异常,那么就要启动异常的处理过程。
端口需求
对于一个4-way的超标量处理器来说,在ROB中每周期可以retire的指令个数应该是不小于4的。处理器从ROB中最旧的一些指令中(由head pointer 指定),选择那些已经变为complete状态的指令,使其从流水线中退休。例如图 10.4给出的ROB,在最旧的指令中,有三条指令变为了complete状态,那么在一个周期内就可以将这三条指令都退休。
要实现如图10.4所示的功能,需要对ROB中最旧的指令开始(由head pointer指定)连续的四个complete信号进行判断,如果某个complete信号为0,那么它后面的所有指令都不允许在本周期退休,这个功能可以用如图10.5所示的电路实现。
对于一个4-way的超标量处理器来说,ROB至少需要支持4个读端口,但是这远远不是ROB真正需要的端口个数,流水线的其他阶段还对ROB有端口需求,它和处理器所采用的架构有关系,如果处理器采用ROB进行寄存器重命名的方式,此时对ROB的端口需求是最多的,这些端口包括:
1. 在寄存器重命名阶段,需要从ROB中读取4条指令的源操作数,因此需要ROB至少支持8个读端口(假设每条指令有两个源寄存器)。
2. 在分发阶段,需要向ROB写入4条指令,因此需要ROB支持4个写端口。
3. 在写回阶段,需要向ROB写入最少4条指令,之所以使用至少4个写端口,是因为很多处理器的issue width要大于machine width,处理器会放置更多的FU来提高运算的并行度,这样导致每周期运算得到的结果要大于4个。
4. 在提交阶段,需要读4条指令,因此需要4个读端口。
所以,ROB需要支持12个读端口和最少8个写端口,这种多端口FIFO在面积和延迟方面很难进行优化,这也是采用ROB进行寄存器重命名的架构所面临的问题:ROB会成为处理器中最繁忙的部件,需要进行详细设计和优化。