# 记分牌算法

## 一、动态调度思想

### 1、简单流水线技术

（1）指令按序发射（issue），按序执行，按序结束

（2）一旦前面的指令停止，**后面指令不能提前执行**

### 2、动态调度

#### 2.1、动态调度条件

按序发射多条指令，只要满足以下两个条件，就可以不按顺序执行，不按顺序完成。、

（1）无结构冲突

（2）操作数已经准备好

#### 2.2、流水线改造

 流水线中常常有一些被称为“相关”（冒险）的情况发生，它使得指令序列中下一条指令无法依照设计的时钟周期运行，这些“相关”会减少流水线的性能。

流水线中的相关分为三种类型。

（1）结构相关：指的是在指令运行的过程中，因为硬件资源满足不了指令运行的要求，发生硬件资源冲突而产生的相关。

比如：指令和数据都共享一个存储器。在某个时钟周期，流水线既要完毕某条指令对存储器中数据的访问操作，又要完成的取指令操作。这样就会发生存储器訪问冲突。产生结构相关。

（2）数据相关：指在流水线中运行的几条指令中。一条指令依赖于前面指令的运行结果。

（3）控制相关：指流水线中的分支指令或者其它须要改写PC的指令造成的相关。


为了实现动态调度，流水线必须具备以下功能：

##### 2.2.1、允许按序取多条指令、按序发射多条指令

取指（IF）流水级：允许按顺序取多条指令进入队列（queue），然后从队列取出，进入译码（ID）流水级

##### 2.2.2、能检查并消除冒险（hazards）

将译码（ID）流水级分为独立的两级： *发射（issue）级*  和   *读操作数（read operand）级*

（1）发射级：完成指令译码，并检查是否存在结构冒险（即：在这一级解决 *结构冲突* 的问题）

（2）读操作数级：等待无数据冒险（RAW）之后，读出操作数（即：在这一级解决 *数据冲突* 的问题）

###### 2.2.2.1、知识补充-数据冒险

数据冒险：指在流水线中运行的几条指令中。一条指令依赖于前面指令的运行结果。

这里介绍一下数据冒险的3种类型：

①、RAW： Read After Write，读后写冒险 ，如果指令j是在指令i后面运行的指令。RAW表示：指令i将数据写入寄存器后，指令j才允许从这个寄存器读取数据。

如果指令j在指令i写入寄存器前尝试读出该寄存器的内容。将得到不对的数据。
```
例如：
（指令i）DIV F0, F2, F6
（指令j）ADD F6, F0, F8

我们本来想将除法的结果与某个数相加，但是如果顺序执行，5级流水线的条件下：
执行加法指令时，寄存器F6中存放的不是除法的结果，而是某个未知量
```

②、WAR：Write After Read，如果指令j是在指令i后面运行的指令，WAR表示：指令i读出数据后，指令j才允许写这个寄存器。

如果指令j在指令i读出数据前就写该寄存器，将使得指令i读出的数据不对。
```
例如：
（指令i）SUB F8, F10, F14
（指令j）ADD F6, F0, F8

在乱序执行的情况下，ADD指令可能先于SUB指令执行：
此时，SUB还没有读出F8中的数据，ADD就写回了，覆盖掉了原始数据
```

③、WAW：Write After Write，如果指令j是在指令i后面运行的指令，WAW表示：指令i将数据写入寄存器后，指令j才将数据写入这个寄存器。

如果指令j在指令i之前写该寄存器，将使得该寄存器的值不是最新值。
```
例如：
（指令i）SUB F8, F10, F14
...........（其他指令）
（指令j）ADD F6, F0, F14
...........


在乱序执行的情况下，ADD指令可能先于SUB指令执行：
此时，ADD先将运算结果写回到F14，后来运行SUB指令时再次写入F14，导致ADD的运行结果被覆盖
```


对于简单的五级流水线而言，仅仅有在流水线回写阶段才会写寄存器（实际上其余指令也是一样的，在后面实现其余指令时，对这一点会更加清楚），因此不存在WAW相关。又由于仅仅能在流水线译码阶段读寄存器、回写阶段写寄存器，所以不存在WAR相关。所以简单流水线仅仅存在RAW相关。

