记分牌算法
在一个顺序执行的流水线中,每条指令依次发射,且每个流水阶段只有一条指令在运行。这种顺序执行方式存在一个问题:当前一条指令执行周期数较长时(如乘除法指令是加法指令的几十倍、访存指令缓存不命中),后面指令都将停等,尽管它们和前一条指令不存在数据冒险。为了降低这种性能损失,乱序执行的处理器被设计出来。
在乱序执行中,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