在本次设计的流水线中,存在一些冲突会使得电路不会向着我们希望的步骤运行。这些冲突分为两种:数据冲突和结构冲突。
1.数据冲突
我把数据冲突分为三个小类,存储单元的读写冲突、普通RAW(Read After Write)冲突、需要暂停流水线的RAW冲突。
1.1
对于其中第一小类冲突,可以不通过额外的冲突检测单元,通过对模块设计在下降沿写入数据,在上升沿读入数据即可。
1.2
对于第二种冲突,存在于下一条指令需要用到上一条指令结果的时候,具体分为两种情况,如下图所示:
其中第二条与第三条分别需要用到第一条指令的结果,并且在Decode阶段结果并没有存入寄存器组,因此取出的数有误。针对这种情况,可以利用forward方法(又叫bypass),即将第一条指令的Memory寄存器与WriteBack寄存器结果导入Execute阶段,供后续的二三条指令选择。如果出现第一条指令的rd地址与后续两条指令的源寄存器地址相同,则选择对应的数据,将数据通路更改如下,黑色连接线为实现forward所加的电路:
该部分的逻辑如下,以rs为例,rt的判断同理:
if ((rsE != 0) AND (rsE == WriteRegM) AND RegWriteM) then ForwardAE = 10 else if ((rsE != 0) AND (rsE == WriteRegW) AND RegWriteW) then ForwardAE = 01 else ForwardAE = 00
1.3
上述方法可以解决大部分RAW冲突,但对于lw指令而言,取出的数据要在第五阶段才可以给下一条指令使用,对于lw后一条指令,这一条指令在Execute阶段时lw才进行到Memory阶段,因此lw读到的数据无法forward到下一条指令中,针对这种情况,可以暂停流水线一个周期,等待lw取到的数据进入Writeback阶段再forward到下一条指令的Execute阶段。
对流水线暂停,我们只需要对F,D,E三个寄存器进行操作,分别让F,D两个寄存器值保持不变,并清空E中的值。具体逻辑如下:
lwstall = ((rsD== rtE) OR (rtD== rtE)) AND MemtoRegE
StallF = StallD = FlushE = lwstall
2.控制冲突
控制冲突发生在beq、bne等需要跳转的指令中,对于b指令而言,在不对数据通路进行修改的情况下,需要两个周期去判断是否需要跳转;对于j指令而言,需要一个周期去判断。目前主流的处理器都大量使用分支预测(branch prediction)技术,以预测到是否跳转,提高效率。在这里我只使用了简单的暂停流水线方法,同时将原本在Execute阶段判断两个数据是否相等(bne和beq)提前到Decode阶段,这样遇到b跳转指令时只需要暂停一个周期。
解决了控制冲突后,这一改变又引入了新的RAW冲突。与1中不同的是,这一冲突是下一指令在Decode阶段没有更新数据所导致的,因此相比于之前的forward有所区别。1.若所需要的源操作数在Writeback阶段不需要forward,2.所需要的源操作数在Memory阶段与上述forward相同,3.所需要的源操作数在Execute阶段或是在lw指令中的Memory阶段则需要暂停一个周期(若lw在Execute根据这个逻辑会暂停两个周期),结合之前的暂停逻辑,stall与flush逻辑如下。
branchstall = (PCSrcD AND RegWriteE AND (WriteRegE == rsD OR WriteRegE == rtD)) OR (PCSrcD AND MemtoRegM AND (WriteRegM == rsD OR WriteRegM == rtD)) StallF = StallD = FlushE = lwstall OR branchstall
这部分的forward逻辑如下所示:
ForwardAD = (rsD != 0) AND (rsD == WriteRegM) AND RegWriteM ForwardBD = (rtD != 0) AND (rtD == WriteRegM) AND RegWriteM
改进后的数据通路如《从零开始写处理器(1)》中所示,有点懒了就用了同一张图。代码如下:
`timescale 1ns / 1ps module hazard_handle( input [4:0] rsE,rtE,//forward RAW input [4:0] rtD,//stall RAW input [4:0] rsD, input [4:0] writeregM,writeregW,//forward RAW input regwriteM,regwriteW,//forward RAW input memtoregE,//stall RAW input [4:0] writeregE, input pcsrcD,memtoregM,regwriteE, input jump,//from control unit output [1:0]forwardAE,forwardBE, output forwardAD,forwardBD, output flushD, output stallF,stallD,flushE); reg lwstall; wire bstall; wire bstall_temp1,bstall_temp2; //RAW data hazard forward_unit forward_RAW(writeregM,writeregW,rsE,rtE,regwriteM,regwriteW,forwardAE,forwardBE); //RAW data hazard(lw) always@(*) begin if(((rtD==rsE)||(rtD==rtE))&memtoregE&(rtD!=0)) lwstall = 1; else lwstall = 0; end //assign {stallF,stallD,flushE} = {3{lwstall}}; //control hazard(branch) assign flushD = pcsrcD | jump; wire [1:0]forwardAD_temp,forwardBD_temp; forward_unit forward_control(writeregM,0,rsE,rtE,regwriteM,0,forwardAD_temp,forwardBD_temp); assign forwardAD = forwardAD_temp[0]; assign forwardBD = forwardBD_temp[0]; assign bstall_temp1 = pcsrcD&&(regwriteE&&((writeregE==rsD)||(writeregE==rtD))); assign bstall_temp2 = pcsrcD&&(memtoregM&&((writeregM==rsD)||(writeregM==rtD))); assign bstall = bstall_temp1 | bstall_temp2; assign stallD = lwstall | bstall; assign stallF = lwstall | bstall; assign flushE = lwstall | bstall; endmodule `timescale 1ns / 1ps module forward_unit( input [4:0]baseM,baseW, input [4:0]rs,rt, input ctrM,ctrW, output reg [1:0]forwardA,forwardB); //M-->01,W-->10 always@(*) begin if((rs!=0)&&(rs==baseM)&&ctrM) forwardA = 2'b01; else if ((rs!=0)&&(rs==baseW)&&ctrW) forwardA = 2'b10; else forwardA = 2'b00; end always@(*) begin if((rt!=0)&&(rt==baseM)&&ctrM) forwardB = 2'b01; else if ((rt!=0)&&(rt==baseW)&&ctrW) forwardB = 2'b10; else forwardB = 2'b00; end endmodule
到这里处理器就完成了,下一节会分享一些测试的代码。
本文所有的图来源于《Digital Design and Computer Architecture(2nd Edition)》by David Harris and Sarah L. Harris,用到的电路与设计思路也源于此书。