然而，经过改造之后的流水线，不再按序发射、执行，则可能出现：WAW 和 WAR
```
例：
DIV F0, F2, F4
ADD F6, F0, F8
SUB F8, F10, F14
MUL F6, F10, F8

在这个例子中：
（1）ADD与SUB之间存在反相关，若允许乱序执行，可能出现：WAR
（2）ADD与MUL之间存在输出相关，若允许乱序执行，可能出现：WAW
```

###### 2.2.2.2、知识补充-结构冒险

结构冒险：指的是在指令运行的过程中，因为硬件资源满足不了指令运行的要求，发生硬件资源冲突而产生的相关。

例如：指令和数据都共享一个存储器。在某个时钟周期，流水线既要完成某条指令对存储器中数据的访问操作，又要完成取指令操作。这样就会发生存储器訪问冲突。产生结构相关。

为了防止上述情况的发生，一般而言不会将指令与数据放在一起。

需要使用的硬件资源：

（1）存储器

（2）寄存器

（3）ALU

（4）指令存储器

###### 2.2.2.3、知识补充-控制冒险

控制冒险：指流水线中的分支指令或者其它须要改写PC的指令造成的相关。


##### 2.2.3、具备多重功能单元或流水化的功能单元

为了满足不按顺序执行和不按顺序结束，在执行（EX）流水级必须准备好多重功能单元，比如：多个加法器、乘法器、除法器等。

或者将这些功能单元也设计成流水线结构，允许多条指令重叠执行，最终达到每一流水周期，完成一条指令的目的

## 二、记分牌

### 1、简介

记分牌（scoreboard）是一种*集中控制部件*，其功能是控制数据寄存器与处理部件之间的**数据传送**。

在记分牌中保存有与各个处理部件相联系的寄存器中的数据装载情况。

当一个处理部件所要求的的数据都已经就绪（全部装载好了），记分牌允许处理部件开始执行；当执行完成后，处理部件会通知记分牌释放相关资源。

在记分牌中记录了数据寄存器和多个处理部件状态的变化情况，通过它来检测和消除或减少数据相关性，加快程序执行速度。

当一条指令暂停执行时，如果其他后继指令与暂停指令及已发射的指令无任何相关，则仍然可以发射，执行。

**发射是顺序的，而执行是乱序的**

### 2、记分牌的功能

#### 2.1、指令发射与执行

- 每一条指令都要经过记分牌（将其记录在“指令状态表”中）

- 记录数据相关性，将源操作数与目的操作数记录在“功能单元状态表”中


#### 2.2、冲突检测与消除

- 决定指令何时可以读出操作数（检测是否有RAW冲突）

- 决定指令何时可以开始执行（操作数都就绪，立即开始执行）

- 若某条指令不能立即执行，则由记分牌监控硬件的每一变化，并决定何时才能开始执行（冲突消除）

- 控制指令何时可以将结果写入**目的寄存器**（即：检测是否有WAR冲突0）


#### 2.3、记分牌在流水线中的作用

考虑简单的5级流水线：
（1）取指IF    （2）译码ID    （3）执行EX    （4）访存MEM    （5）写回WB

我们现在只考虑：**运算指令**，运算指令没有MEM节拍，并将译码ID改为：发射Issue和读操作数Read Operands两拍：

（1）取指IF    （2）发射Issue   （3）读操作数Read Operands   （4）执行EX    （5）写回WB


注： 第四个节拍：访存MEM，在MIPS中，对于存储器的访问，只能由load/store这两种指令完成，所以除了这两种指令之外的指令都没有访存MEM节拍

记分牌起作用是在把指令从取指队列中取出，送入Issue拍开始的，根据流水线级来分析记分牌的作用：


##### 2.3.1、发射Issue

Issue阶段： 指令译码 并 检测结构冒险(ID1) ，按照指令顺序发射

记分牌提供“指令状态表”，检测：*结构冒险*、*WAW冲突*。

为什么检查WAW冲突？

因为指令是乱序执行、乱序结束的。

（1）若某条指令所需的功能单元为空（即：没有结构冲突），且无其他指令用到同一**目的寄存器**（即：不存在WAW冲突），则记分牌将这条指令发射（issue）到功能单元，并更新其内部的数据结构。

（2）若存在结构冲突或WAW冲突，**则停顿后续指令的发射，直到冲突消失为止。**


