记分牌算法

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

在乱序执行中,CPU会将指令分为多个微操作(Micro-operations),并将这些微操作按照指令之间的依赖关系进行调度,以最大化地利用CPU的执行单元。这样可以让CPU在等待某些资源时,可以继续执行其他指令,从而提高CPU的利用率和性能。

记分牌实际上是一个信息存储单元,它负责记录指令状态、运算功能单元状态和寄存器结果状态,在ID流水段CPU依据这些状态信息检查结构冒险和数据冒险,以此判断是否发射当前指令。

一个功能单元通常表现为如下形式

有9个字段表示每一个部件的状态:

  • busy:指示该单元是否繁忙
  • Op:该单元正在进行的运算类型
  • Fi:运算结果的目标寄存器
  • FjFk:源寄存器
  • RjRk:指示源寄存器FjFk是否准备就绪但尚未读取。在读取操作数后设置为“yes”
  • QjQk:如果源寄存器FjFk没有准备就绪,那生成它们的值的部件

一个寄存器结果状态通常表现为如下形式:

如果一条活动指令以该寄存器为目标寄存器,则指出哪个功能单元将写入每个寄存器。只要没有向该寄存器写入的未完成指令,则将该字段设置为空。

乱序流水线工作步骤

为了检查指令之间的结构冒险和数据冒险,需要将ID阶段拆成发射和读取操作数两个阶段,以下四个阶段代替了标准RISCV流水线中的ID、EX和WB步骤:

  1. 发射:如果指令的一个功能单元空闲(功能部件状态的busy字段),且没有其它活动指令以同一寄存器为目标寄存器(目标寄存器状态是否为空),则记分牌向功能单元发射指令,并更新指令状态。该步骤能解决结构冒险WRW冒险。
  2. 读取操作数:如果不存在检查活动指令写入源寄存器(RjRk字段为yes),记分牌将该指令发送到功能部件(RjRk字段标记为no)。该步骤能解决RAW冒险
  3. 执行:功能单元开始执行对应操作,更新功能单元状态。当结果就绪后,通知记分牌。
  4. 写结果:当其它活动指令不需要读取当前计算结果即将写入的寄存器,写回寄存器,并清空该条指令在记分牌记录的状态,同时修改以该指令目标寄存器作为源操作数的指令的RjRk字段。该步骤能解决WAR冒险

案例演示

下文演示一个简单的记分牌算法下多条指令乱序执行的案例。

Circle 1

检查记分牌状态,功能部件状态和寄存器状态均为空,第一条指令发射。

  • 更新结构状态:该指令issue步骤在第 1 个周期执行
  • 更新功能部件状态:Interger部件开始忙碌,执行load操作,目标寄存器FiF6,源寄存器FkR2,且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操作,目标寄存器FiF2,源寄存器FkR3,且R2准备就绪,Rk标记为yes
  • 更新寄存器状态:F2寄存器即将被Integer部件的结果写入

Circle 6

当前没有活动指令将要写入源寄存器R3, 第二条LD指令进入读操作数阶段。

检查记分牌状态,Multi部件空闲,寄存器状态中F0字段为空,MULTD指令发射。

  • 更新结构状态:MULTD指令issue步骤在第 6 个周期执行
  • 更新功能部件状态:Multi1部件忙碌,执行Multi操作,目标寄存器FiF0,源寄存器FjFkF2F4,检查寄存器状态,此时源寄存器Fj没有准备就绪,有活动指令将从Integer部件写入,标记Qj
  • 更新寄存器状态:F0即将被Multi1部件写入

Circle 7

第二条LD进入执行步骤,将Rk标记为no,表示源寄存器Fk读取完成。MULTD指令的Rj为no,表示源寄存器Fj未准备就绪,无法进入读操作数步骤。

检查记分牌状态,Add部件空闲,寄存器状态中F8字段为空,SUBD指令发射。

  • 更新结构状态:SUBD指令issue步骤在第 7 个周期执行
  • 更新功能部件状态:Add部件忙碌,执行Sub操作,目标寄存器FiF8,源寄存器FjFkF6F2,检查寄存器状态,此时源寄存器Fk没有准备就绪,有活动指令将从Integer部件写入,标记Qk
  • 更新寄存器状态:F8即将被Add部件写入

Circle 8

第 8 个周期分为前后半个周期分别分析。

在前半个周期,Divide部件空闲,寄存器状态中F10为空,DIVD指令发射。

  • 更新结构状态:DIVD指令issue步骤在第 8 个周期执行
  • 更新功能部件状态:Multi1部件忙碌,执行Multi操作,目标寄存器FiF10,源寄存器FjFkF0F6,检查寄存器状态,此时源寄存器Fj没有准备就绪,有活动指令将从Integer部件写入,标记Qj
  • 更新寄存器状态:F10即将被Multi1部件写入

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

Circle 9

MULTD指令和SUBD指令的源寄存器均已准备就绪且尚未读取,两条指令都进入读操作数步骤。由于Add部件非空闲,ADDD指令不能发射。

后面的步骤都相似,有时间再慢慢补充。下面是执行完这6条指令记分牌状态:

Circle 62

参考