从零开始写处理器(2)——冲突检测

  在本次设计的流水线中,存在一些冲突会使得电路不会向着我们希望的步骤运行。这些冲突分为两种:数据冲突和结构冲突。

1.数据冲突

  我把数据冲突分为三个小类,存储单元的读写冲突、普通RAW(Read After Write)冲突、需要暂停流水线的RAW冲突。

1.1

  对于其中第一小类冲突,可以不通过额外的冲突检测单元,通过对模块设计在下降沿写入数据,在上升沿读入数据即可。

1.2

  对于第二种冲突,存在于下一条指令需要用到上一条指令结果的时候,具体分为两种情况,如下图所示:

 

其中第二条与第三条分别需要用到第一条指令的结果,并且在Decode阶段结果并没有存入寄存器组,因此取出的数有误。针对这种情况,可以利用forward方法(又叫bypass),即将第一条指令的Memory寄存器与WriteBack寄存器结果导入Execute阶段,供后续的二三条指令选择。如果出现第一条指令的rd地址与后续两条指令的源寄存器地址相同,则选择对应的数据,将数据通路更改如下,黑色连接线为实现forward所加的电路:

扫描二维码关注公众号,回复: 9019409 查看本文章

该部分的逻辑如下,以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,用到的电路与设计思路也源于此书。

猜你喜欢

转载自www.cnblogs.com/AAgnosticEngineer/p/12284134.html
今日推荐