##### 2.3.2、读操作数Read Operands

Read Operands阶段： 查看功能部件状态表，不存在数据冒险时，读取操作数。

记分牌监控源操作数是否就绪，检测：RAW冲突。

（1）若正在运行的指令不对当前指令的源操作数寄存器进行写操作（检测不存在RAW），或者一个正在工作的功能部件已经完成了对该寄存器的写操作（此时写完了，不存在RAW冲突），则该操作数有效，当操作数有效时，记分牌控制功能部件读取操作数，准备执行。

（2）若检测到了RAW冲突，则停顿该指令，但是在动态调度的情况下，有多条指令并行操作，因此可能存在其他的指令满足执行条件，则执行其他满足条件的指令，从而消除了停顿带来的损失


##### 2.3.3、执行EX

功能单元开始对操作数执行操作。当得到“结果”后，**功能单元通知记分牌该操作已执行完毕。**

##### 2.3.4、写回WB

当记分牌得知功能单元已经结束执行流水线时，记分牌检测：WAR冲突。

查看记分牌中，之前的指令里面，有没有要读该寄存器值得指令，有则停下，先不要写回，等数据读过了，才能写。



### 3、记分牌的数据结构

#### 3.1、指令状态表（instruction status）
指示每一条指令处于哪一级流水线

#### 3.2、寄存器结果状态表（register result status）
指示哪一功能部件将把结果写入到哪一寄存器

#### 3.3、功能部件状态表（functional unit status）
指示部件所处状态，每一个功能部件都有9个状态：


![](_v_images/_1574150696_12473.png)


### 4、例子
代码：
```
L.D    F6, 34(R2)
L.D    F2, 45(R3)
# 假定EX阶段浮点功能部件所需的延迟：
MULT.D    F0, F2, F4    # 乘法运算需要：10个时钟周期
SUB.D     F8, F6, F2    # 加法/减法运算需要：2个时钟周期
DIV.D     F10, F0, F6   # 除法运算需要：40个时钟周期
ADD.D     F6, F8, F2
```
下面分析这个例子，来看看记分牌的工作方式：

（1）ADD.D指令，因为记分牌有空而列入，但是加法运算器（因为只需要对加法的操作数进行反相，就可以直接实现减法运算）被SUB.D占用了，ADD.D指令出现结构冒险，因此处于停顿状态，没有进入issue状态。

（2）MULT.D、SUB.D、DIV.D 均进入issue状态，但是由于源操作数未就绪，而不能进入读操作数阶段。

- MULT.D：等待F2（RAW）
- SUB.D：等待F2（RAW）
- DIV.D ：等待F0（RAW）

![](_v_images/_1574151043_12118.png)

这里的“否”有两个意思：

①、源操作数没有准备好

②、源操作数已经读出来了

至于是哪一个意思，要结合$Q_j$与$R_j$（$Q_k$与$R_k$）来看，如果是第一个意思，那么$Q_j$（$Q_k$）会指出去哪找源操作数。

“是”表示：已经就绪，但是没有读取出来


寄存器结果状态表：其实就是指示哪些寄存器将要被写入。


（3）MULT.D指令到达写回结果状态时，记分牌的状态如下：

![](_v_images/_1574151886_30224.png)


（4）DIV.D指令到达写回结果状态时，记分牌的状态如下：

![](_v_images/_1574151939_24096.png)





#### 注：在指令的写回阶段需要修改记分牌

最后修改记分牌：

（1） 修改指令状态表

（2）修改功能部件状态表

（3）修改寄存器结果状态表

 ![](_v_images/_1574323845_28383.png)









### 5、记分牌算法的性能限制

记分牌动态调度算法利用程序中存在的ILP，通过**顺序发射，乱序执行**，达到减少RAW造成的停顿周期，然而其性能，即：消除停顿stall的能力受以下因素影响

（1）指令级并行性不高（需要进一步研究，开发更多的ILP）

（2）记分牌表项较少（可通过增加硬件来解决）

（3）功能部件较少，容易产生结构相关，特别是Load/Store指令，也是使用IU部件完成的。

（4）存在WAR与WAW（虽然减少了RAW，但是多了这俩）

**（5）记分牌方法消除的RAW，是由于乱序执行产生的。实际出现RAW时，也会产生停顿。**




