MIPS指令集流水线CPU设计

一、实验目的

1.综合运用Verilog进行复杂系统设计

2.深刻理解计算机系统的硬件原理

二、实验内容

1.设计一个MIPS指令集CPU。

2.CPU需要包含寄存器组,RAM模块,ALU模块,指令译码模块。

3.该CPU能运行基本的汇编指令

4.实现Cache,流水线,或其他现代CPU的高级功能

三、实验要求

1.分析每个模块的程序结构,画出其流程图

2.画出模块的电路图

3.分析电路的仿真波形,标出关键数据

4.记录设计和调试过程

四、实验过程

1.CPU设计的主要步骤

F:\QQ聊天记录and其他\2847218411\Image\Group\__XC(FL1P4OD4_}D%K9WBXF.jpg

图片摘自《计算机组成原理与设计课件》

从图中我们可以看出,CPU的设计主要分为三个步骤,其中第一个步骤就是指令系统的设计,当然,由于此次实验采用的是MIPS指令集,所以不需要设计指令系统,其次是数据通路设计和控制器设计,控制器设计是CPU设计的核心步骤,关系到整个系统的正常运行

2.数据通路的设计

本次实验采用的数据通路为流水线CPU的数据通路,相较于单周期的数据通路,流水线数据通路增加了级间寄存器。何为级间寄存器。由于流水线CPU的指令的执行被分为几个阶段,所以CPU在执行的过程中必须保存中间的数据,因此需要在原有的数据通路中添加一些级间寄存器来保存中间结果,以保证程序的正常运行。

