记分牌算法
在一个顺序执行的流水线中,每条指令依次发射,且每个流水阶段只有一条指令在运行。这种顺序执行方式存在一个问题:当前一条指令执行周期数较长时(如乘除法指令是加法指令的几十倍、访存指令缓存不命中),后面指令都将停等,尽管它们和前一条指令不存在数据冒险。为了降低这种性能损失,乱序执行的处理器被设计出来。
在乱序执行中,CPU会将指令分为多个微操作(Micro-operations),并将这些微操作按照指令之间的依赖关系进行调度,以最大化地利用CPU的执行单元。这样可以让CPU在等待某些资源时,可以继续执行其他指令,从而提高CPU的利用率和性能。
记分牌实际上是一个信息存储单元,它负责记录指令状态、运算功能单元状态和寄存器结果状态,在ID流水段CPU依据这些状态信息检查结构冒险和数据冒险,以此判断是否发射当前指令。
一个功能单元通常表现为如下形式

有9个字段表示每一个部件的状态:
busy:指示该单元是否繁忙Op:该单元正在进行的运算类型Fi:运算结果的目标寄存器Fj、Fk:源寄存器Rj、Rk:指示源寄存器Fj、Fk是否准备就绪但尚未读取。在读取操作数后设置为“yes”Qj、Qk:如果源寄存器Fj、Fk没有准备就绪,那生成它们的值的部件
一个寄存器结果状态通常表现为如下形式:

如果一条活动指令以该寄存器为目标寄存器,则指出哪个功能单元将写入每个寄存器。只要没有向该寄存器写入的未完成指令,则将该字段设置为空。
乱序流水线工作步骤
为了检查指令之间的结构冒险和数据冒险,需要将ID阶段拆成发射和读取操作数两个阶段,以下四个阶段代替了标准RISCV流水线中的ID、EX和WB步骤:
- 发射:如果指令的一个功能单元空闲(功能部件状态的
busy字段),且没有其它活动指令以同一寄存器为目标寄存器(目标寄存器状态是否为空),则记分牌向功能单元发射指令,并更新指令状态。该步骤能解决结构冒险和WRW冒险。 - 读取操作数:如果不存在检查活动指令写入源寄存器(
Rj和Rk字段为yes),记分牌将该指令发送到功能部件(Rj和Rk字段标记为no)。该步骤能解决RAW冒险。 - 执行:功能单元开始执行对应操作,更新功能单元状态。当结果就绪后,通知记分牌。
- 写结果:当其它活动指令不需要读取当前计算结果即将写入的寄存器,写回寄存器,并清空该条指令在记分牌记录的状态,同时修改以该指令目标寄存器作为源操作数的指令的
Rj和Rk字段。该步骤能解决WAR冒险。
案例演示
下文演示一个简单的记分牌算法下多条指令乱序执行的案例。
Circle 1

检查记分牌状态,功能部件状态和寄存器状态均为空,第一条指令发射。
- 更新结构状态:该指令issue步骤在第 1 个周期执行
- 更新功能部件状态:Interger部件开始忙碌,执行load操作,目标寄存器
Fi为F6,源寄存器Fk为R2,且R2准备就绪,Rk标记为yes - 更新寄存器状态:
F6寄存器即将被Integer部件的结果写入
Cirlce 2

第一条LD指令进入读操作数步骤,记分牌检查Integer部件非空闲,第二条LD指令不发射。
Circle 3

第一条LD指令进入执行步骤,将Rk标记为no,表示源寄存器Fk读取完成。由第二条LD指令阻塞发射,因此后续的所有指令都无法发射。
Circle 4

第一条LD指令进入写回步骤,将存储器输出值写回寄存器堆,并且清空记分牌。
Circle 5

检查记分牌状态,Integer部件空闲,寄存器状态中F2字段为空,第二条LD指令发射。
- 更新结构状态:该指令issue步骤在第 5 个周期执行
- 更新功能部件状态:Interger部件开始忙碌,执行load操作,目标寄存器
Fi为F2,源寄存器Fk为R3,且R2准备就绪,Rk标记为yes - 更新寄存器状态:
F2寄存器即将被Integer部件的结果写入
Circle 6

当前没有活动指令将要写入源寄存器R3, 第二条LD指令进入读操作数阶段。
检查记分牌状态,Multi部件空闲,寄存器状态中F0字段为空,MULTD指令发射。
- 更新结构状态:
MULTD指令issue步骤在第 6 个周期执行 - 更新功能部件状态:Multi1部件忙碌,执行Multi操作,目标寄存器
Fi为F0,源寄存器Fj和Fk为F2和F4,检查寄存器状态,此时源寄存器Fj没有准备就绪,有活动指令将从Integer部件写入,标记Qj - 更新寄存器状态:
F0即将被Multi1部件写入
Circle 7

第二条LD进入执行步骤,将Rk标记为no,表示源寄存器Fk读取完成。MULTD指令的Rj为no,表示源寄存器Fj未准备就绪,无法进入读操作数步骤。
检查记分牌状态,Add部件空闲,寄存器状态中F8字段为空,SUBD指令发射。
- 更新结构状态:
SUBD指令issue步骤在第 7 个周期执行 - 更新功能部件状态:Add部件忙碌,执行Sub操作,目标寄存器
Fi为F8,源寄存器Fj和Fk为F6和F2,检查寄存器状态,此时源寄存器Fk没有准备就绪,有活动指令将从Integer部件写入,标记Qk - 更新寄存器状态:
F8即将被Add部件写入
Circle 8
第 8 个周期分为前后半个周期分别分析。

在前半个周期,Divide部件空闲,寄存器状态中F10为空,DIVD指令发射。
- 更新结构状态:
DIVD指令issue步骤在第 8 个周期执行 - 更新功能部件状态:Multi1部件忙碌,执行Multi操作,目标寄存器
Fi为F10,源寄存器Fj和Fk为F0和F6,检查寄存器状态,此时源寄存器Fj没有准备就绪,有活动指令将从Integer部件写入,标记Qj - 更新寄存器状态:
F10即将被Multi1部件写入

在后半个周期,第二条指令执行写回步骤,清空它在记分牌中的状态信息,此时寄存器F2已准备就绪,更改功能部件状态中MULTD指令的Qj、Rj字段,SUBD指令的Qk、Rk字段。
Circle 9

MULTD指令和SUBD指令的源寄存器均已准备就绪且尚未读取,两条指令都进入读操作数步骤。由于Add部件非空闲,ADDD指令不能发射。
后面的步骤都相似,有时间再慢慢补充。下面是执行完这6条指令记分牌状态:
Circle 62
