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

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

上一章建立了原始的OpenMIPS五级流水线结构,但是只实现了一条ori指令,从本章开始,将逐步完善。本章首先讨论了流水线数据相关问题,然后修改OpenMIPS以解决该问题,并在5.3节验证了解决效果。接着对逻辑、移位操作与空指令的指令格式、用法、作用进行了一一说明,在5.5节通过扩展OpenMIPS实现了这些指令,最后编写测试程序,对实现效果进行了检验。

5.1 流水线数据相关问题

我们在第4章实现的五级流水线结构很简单,如果按照“简单即美(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具体情况,也可以称为流水线译码、执行阶段存在数据相关。

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

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

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

module regfile(
	......
);

	......

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

// raddr1是读地址、waddr是写地址、we是写使能、wdata是要写入的数据
	always @ (*) begin

	  ......

	  end else if((raddr1 == waddr) && (we == `WriteEnable)
	  	            && (re1 == `ReadEnable)) begin
	    rdata1 <= wdata;

	  ......

	end

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

// 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所示。

(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对数据相关问题的解决措施,敬请关注!

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

时间: 2024-10-05 23:46:33

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

自己动手写CPU之第五阶段(4)——逻辑、移位与空指令的实现

将陆续上传本人写的新书<自己动手写CPU>(尚未出版),今天是第18篇,我尽量每周四篇 5.5 修改OpenMIPS以实现逻辑.移位操作与空指令 为了实现逻辑.移位操作与空指令(其中nop.ssnop不用特意实现,可以认为是特殊的逻辑左移指令sll),只需要修改OpenMIPS的如下两个模块. 修改译码阶段的ID模块,用以实现对上述指令的译码. 修改执行阶段的EX模块,使其按照译码结果进行运算. 5.5.1 修改译码阶段的ID模块 首先给出如下宏定义,都在文件defines.v中定义,读者可以

自己动手写CPU之第五阶段(2)——OpenMIPS对数据相关问题的解决措施

将陆续上传本人写的新书<自己动手写CPU>(尚未出版),今天是第16篇,我尽量每周四篇 5.2 OpenMIPS对数据相关问题的解决措施 OpenMIPS处理器采用数据前推的方法来解决流水线数据相关问题.通过补充完善图4-4原始的数据流图,添加部分信号使得可以完成数据前推的工作,如图5-7所示.主要是将执行阶段的结果.访存阶段的结果前推到译码阶段,参与译码阶段选择运算源操作数的过程. 图5-8给出了为实现数据前推而对OpenMIPS系统结构所做的修改.有两个方面. (1)将处于流水线执行阶段的

自己动手写CPU之第五阶段(3)——MIPS指令集中的逻辑、移位与空指令

将陆续上传本人写的新书<自己动手写CPU>(尚未出版),今天是第17篇,我尽量每周四篇 5.4 逻辑.移位操作与空指令说明 MIPS32指令集架构中定义的逻辑操作指令有8条:and.andi.or.ori.xor.xori.nor.lui,其中ori指令已经实现了,本章要实现其余7条指令. MIPS32指令集架构中定义的移位操作指令有6条:sll.sllv.sra.srav.srl.srlv. MIPS32指令集架构中定义的空指令有2条:nop.ssnop.其中ssnop是一种特殊类型的空操作

自己动手写CPU之第五阶段(5)——测试逻辑、移位与空指令的实现

将陆续上传本人写的新书<自己动手写CPU>(尚未出版),今天是第19篇,我尽量每周四篇 5.6 测试程序1--测试逻辑操作实现效果 编写如下测试程序用于检验逻辑操作指令是否实现正确,文件名命名为inst_rom.S,在本附带光盘Code\Chapter5_2\AsmTest\LogicInstTest目录下有测试程序源文件. .org 0x0 .global _start .set noat _start: lui $1,0x0101 # $1 = 0x01010000 ori $1,$1,0

自己动手写CPU之第七阶段(2)——简单算术操作指令实现过程

将陆续上传本人写的新书<自己动手写CPU>,今天是第25篇,我尽量每周四篇 亚马逊的预售地址如下,欢迎大家围观呵! http://www.amazon.cn/dp/b00mqkrlg8/ref=cm_sw_r_si_dp_5kq8tb1gyhja4 China-pub的预售地址如下: http://product.china-pub.com/3804025 7.2 简单算术操作指令实现思路 虽然简单算术操作指令的数目比较多,有15条,但实现方式都是相似的,与前几章逻辑.移位操作指令的实现方式也

自己动手写CPU之第七阶段(3)——简单算术操作指令实现过程(续)

将陆续上传本人写的新书<自己动手写CPU>,今天是第26篇,我尽量每周四篇 China-pub的预售地址如下(有目录.内容简介.前言): http://product.china-pub.com/3804025 亚马逊的预售地址如下,欢迎大家围观呵! http://www.amazon.cn/dp/b00mqkrlg8/ref=cm_sw_r_si_dp_5kq8tb1gyhja4 为了实现简单算术指令,需要修改译码阶段的ID模块.执行阶段的EX模块,上一篇博文中已经介绍了对译码阶段ID模块的

自己动手写CPU之第六阶段(2)——移动操作指令实现思路

将陆续上传本人写的新书<自己动手写CPU>(尚未出版),今天是第21篇,我尽量每周四篇 6.2 移动操作指令实现思路 6.2.1 实现思路 这6条移动操作指令可以分为两类:一类是不涉及特殊寄存器HI.LO的指令,包括movn.movz:另一类是涉及特殊寄存器HI.LO的指令,包括mfhi.mflo.mthi.mtlo.前一类很好实现,基本思路与第5章实现逻辑.移位操作指令时类似,只需要修改ID.EX模块即可.后一类涉及到特殊寄存器HI.LO,需要为OpenMIPS添加HI.LO寄存器,以及相应

自己动手写CPU之第七阶段(1)——简单算术操作指令说明

将陆续上传本人写的新书<自己动手写CPU>(尚未出版),今天是第24篇,我尽量每周四篇 本章将实现MIPS32指令集架构定义的所有算术操作指令,共有21条,按照OpenMIPS实现这些指令的方式,可以分为三类,分别介绍如下. (1)简单算术操作指令 共有15条,包括加法.减法.比较.乘法等指令,这些指令在流水线的执行阶段都只需要一个时钟周期,而且实现思路很直观,与第4章添加逻辑操作指令类似,只需修改译码阶段的ID模块.执行阶段的EX模块,即可实现. (2)乘累加.乘累减指令 共有4条:乘累加m

自己动手写CPU之第六阶段(1)——移动操作指令说明

将陆续上传本人写的新书<自己动手写CPU>(尚未出版),今天是第20篇,我尽量每周四篇 本章将实现移动操作指令,首先在6.1节介绍了MIPS32指令集架构中定义的移动操作指令的格式.作用,接着在6.2节给出移动操作指令实现思路,介绍了修改后的数据流图.新出现的数据相关问题及其解决措施,并给出了修改后的OpenMIPS系统结构图.在6.3节列出了详细的修改过程.本章最后通过一个测试程序验证移动操作指令是否实现正确. 6.1 移动操作指令说明 MIPS32指令集架构中定义的移动操作指令共有6条:m