自己动手写CPU之第五阶段(1)——流水线数据相关问题

时间:2022-11-28 03:27:51

将陆续上传本人写的新书《自己动手写CPU》(尚未出版),今天是第15篇,我尽量每周四篇

上一章建立了原始的OpenMIPS五级流水线结构,可是仅仅实现了一条ori指令,从本章開始,将逐步完好。

本章首先讨论了流水线数据相关问题。然后改动OpenMIPS以解决该问题。并在5.3节验证了解决效果。接着对逻辑、移位操作与空指令的指令格式、使用方法、作用进行了一一说明。在5.5节通过扩展OpenMIPS实现了这些指令,最后编写測试程序,对实现效果进行了检验。

5.1 流水线数据相关问题

我们在第4章实现的五级流水线结构非常easy。假设依照“简单即美(Simple is Beautiful)的标准,那么我们的流水线是美的,可是不完美,由于现实往往是复杂的,一个简单的流水线是解决不了如此多的现实问题的,本节探讨的数据相关问题就是当中一个问题。在我们实现逻辑、移位操作等其他指令之前。必须先讨论这个问题,由于这个问题已经影响到測试程序的编写了。

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

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

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

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

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

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

结构相关、控制相关将在兴许指令分析中讨论,本节重点讨论数据相关的问题。流水线数据相关又分为三种情况:RAW、WAR、WAW。

  • RAW:Read After Write,如果指令j是在指令i后面运行的指令。RAW表示指令i将数据写入寄存器后,指令j才干从这个寄存器读取数据。

    如果指令j在指令i写入寄存器前尝试读出该寄存器的内容。将得到不对的数据。

  • WAR:Write After Read。如果指令j是在指令i后面运行的指令,WAR表示指令i读出数据后,指令j才干写这个寄存器。如果指令j在指令i读出数据前就写该寄存器,将使得指令i读出的数据不对。
  • WAW:Write After Write,如果指令j是在指令i后面运行的指令,WAW表示指令i将数据写入寄存器后,指令j才干将数据写入这个寄存器。如果指令j在指令i之前写该寄存器,将使得该寄存器的值不是最新值。

对于第4章建立的原始OpenMIPS五级流水线而言,从ori指令的实现过程能够知道,仅仅有在流水线回写阶段才会写寄存器(实际上其余指令也是一样的,在后面实现其余指令时,对这一点会更加清楚),因此不存在WAW相关。又由于仅仅能在流水线译码阶段读寄存器、回写阶段写寄存器,所以不存在WAR相关。所以OpenMIPS的流水线仅仅存在RAW相关。RAW相关有三种情况。

(1)相邻指令间存在数据相关

考虑例如以下代码。

1   ori $1,$0,0x1100        # $1 = $0 | 0x1100 = 0x1100
2 ori $2,$1,0x0020 # $2 = $1 | 0x0020 = 0x1120

第1条ori指令会写寄存器$1。随后的第2条ori指令须要读出$1的数据,可是第1条ori指令在回写阶段才会将其运算结果写入$1,而第2条ori指令在译码阶段就须要读取$1的值。此时第1条ori指令还处于运行阶段,所以得到的必定不是第1条ori指令计算得出的结果,按这个值运算,必定会出错。

如图5-1所看到的。

这样的情况能够称为相邻指令间存在数据相关。针对OpenMIPS详细情况。也能够称为流水线译码、运行阶段存在数据相关。

自己动手写CPU之第五阶段(1)——流水线数据相关问题

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGVpc2hhbmd3ZW4=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

(2)相隔1条指令的指令间存在数据相关

考虑例如以下代码。

1   ori $1,$0,0x1100        # $1 = $0 | 0x1100 = 0x1100
2 ori $3,$0,0xffff # $3 = $0 | 0xffff = 0xffff
3 ori $2,$1,0x0020 # $2 = $1 | 0x0020 = 0x1120

第1条ori指令会写寄存器$1。第3条ori指令在译码阶段须要读取寄存器$1,此时第1条ori指令还处于訪存阶段。所以得到的必定也不是正确的值。

