解决流水线数据冲突
实验原理
数据相关引发流水线数据冲突的场景:产生结果的指令(“写者”)尚未将处理后的结果写回到通用寄存器堆中,而需要这个结果的指令(“读者”)已经在译码阶段了,此刻“读者”指令从通用寄存器堆中读出的值是旧值而非新值。
如何避免产生这种错误呢?下面将讨论解决数据相关引发流水线冲突的检测和两种解决办法。
流水线数据相关的检测
根据五级流水线的CPU微结构,判断或是检测数据相关的条件具体可以描述为:处于译码阶段的指令需要获取源寄存器值,如果这些源寄存器中的任何一个地址(寄存器号)与当前时刻处于执行阶段、访存阶段或是写回阶段的需要写入的目的寄存器的地址相同,则表明处于译码阶段的指令与当前时刻处于执行阶段、访存阶段或是写回阶段的指令存在数据相关。
但是在检测数据相关时还要考虑以下情况。第一,参与检测的指令到底有没有寄存器的源操作数,比如I型指令中,只有一个寄存器的源操作数,再比如U型和J型指令压根就没有寄存器的源操作数,那么在考虑这类指令的数据相关时,不需要将和寄存器无关的源操作数考虑进去。第二,指令中有寄存器的源操作数,但是寄存器号为0,那么也不会相关,因为0号寄存器的值恒为0。第三,处于执行阶段、访存阶段或是写回阶段的指令是否需要写结果寄存器,比如分支指令不需要写寄存器,其“目的寄存器号”实际是立即数的一部分。
用停顿法处理数据冲突
解决数据冲突一种直观的解决思路是:让需要这个结果的“读者”指令在取指和译码阶段一直等待,直到产生结果的“写者”指令将结果写入到通用寄存器堆中,才可以进入到流水线的下一级的执行阶段。这种方法称为停顿(stall),也称作阻塞。
因为取指令阶段NextPC的值随着PC的变化而变化,译码阶段的其他值也是根据译码阶段的指令的变化而变化,所以停顿的基本思想就是保持这两个数值保持不变,简单来说就是保持从PC寄存器取出的pc值和从译码阶段的流水线寄存器PR1取出的指令值不变。具体实现方法可以利用寄存器的使能信号,当检测到冲突时阻止PC和PR1这两个寄存器更新数据,如图 1所示stall信号。当取指令和译码阶段停顿时,其他阶段的数据仍然继续在流水线中向前传递,当相关的数据写入目的寄存器后,数据相关消失,随即撤销停顿信号,流水线恢复正常。根据相关的寄存器是处于执行阶段、访存阶段还是写回阶段,停顿的周期数分别是3、2、1。
当发生数据冲突时,从寄存器堆读出的寄存器值是错误的,如果该寄存器值传递到执行阶段,ALU的运算结果也将是错误的,最终会将错误的结果写入目的寄存器。为了避免将错误的结果写入目的寄存器,在停顿的同时需要清零译码—执行的流水线寄存器PR2(包括控制信号的流水线寄存器),如图 1所示flush信号。
用前递法解决数据冲突
在遇到数据相关的指令时可以观察到一个现象,那就是需要这个结果的指令(“读者”)在译码阶段等待的过程中,前面产生结果的指令(“写者”)其实已经在执行阶段产生了结果,只不过还没有写入到寄存器堆中。
比如这两条指令:
add x5,x0,4 (1) add x6,x5,3 (2)
当指令①处于执行阶段时,指令②处于译码阶段,且该指令要获取x5寄存器的值,此时就可以在执行阶段的ALU的结果输出处到译码阶段的寄存器堆读出结果的生成处添加一条旁路通路,把执行阶段对x5的处理结果送到译码阶段。这种方法称作前递或转发(forwarding),也称作旁路(bypassing)。同样的,按照这种思想,还可以依次添加从访存阶段、写回阶段到译码阶段的前递通路。增加前递的数据通路如图 2所示。
上面的方法可以处理运算指令写入寄存器的情况,但对于load指令还存在问题。如果在流水线上执行如下指令序列:
lw x19,8(x0) (1) addi x20,x19,3 (2)
假设此时指令①位于执行(EX)阶段,那么指令②就位于译码阶段,此时x19产生了数据相关,虽然存在执行阶段到译码阶段的前递通路,但是lw指令在执行阶段尚未读出存储器中的数据,要到访存(MEM)阶段才能读出要写入目的寄存器x19的数据。所以此时需要等待一个周期之后才能从MEM阶段前递,解决跟在lw指令后面的指令与之产生的数据冲突。
此外在硬件设计时,还需要考虑前递的优先级。下面是一个特殊例子。
add x4,x1,x1 (1) add x4,x2,x2 (2) add x4,x3,x3 (3) sub x6,x5,x4 (4)
该指令序列的①②③都写入x4寄存器,当指令④处于译码阶段,同时与指令①②③发生数据相关,指令①处于写回阶段,指令②处于访存阶段,指令③处于执行阶段,那么应该前递哪个阶段的数据呢?很显然,应该选择离它最近的执行阶段前递过来的结果,因为它是x4的最终结果。所以,硬件实现时还要考虑不同流水段的前递优先级。
实验任务
1. 前递数据通路设计
上面介绍了流水线数据冲突的检测和解决办法,并且以23条指令的数据通路为例给出了具体方案。实验任务是在前面实验自己设计的27条指令的单周期数据通路的基础上,设计支持27条指令的流水线数据通路并且能够解决数据冲突。具体要求如下。
(1)用前递法解决流水线数据冲突。
(2)对于由上一条load指令引起的数据冲突需要停顿一个周期再前递。
2. 虚拟面板设计
根据自己设计的数据通路,绘图作为虚拟面板的背景;添加必要的观察数据和信号,同时在设计代码中添加相应的观察变量。 具体步骤可参阅支持27条指令,但是实验材料有所不同,本实验所需实验材料如下所示。
📂 riscv 📂 RV-27PL1 📁 diagram 📄 RV-PL23.drawio 📄 RV-PL27_golden.jvp
由于同时有多条指令在流水线上执行,为了帮助分析运行结果,可以在虚拟面板上添加指令提示框,显示每个流水段当前执行的指令,就像前面23条指令流水线实验的虚拟面板那样。指令提示框在虚拟面板左边栏中是字母“P”表示的虚拟元件,可以拖放到虚拟面板中,然后在右边栏中设置其大小。