F:\QQ聊天记录and其他\2847218411\Image\Group\P@AI6V_]CXGH32`YGG]RF)R.jpg

图片摘自《计算机组成原理与设计课件》

本次实验才用的是五级流水,所以需要的级间寄存器为IF/ID级间寄存器,ID/EX级间寄存器,EX/MA级间寄存器,MA/WB级间寄存器。值得注意的是,每个级间寄存器要保留的值是不一样的。

3.控制器的设计

前面已经提到过,CPU的设计中,控制器的设计是最核心的,关系到整个CPU的正常运行。流水线CPU控制系统设计的核心就是对于冒险的处理。

在开始设计CPU的控制器之前,我们需要对流水线的工作流程有一定的了解。根据MIPS处理的特点,将指令执行的整个过程分为五部分取指令(IF),译码(ID),执行(EX),存储器访问(MEM),回写(WE)五级。整个过程可以用下表来表示。

F:\QQ聊天记录and其他\2847218411\Image\Group\3CYGPEQO6LG]EC$TEUN[T[X.jpg

图片摘自《计算机组成原理与设计课件》

由于指令处于不同的阶段需要不同的信号,所以可以采用模块化的设计思想,为处于不同阶段的CPU设计不同的信号。以下就本次实验所设计的信号做一些说明。

各级的控制信号

IF级

从ROM中读取指令,并在下一个时钟沿到来时把指令送到ID级的指令缓冲器中。该级控制信号决定下一个指令指针的PCSource信号、阻塞流水线的PC_IFwrite信号、清空流水线的IF_flush信号.

ID级

对IF级来的指令进行译码,并产生相应的控制信号。整个CPU的控制信号基本都是在这级上产生。该级自身不需任何控制信号。

EX级

该级进行算术或逻辑操作。此外LW、SW指令所用的RAM访问地址也是在本级上实现。控制信号有ALUCode、ALUSrcA、ALUScrB和RegDst,根据这些信号确定ALU操作、选择两个ALU操作数A、B,并确定目标寄存器。

MEM级

存储器访问级。只有在执行LW、SW指令时才对存储器进行读写,对其他指令只起到一个周期的作用。该级只需存储器写操作允许信号MemWrite。

WB级

该级把指令执行的结果回写到寄存器文件中。该级设置信号MemtoReg和寄存器写操作允许信号RegWrite,其中MemtoReg决定写入寄存器的数据来自于MEM级上的缓冲值或来自于MEM级上的存储器。

数据冒险处理相关信号量

在开始数据冒险处理信号量相关设计之前,应该先对冒险有一定的了解。

冒险的概念,由于多条指令并行执行,由于各种各样的原因,导致下一条指令不能执行的现象称为冒险。冒险主要分为三类。

1.结构冒险,这种类型的冒险是由于不同的指令竞争同一个硬件产生的。可以采用部件冗余或者部件复用技术来解决。所以才本次实验的设计中,指令和数据分别采用一个存储器,这样可以使得数据和指令可以并行的存取。同时还加了一些加法器使得PC的修改不依靠ALU,从而进一步提高CPU的并行性。、

2.数据冒险.数据冒险分为两类,数据冒险的核心问题就是在生产者还没有产生数据之前,消费者就要消费数据了,从而导致流水线出现问题。数据冒险主要分为两类,一类是数据相关,另一类是数据冒险。下面一一介绍。

1) 数据相关

流水线内部其中任何一条指令要用到任何其他指令的计算结果时,将导致数据冒险。

2) 数据冒险

此类冒险发生在当定向的目标阶段在时序上早于定向的源阶段。通过举例子进一步说明上述冒险。

数据相关又分为三类,一阶,二阶,三阶数据冒险。

i)一阶数据冒险:

从图中可以看出,第一条指令在第五个周期才将结果写回寄存器,而第二条指令在其第二个周期,也就是总的地四个周期就需要该寄存器的值,显然将得到错误的未更新的值。这里采用的方法是,转发的方式,其实寄存器的值已经在第三个周期产生了,所以可以将结果转发到第二条指令的执行阶段。从而避免了第二条指令读取错误的值

2)二阶数据冒险:

第三条指令在第三阶段需要使用第一条指令产生的结果时,此时第一条指令的结果还没有写回到寄存器中,同样可以通过转发将结果转发到第三条指令的第二阶段。

以上冒险可以通过以下图片得到很好的解释:

摘自《自制CPU入门》

3) 三阶数据冒险

从图中可以看出,第四条指令的两个源操作数都有冒险,但是每个操作数的冒险的情况不一样,其中Rt的冒险是前面介绍过的一阶冒险,而Rs的冒险是三阶冒险,此时第一条指令已经处于(Write Back)写回阶段了,我们依然考虑使用转发来解决这个冒险,不过这个问题已经不能单纯的使用转发机制来完成了,还需要寄存器组的配合,需要寄存器组具有Read After Write的特性,同时还要配合转发部件的工作。

以上三种冒险都可以通过转发来完成,但是有一种冒险除外,那就是下面这种情况。

此时单纯的转发已经不能解决问题了,因为这个传递的方向和时许方向相反,传递的方向只能是向前的。所以我们需要采用流水线停顿的方式来解决此冒险。冒险成立的条件是:
1. 上一条指令是LW指令,即MwmRead_Ex = 1;

2. 在Ex级的LW指令与在ID级的指令读写的是同一个寄存器,即RegWriteAddr_ex=RsAddr_id 或 RegWriteAddr_ex=RtAddr_id

下面结合图片作一些说明:

从图中可以看出,如果采用转发的话,转发方向为红色箭头标出部分,与时序方向相反,是不正确的。因此我们采用流水线停顿的方法,什么是流水线停顿就是使流水线停止工作一个时钟周期。

图片摘自https://www.cnblogs.com/lfri/p/10053598.html

从图中可以看出,通过添加气泡(bubble)使得此类数据冒险转化为二阶数据冒险,这样就可以通过转发方式来处理此类冒险。

所以为了解决数据冒险,我们需要新增的控制信号有转发相关的控制信号和停顿相关的控制信号。

本次实验中关于处理转发相关的代码为Forwarding.v文件,关于Forwarding.v的设计可以参见下表:

ForwardA[0]=RegWrite_wb&&(RegWriteAddr_wb!=0)

&&(RegWriteAddr_mem!=RsAddr_ex)

&&(RegWriteAddr_wb==RsAddr_ex);

ForwardA[1]=RegWrite_mem&&(RegWriteAddr_mem!=0)

&&(RegWriteAddr_mem==RsAddr_ex);

ForwardB[0]=RegWrite_wb&&(RegWriteAddr_wb!=0)

&&(RegWriteAddr_mem!=RtAddr_ex)

&&(RegWriteAddr_wb==RtAddr_ex);

ForwardB[1]=RegWrite_mem&&(RegWriteAddr_mem!=0)

&&(RegWriteAddr_mem==RtAddr_ex);

同时为了处理三阶数据冒险,我们还需要对寄存器组做一些特别的设计。

从图中我们可以看出,通过转发检测电路产生的信号,寄存器组在写的同时通过多路选择器输出了相关寄存器的值。

同时为了完成流水线停顿相关的控制,我们还需要增加一个控制部件就是关于数据冒险的检测控制单元,在本次实验中相关控制的单元的部件为HazardDetector.v,其输出的两个信号为PC_IFWrite和Stall,其中Stall信号将ID/EX级寄存器全部清零,这些信号传递到流水线后面的各级,由于控制信号均为0,所以不会对任何寄存器和存储器有读写操作,高电平有效。PC_IFWriter信号禁止PC寄存器和IF/ID级寄存器接受新的数据,低电平有效。

Stall=((RegWriteAddr_ex==RsAddr_id)||(RegWriteAddr_ex==RtAddr_id))&&MemRead_ex

PC_IFWriter = ~Stall

3.控制冒险(分支冒险)

流水线每个时钟周期都得取指令才能维持运行,但分支指令必须等到MEM级才能确定是否执行分支。这种为了确定预取正确的指令而导致的延迟叫做控制冒险或分支冒险。

一种比较普遍的提高分支阻塞速度的方法是假设分支不发生,并继续执行顺序的指令流。如果分支发生的话,就丢弃已经预取并译码的指令,指令的执行沿着分支目标继续。由于分支指令直到MEM级才能确定下一条指令的PC,这就意味着为了丢弃指令必须将流水线中的IF、ID和EX级的指令都清除掉(flush)。这种优化方法的代价较大,效率较低。

如果我们能在流水线中提前分支指令的执行过程,那么就能减少需要清除的指令数。这是一种提高分支效率的方法,降低了执行分支的代价。

因此我们采用提前分支指令的方法解决分支冒险。

提前分支指令需要提前完成两个操作:

1) 计算分支目的地址:

由于已经有了PC值和IF/ID流水线寄存器中的指令值,所以可以很方便地将EX级的分支地址计算电路移到ID级。我们针对所有指令都执行分支地址的计算过程,但只有在需要它的时候才会用到。

2)判断分支的跳转条件:

我们将用于判断分支指令成立的Zero信号检测电路(Z test )从ALU中独立出来,并将它从EX级提前至ID级。具体的设计将在ID级设计中介绍。

在提前完成以上两个操作之外,我们还需丢弃IF级的指令。具体做法是:加入一个控制信号IF_flush,做为IF/ID流水线寄存器的清零信号。当分支冒险成立,即Z=1,则IF_flush=1,否则IF_flush=0,故IF_flush = Z。

考虑到本系统还要实现的无条件跳转指令:J和JR,在执行这两个指令时也必须要对IF/ID流水线寄存器进行清空,因此, IF_flush的表达式应表示为:

IF_flush = Z || J || JR。

通过以上设计步骤已经基本解决的冒险相关的问题。这也使流水线CPU设计的核心问题,其他问题同多周期,单周期CPU设计的区别不是很大,这里不再赘述。

五、实验结果

本次实验采用的环境为Icarus Verilog+gtkwave+Vscode+Quartus ii

其中Icarus用于编译.v文件,gtkwave用于查看波形,Quartus ii 用于生成电路框图。

以下给出本次实验的调试过程。

本次实验采用的测试指令为以下几条指令:

1) 一阶数据相关:

2) 二阶数据相关:

3) 三阶数据相关

关于图中的一些解释,由于指令4既含有三阶数据相关又有一阶数据相关。三阶数据相关的信号使RsSel信号,将指令1中处于最后一个级间寄存器的输出值转发到最后一条指令第二阶段,同时,由于还含有一个一阶数据相关,所以又出现了ForwardB信号的变化。

4) 数据冒险

图片见下图,从图中我们可以看出,在lW指令的第二阶段产生了Stall信号,使得PC的值保持不变,即流水线停顿了一个时钟周期,往流水线中加入了气泡,同时下一条指令的指令推迟了一个时钟周期,这样就使得该数据冒险转变为了二阶数据相关。可以看到,当第二条指令指令到第二阶段的时候,Forward的信号发生了变化,此时就是处理二阶数据相关产生的信号。

5) 控制冒险相关

此时执行的指令为BEQ指令,当执行此指令的是时候,为了防止执行一些不必要的执行,通过设置独立的ALU来将BEQ的计算提前到ID级,这样就可以尽量避免读错指令带来的损失,提高程序运行效率。IF_Flush是为了清空IF/ID寄存器。

从结果输出来看,完成了跳转。

至此我们解决了所有的冒险情况,CPU可以正常的运行了。

最终的结果输出也符合预期:

整体的波形图:

六、电路框图和代码:

1. 数据通路图

2.代码

 

 

One thought on “MIPS指令集流水线CPU设计

Leave a Reply

发表评论

邮箱地址不会被公开。 必填项已用*标注

本站所有文章均为原创,若需转载,请注明出处©twn29004 | 陕ICP备 20000896 网站备案号

总访问量:9605422    今日访问量:492    您是今天第:492 个访问者