如图5-2所看到的。这样的情况能够称为相隔1条指令的指令间存在数据相关。针对OpenMIPS详细情况。也能够称为流水线译码、訪存阶段存在数据相关。

自己动手写CPU之第五阶段(1)——流水线数据相关问题

(3)相隔2条指令的指令间存在数据相关

考虑例如以下代码。

1   ori $1,$0,0x1100        # $1 = $0 | 0x1100 = 0x1100
2 ori $3,$0,0xffff # $3 = $0 | 0xffff = 0xffff
3 ori $4,$0,0xffff # $4 = $0 | 0xffff = 0xffff
4 ori $2,$1,0x0020 # $2 = $1 | 0x0020 = 0x1120

第1条ori指令会写寄存器$1,第4条ori指令在译码阶段须要读取寄存器$1。此时第1条指令处于回写阶段,在回写阶段最后的时钟上升沿才会将运算结果写入$1,所以第4条ori指令得到的不是正确的寄存器$1的值。如图5-3所看到的。这样的情况能够称为相隔2条指令的指令间存在数据相关,针对OpenMIPS详细情况,也能够称为流水线译码、回写阶段存在数据相关。

自己动手写CPU之第五阶段(1)——流水线数据相关问题

当中相隔2条指令存在数据相关(即流水线译码、回写阶段存在数据相关)这样的情况,在第4章设计的Regfile模块中已经得到了解决,Regfile模块部分代码例如以下。

module regfile(
......
); ...... /****************************************************************
*********** 第三段:读port1的读操作 *********
*****************************************************************/ // raddr1是读地址、waddr是写地址、we是写使能、wdata是要写入的数据
always @ (*) begin ...... end else if((raddr1 == waddr) && (we == `WriteEnable)
&& (re1 == `ReadEnable)) begin
rdata1 <= wdata; ...... end /****************************************************************
*********** 第四段:读port2的读操作 *********
*****************************************************************/ // raddr2是读地址、waddr是写地址、we是写使能、wdata是要写入的数据
always @ (*) begin ...... end else if((raddr2 == waddr) && (we == `WriteEnable)
&& (re2 == `ReadEnable)) begin
rdata2 <= wdata; ...... end endmodule

在读操作中有一个推断,假设要读取的寄存器。是在下一个时钟上升沿要写入的寄存器。那么就将要写入的数据直接作为结果输出。如此就攻克了相隔2条指令存在数据相关的情况。

对于相邻指令间存在数据相关、相隔1条指令的指令间存在数据相关这两种情况。有三种解决方法。

(1)插入暂停周期:当检測到相关时,在流水线中插入一些暂停周期,如图5-4所看到的。

自己动手写CPU之第五阶段(1)——流水线数据相关问题

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGVpc2hhbmd3ZW4=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

(2)编译器调度:编译器检測到相关后,能够改变部分指令的运行顺序,如图5-5所看到的。

自己动手写CPU之第五阶段(1)——流水线数据相关问题

(3)数据前推:将计算结果从其产生处直接送到其它指令须要处或全部须要的功能单元处,避免流水线暂停。如图5-6所看到的的样例,新的$1值实际在第1条ori指令的运行阶段已经计算出来了。能够直接将该值从第1条ori指令的运行阶段送入第2条ori指令的译码阶段,从而使得第2条ori指令在译码阶段得到$1的新值。也能够直接将该值从第1条ori指令的訪存阶段送入第3条ori指令的译码阶段,从而使得第3条ori指令在译码阶段也得到$1的新值。

自己动手写CPU之第五阶段(1)——流水线数据相关问题

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGVpc2hhbmd3ZW4=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

读者须要注意,第(3)种方法有一个前提就是新的寄存器的值能够在运行阶段计算出来,假设是载入指令,那么就不满足这个前提,由于载入指令在訪存阶段才干获得终于结果。这是一种load相关,本书将在实现载入存储指令的时候考虑这样的情况,本章暂不考虑。

下一次将介绍OpenMIPS对数据相关问题的解决措施,敬请关注!