MIPS

流水线数据相关问题

      流水线中经常有一些被称为“相关”的情况发生,它使得指令序列中下一条指令无法按照设计的时钟周期执行,这些“相关”会降低流水线的性能。流水线中的相关分为三种类型。

      (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. 1 ori $1,$0,0x1100 # $1 = $0 | 0x1100 = 0x1100

  2. 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具体情况,也可以称为流水线译码、执行阶段存在数据相关。

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

      考虑如下代码。

 
  1. 1 ori $1,$0,0x1100 # $1 = $0 | 0x1100 = 0x1100

  2. 2 ori $3,$0,0xffff # $3 = $0 | 0xffff = 0xffff

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

      第1条ori指令会写寄存器$1,第3条ori指令在译码阶段需要读取寄存器$1,此时第1条ori指令还处于访存阶段,所以得到的必然也不是正确的值。如图5-2所示。这种情况可以称为相隔1条指令的指令间存在数据相关,针对OpenMIPS具体情况,也可以称为流水线译码、访存阶段存在数据相关。

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

      考虑如下代码。

 
  1. 1 ori $1,$0,0x1100 # $1 = $0 | 0x1100 = 0x1100

  2. 2 ori $3,$0,0xffff # $3 = $0 | 0xffff = 0xffff

  3. 3 ori $4,$0,0xffff # $4 = $0 | 0xffff = 0xffff

  4. 4 ori $2,$1,0x0020 # $2 = $1 | 0x0020 = 0x1120

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

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

 
  1. module regfile(

  2. ......

  3. );

  4.  
  5. ......

  6.  
  7. /****************************************************************

  8. *********** 第三段:读端口1的读操作 *********

  9. *****************************************************************/

  10.  
  11. // raddr1是读地址、waddr是写地址、we是写使能、wdata是要写入的数据

  12. always @ (*) begin

  13.  
  14. ......

  15.  
  16. end else if((raddr1 == waddr) && (we == `WriteEnable)

  17. && (re1 == `ReadEnable)) begin

  18. rdata1 <= wdata;

  19.  
  20. ......

  21.  
  22. end

  23.  
  24. /****************************************************************

  25. *********** 第四段:读端口2的读操作 *********

  26. *****************************************************************/

  27.  
  28. // raddr2是读地址、waddr是写地址、we是写使能、wdata是要写入的数据

  29. always @ (*) begin

  30.  
  31. ......

  32.  
  33. end else if((raddr2 == waddr) && (we == `WriteEnable)

  34. && (re2 == `ReadEnable)) begin

  35. rdata2 <= wdata;

  36.  
  37. ......

  38.  
  39. end

  40.  
  41. endmodule

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

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

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

      (2)编译器调度:编译器检测到相关后,可以改变部分指令的执行顺序,如图5-5所示。

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

      读者需要注意,第(3)种方法有一个前提就是新的寄存器的值可以在执行阶段计算出来,如果是加载指令,那么就不满足这个前提,因为加载指令在访存阶段才能获得最终结果,这是一种load相关,本书将在实现加载存储指令的时候考虑这种情况,本章暂不考虑。

 OpenMIPS对数据相关问题的解决措施

      OpenMIPS处理器采用数据前推的方法来解决流水线数据相关问题。通过补充完善图4-4原始的数据流图,添加部分信号使得可以完成数据前推的工作,如图5-7所示。主要是将执行阶段的结果、访存阶段的结果前推到译码阶段,参与译码阶段选择运算源操作数的过程。

      图5-8给出了为实现数据前推而对OpenMIPS系统结构所做的修改。有两个方面。

      (1)将处于流水线执行阶段的指令的运算结果,包括:是否要写目的寄存器wreg_o、要写的目的寄存器地址wd_o、要写入目的寄存器的数据wdata_o等信息送到译码阶段,如图5-8中虚线所示。

      (2)将处于流水线访存阶段的指令的运算结果,包括:是否要写目的寄存器wreg_o、要写的目的寄存器地址wd_o、要写入目的寄存器的数据wdata_o等信息送到译码阶段。

 

 流水线暂停机制设计

      因为OpenMIPS设计乘累加、乘累减、除法指令在流水线执行阶段占用多个时钟周期,因此需要暂停流水线以等待这些多周期指令执行完毕,一种直观的实现方法是:要暂停流水线,只需保持取指令地址PC的值不变,同时保持流水线各个阶段的寄存器(也就是IF/ID、ID/EX、EX/MEM、MEM/WB模块的输出)不变。

      OpenMIPS采用的是一种改进的方法:假如位于流水线第n阶段的指令需要多个时钟周期,进而请求流水线暂停,那么需保持取指令地址PC的值不变,同时保持流水线第n阶段、第n阶段之前的各个阶段的寄存器不变,而第n阶段后面的指令继续运行。比如:流水线执行阶段的指令请求流水线暂停,那么保持PC不变,同时保持取指、译码、执行阶段的寄存器不变,但是可以允许访存、回写阶段的指令继续运行。

      为此,设计添加CTRL模块,其作用是接收各个阶段传递过来的流水线暂停请求信号,从而控制流水线各个阶段的运行。

      为了实现流水线暂停机制,对系统结构做如图7-10所示的修改。

      CTRL模块的输入来自ID、EX模块的请求暂停信号stallreq,对于OpenMIPS教学版而言,只有译码、执行阶段可能会有暂停请求,取指、访存阶段都没有暂停请求,因为指令读取、数据存储器的读写操作都可以在一个时钟周期完成。 (执行阶段的暂停是因为累乘加和除法,译码阶段的暂停是因为加载指令)

      CTRL模块对暂停请求信号进行判断,然后输出流水线暂停信号stall。从图7-10中可知,stall输出到PC、IF/ID、ID/EX、EX/MEM、MEM/WB等模块,从而控制PC的值,以及流水线各个阶段的寄存器。

7.5.2 流水线暂停机制实现

      1、CTRL模块的实现

      CTRL模块的接口如图7-10中所示,各接口作用如表7-1所示。

      读者需要注意:输出信号stall是一个宽度为6的信号,其含义如下。

  •  stall[0]表示取指地址PC是否保持不变,为1表示保持不变
  •  stall[1]表示流水线取指阶段是否暂停,为1表示暂停
  •  stall[2]表示流水线译码阶段是否暂停,为1表示暂停
  •  stall[3]表示流水线执行阶段是否暂停,为1表示暂停
  •  stall[4]表示流水线访存阶段是否暂停,为1表示暂停
  •  stall[5]表示流水线回写阶段是否暂停,为1表示暂停

load相关问题(加载相关)

出现加载相关问题,由于加载是再访存阶段实现的,即使使用数据前推的方式,也解决不了问题,这时候,beq指令依旧再执行阶段了,已经进行了比较判断(译码阶段),数据需要再访存的时候才有,但访存的时候下一条指令已经到了执行阶段。

所以解决方法:在译码阶段检查当前指令与前一个指令是否有LOAD相关的问题,如果有,就让流水线的译码,取指暂停一个周期,然后再将访存阶段的数据前推到译码阶段,这样就解决了load相关(相当于要把访存阶段的数据往前推两个阶段,一个暂停加一个前推,刚好往前走两步)

 延迟槽

引入分支延迟槽的目的主要是为了提高流水线的效率。

流水线中,分支指令执行时因为确定下一条指令的目标地址(紧随其后 or 跳转目标处?)一般要到第 2 级以后,在目标确定前流水线的取指级是不能工作的,即整个流水线就“浪费”(阻塞)了一个时间片,为了利用这个时间片,在体系结构的层面上规定跳转指令后 面的一个时间片为分支延迟槽(branch delay slot)。位于分支延迟槽中的指令总是被执行,与分支发生与否没有关系。这样就有效利用了一个时间片,消除了流水线的一个“气泡”。

Q1:为什么跳转指令或者分支指令之后的两个指令无效,

答:猜测是因为跳转指令执行后,希望继续执行的是新位置之后的指令,但是由于跳转指令或者分支指令是在执行阶段才有效,取指和译码阶段依旧是原来位置递增的两个指令,所以是两个无效的指令。

Q2:解释一下延迟槽,

答:跳转指令之后的一个指令就是延迟槽指令,规定延迟槽指令一定被执行,因为跳转指令是在执行阶段才有效,所以会有连个时钟周期的浪费(取指和译码),通过设置延迟槽,可以避免浪费一个时钟周期(译码),又通过将转移判断提前到译码阶段,就可以再避免一个周期的浪费(取指)

Q3:分支指令的返回地址

答:如果没出现异常,返回地址是转移指令之后的第二条指令的地址(第一条是延迟槽指令,已经被执行了,),

 Q4:为什么延迟槽异常返回地址是上一条指令的地址


A4:
简单说,一般CPU的分支跳转指令流是:分支跳转指令->目标跳转地址的指令。
但MIPS的分支跳转指令流是:分支跳转指令 -> 延时槽指令 -> 目标跳转地址的指令,在中间操作插入了延时槽指令。
如果PC在延时槽地址中断后,中断返回时返回延时槽指令地址的话,重新执行的指令流为:延时槽指令 -> (延时槽指令地址 + 4)地址的指令,没有跳转了!
这样完全不是原来被打断的指令流,为了恢复原来的指令流需要将延时槽前面的跳转指令重新装入流水线。
所以在延时槽中断后返回的地址是前面跳转指令的地址。

 

猜你喜欢

转载自blog.csdn.net/fengxiaocheng/article/details/82015454
今日推荐