突然发现这个还没传,懒得写了,直接把实验报告传上来吧。
流水线CPU实验报告
本实验最终实现了11条指令,完成了2位控制的分支预测,以及load-use的阻塞,jump的阻塞,beq预测失败的阻塞,还有RAW问题的转发控制。处理了《计算机组成与系统结构》(第3版 清华大学出版社 袁春风)一书中可能忽略的地方。
1.设计思路
指令周期:
- 取指令(IF):取出指令,PC自动增长,如果有地址转移,则把地址送入PC
- 指令译码(ID):对取指令操作中得到的指令进行分析和译码,产生相应的控制信号
- 指令执行(EXE):跟踪指令译码得到的控制信号,具体的执行指令动作
- 存储器访问(MEM):写入存储器,或读存储器
- 结果写回(WB):指令执行的结果或者访问存储器中得到的数据写回相应的目的寄存器
以此将流水线分为5段,各类数据、控制信号在不同分段分别加上后缀F、D、E、M、W,用来区分是哪一段的。
1.1 指令集
基本形式(R-type,I-type,J-type)
31-26 | 25-21 | 20-16 | 15-11 | 10-6 | 5-0 |
---|---|---|---|---|---|
op(6bits) | rs(5bits) | rt(5bits) | rd(5bits) | shamt(bits) | funct(6bits) |
op | rs | rt | immediate(16bits) | ||
op | target address(26bits) |
实现的11条指令:
指令 | 对应的码 |
---|---|
add | 000000_rs_rt_rd_shamt_100000 |
sub | 000000_rs_rt_rd_shamt_100010 |
subu | 000000_rs_rt_rd_shamt_100011 |
slt | 000000_rs_rt_rd_shamt_101010 |
sltu | 000000_rs_rt_rd_shamt_101011 |
ori | 000001_rt_rs_imm |
addiu | 001000_rt_rs_imm |
lw | 100011_rt_rs_imm |
sw | 101011_rt_rs_imm |
beq | 000100_rs_rt_imm |
j | 000010_addr |
指令 | OpCode | RegWrite | RegDst | ALUSrc | Branch | MemWrite | MemtoReg | ALUOp |
---|---|---|---|---|---|---|---|---|
R-TYPE | 000000 | 1 | 1 | 0 | 0 | 0 | 0 | 10 |
lw | 100011 | 1 | 0 | 1 | 0 | 0 | 1 | 00 |
sw | 101011 | 0 | X | 1 | 0 | 1 | X | 00 |
beq | 000100 | 0 | X | 0 | 1 | 0 | X | 01 |
addiu | 001000 | 1 | 0 | 1 | 0 | 0 | X | 00 |
ori | 000001 | 1 | 0 | 1 | 0 | 0 | X | 11 |
j | 000010 | 0 | X | 0 | 0 | 0 | 0 | 00 |
1.2 CPU内部控制信号
变量名 | 含义 |
---|---|
MemToReg | 表明是否从Memory中读取数据并写入Reg,1表示是 |
MemWrite | 是否写内存,1表示是 |
Branch | 分支跳转,将PC更新为计算得出的地址,与ALU输出的Zero配合使用 |
RegDst | 目的寄存器的选取,1选取rt,0为rd |
RegWrite | 是否写寄存器,1表示是 |
jump | 表明是直接跳转,使用特定的PC更新方式 |
PCsrc[1:0] | 决定PC的更新方式,00为+4,01为jump,10为branch |
ALUop[1:0] | 决定ALU的运算方式 |
ALUSrcA[1:0] | ALU的A口来源,用来进行4选1,具体内容在数据转发中会详细说明 |
ALUSrcB[1:0] | ALU的B口来源,同ALUSrcA |
loadStall | 在代码中为stall,用于控制load-use情况的阻塞 |
beqStall | 用于控制beq预测失败情况的阻塞 |
jumpStall | 控制jump的阻塞 |
state | 在beq预测器件中用来控制是否预测进行跳转 |
RD1Src | Rs中读出的数据的来源,具体内容在数据转发中会详细说明 |
RD2Src | Rt中读出的数据的来源 |
对于前六个控制信号,随着所处流水段不同,有不同的后缀:F、D、E、M、W。各自相差一个时钟周期。PCsrc的定义为
P
C
s
r
c
=
{
j
u
m
p
M
,
B
r
a
n
c
h
M
&
Z
e
r
o
M
}
;
PCsrc=\{jumpM,BranchM\&ZeroM\};
PCsrc={jumpM,BranchM&ZeroM};
# | Assembly | Address | Machine |
---|---|---|---|
main: | addi $2,$0,5 | 0 | 2002005 |
addi $3,$0,12 | 4 | 2003000c | |
addi $7, $3,-9 | 8 | 2067fff7 | |
or $4, $7, $2 | c | 00e22025 | |
and $5, $3, $4 | 10 | 00642824 | |
add $5, $5, $4 | 14 | 00a42820 | |
beq $5, $7,end | 18 | 10a7000a | |
slt $4, $3,$4 | 1c | 0064202a | |
beq $4, $0,around | 20 | 10800001 | |
addi $5, $0,0 | 24 | 20050000 | |
around: | slt $5,$7, $2 | 28 | 00e2282a |
add $7, $4, $5 | 2c | 00853820 | |
sub $7, $7, $2 | 30 | 00e23822 | |
sw $7,68($3) | 34 | ac670044 | |
lw $2, $80( $0) | 38 | 8c020050 | |
addi $2, $0,1 | 40 | 20020001 | |
j end | 3c | 08000011 | |
end: | sw $2,84($0) | 44 | ac020054 |
ori $10,$2,-1 | 48 | 044affff | |
为了测试loadStall的效果,将j end 和addi $2, $0,1,交换,其实j end没用了,但仍然可以用来测试jumpStall的效果。
2.数据通路
基本框架使用书上的框架,将控制信号合并的一个控制器件controller中,且随流水线传递。在此基础上有比较大的改动,按流水段的顺序来介绍。
2.1 IF阶段
在这个阶段需要完成指令的取出以及PC的正确更新。其余部分都是平凡的,需要重点说一下beq预测的器件,为了完成预测,需要尽可能将Branch的地址提前计算,由于译码是在ID阶段的,所以至多提前到ID阶段,而在ID阶段完成译码后,将BranchD传递给beqForsee,开始进行预测。
在阐述beqForsee内部的具体细节之前,先阐述一下为什么能正确更新,由于流水线阶段为 I F → I D IF\rightarrow ID IF→ID,当在ID阶段完成译码时,下一指令刚从im中被取出来,并被IF/ID寄存器的时钟阻挡在外,也就是说,指令并没有被传递到下一阶段,这个时候将Branch传递回后,立即更新PCbeq,如果发生跳转,则可以正确取出跳转后的第一条指令。
2.1.1 beqForsee(分支预测)
分支预测使用两位预测位的状态转换:
为了完成预测以及预测是否正确的判断,需要输入控制信号BranchD,BranchE和ZeroE;为了正确更新PC,需要输入PCbranch以及正常更新的PC值;除了输出预测的地址PCbeq外,同时还需要输出预测错误时的阻塞信号beqStall:
状态的转移使用时序逻辑控制:
next_state以及PCbeq的更新使用组合逻辑实现:
如果预测正确,则stall为0,如果预测错误,则stall为1,其余代码则实现了图3中的状态转移。需要注意的是,只有BranchE为1才需要进行验证,因为在Exe阶段使用ALU中的subu后,才能确定是否进行跳转。那么也不难发现,如果预测错误,需要阻塞两个周期,因为在Exe阶段前有两个错误的指令正在执行。
只有在译码(ID)阶段中确定是Branch指令后,才进行预测,但又注意到只有状态为11和10时才预测跳转,则可以稍微简化一下。
2.1.2 IF/ID寄存器
流水段之间的寄存器都是用于缓冲各项数据的,这是容易的,在此单独列出是为了详细阐述阻塞的实现。
其中stall和stall3分别为load和jump阻塞,都只阻塞一个周期,stall2是beq阻塞,需要阻塞两个周期。对于IF/ID寄存器而言,如果只阻塞一个周期,将PC阻塞,将指令阻塞。如果阻塞两个周期,则重新取指令。
此外,完成取指后,PCjump的计算是容易的,可以在任意阶段完成,为了方便后续的操作,我们在IF/ID寄存器中完成运算。阻塞的代码实现已在此处展示,后续不再展示阻塞的代码实现。
2.1.3 PCreg
PC的阻塞是比较困难的,当出现load-use阻塞时,直接阻塞,当出现jump阻塞时,则需要更新PC(跳转到目的地址),当出现beq阻塞时,请注意我们需要回到beq的下一条指令,而此时beq在Exe阶段,也就是说ID阶段时PC+4,IF阶段时PC+8,由于笔者的PCmux使用的是PCPlus4F,则需要-4,实际上直接使用PCPlus4D就好,写报告的时候才发现,不太好改了。
2.2 ID阶段
这一阶段主要完成译码,PCbranch的计算(使用一个adder),寄存器数据的读取,以及load-use的阻塞操作。
2.2.1 寄存器的结构冒险
解决是容易的
上升沿写,下降沿读即可。
2.2.2 寄存器数据的冒险(书中疑似忽略的问题)
# | Assembly | Address | Machine |
---|---|---|---|
main: | addi $2,$0,5 | 0 | 2002005 |
addi $3,$0,12 | 4 | 2003000c | |
addi $7, $3,-9 | 8 | 2067fff7 | |
or $4, $7, $2 | c | 00e22025 |
截取仿真所用机器码的前4个指令,不难发现一个问题,在地址为0的指令中,我们需要向2号寄存器中写入5,在经过三个周期后,我们需要从2号寄存器中读出数据(理应为5),这应该不会导致冒险,因为当addi处于Wr阶段时,or处于ID阶段,刚好完美的赶上。
但在仿真时出现了问题,笔者发现了在or指令时,从reg[2]读出的数据是x,检查reg文件后,发现reg[2]当时没有被写入任何数据!仔细排查后,发现了一个问题,那就是寄存器也有一个时钟,而这个时钟是必要的,因为我们需要这个时钟来控制读写:上升沿写,下降沿读。那么这导致了在addi指令在Wr阶段传回来的DataWrite将会被寄存器文件的时钟阻挡在外面,or指令在ID阶段不能从寄存器文件中读出正确的数据!它们差了一个时钟周期。
书上的文字部分阐述了使用寄存器时钟来解决结构冒险的方法,但在数据通路中却没有画出这个时钟,笔者不知道是意为不需要还是单纯的忽略了,如果使用这个时钟,那么这个数据冒险就是需要解决的。
解决办法就是将Wr阶段的resultW(即经过二选一后的最终结果)转发到ReadData中,使用RD1Src和RD2Src来进行控制。具体的转发将在后续的Exe阶段中与ALUSrc的转发一并阐述。
2.2.3 PCjump与PCbranch
将PCjump和PCbranch提前到ID阶段计算,前者是为了在ID阶段就可以进行跳转,这样只用阻塞一个周期;后者是为了将PCbranch传递给beqForsee完成分支预测,而不需要任何阻塞(指预测不需要阻塞)。
笔者思考过将PCbranch放到Exe阶段,似乎也能够完成预测?但这样阻塞不可避免,因为beq处于Exe阶段时,beq下一条指令(地址+4的那条)已经进入了ID阶段,需要阻塞一个周期,所以提前到ID阶段,需要阻塞的地方更少,性能更好。
2.2.4 load-use的阻塞操作
由于load的数据在Wr阶段才写回,如果紧接着的一条指令需要使用load的寄存器,那么当load在Mem阶段时,下一条指令就要使用这个数据来进行运算了,或许我们可以加一条转发路径,但其实就差一点就能赶上了,只要load的数据达到Wr阶段,我们就可以使用ALUsrc的转发路径直接传递到Exe极端。那么只需要阻塞一个周期就好了。
顺便完成jump的阻塞信号传递。
MemRead实际上为MemtoRegE,即当某一条指令发现上一条指令是lw,且自己的某一个操作数的寄存器需要用到lw指令的寄存器时,发生load-use数据冒险,阻塞一个周期。为什么jump和lw都阻塞一个周期,但要用两个stall信号来控制?因为jump的阻塞需要更新PC地址,而lw不需要,详情可见2.1.3
阻塞操作就是Exe阶段之前的流水段寄存器进行相应的阻塞操作,即PC_reg,IF/ID,ID/Ex,其中ID/Ex储存的是下一条指令的一些控制信号,直接清0,PC_reg和IF/ID不变,再取一次指令。
2.3 Ex阶段
这一阶段的重点是数据转发模块ForwardDetection,其余的部件都是前几个实验实现过的,Ex_Mem模块也只是简单的作为缓冲。
2.3.1 ForwardDetection(数据转发,新增了一种情况)
对于ALU的两个源操作数,当前指令位于Exe阶段时,上一条指令位于Mem阶段,如果Mem阶段需要写Reg,写的Reg不为0(这个十分重要!),并且与当前指令的Rs(操作数A)或Rt(操作数B),则需要转发
类似的,当前指令位于Exe阶段时,上上条指令位于Wr阶段,如果Wr阶段需要写Reg,并且写的Reg不为0,且Mem阶段的目的寄存器不为当前指令操作的两个寄存器(避免两个控制信号同时为1,实际上,如果Wr阶段被写了,Mem阶段也被写了,那肯定时使用Mem阶段被写的数据),则需要转发。
但书中忽略了一种情况,那就是RdM==RtE,但RegWriteM=0,也就是说上一条指令使用了Rs或Rt,但它不会写这两个寄存器,也就是说上一条指令不可能产生冒险,那么在RegWriteM=0的情况下, 还需要继续判断上上条指令是否冒险,如果Wr阶段写的寄存器与Ex阶段操作的寄存器相同仍然需要转发。
最终转发控制式为:
转换成实际代码的话,Src为01时则使用传递到Mem阶段的ALU结果,Src为10时使用传递到Wr阶段并且经过二选一后的resultW(二选一指ALU结果和dm读出的结果中选)。
而RDSrc是寄存器数据的转发,需要转发的原因已于2.2.1中阐述。转发条件是,在Wr阶段需要将数据写回到Reg中,并且ID阶段操作的寄存器不是0号寄存器,且写回的寄存器与ID阶段操作的寄存器相同,则转发正确的寄存器值。
2.3.2 书中SrcB的不当处理
需要注意的是,书上对于SrcB的处理是使用一个MUX4。当SrcB为00时,使用ALUsrc来判断,如果为0则取00,如果为1则取11。
这样的处理是粗糙的,因为当执行sw指令时,我们需要将Rt中寄存器的值存在内存中,而Rt寄存器的值应该是由00,01,10这三个转发控制得到的,也就是说,我们需要先经过转发控制得到一个WriteData,并将其传递到Mem阶段,紧接着,再进入由ALUsrc控制的二选一复用器,决定是寄存器值还是立即数。
即使书上只是部分数据通路,但不可能对srcB进行四选一的处理,书上这一部分可能有误。
2.4 Mem阶段
这一阶段是容易的,只需要根据MemWr判断是否要写内存,并读出内存值,传递到下一阶段即可。
2.5 Wr阶段
这一阶段只需要判断最终写到寄存器的值是来自ALU计算结果还是内存即可,没有太多可说的。
最终的数据通路
3.仿真结果
仿真程序就是将表5中的机器码存入im中执行,与之前的实验相同,不再展示。
第二张图是datamemory中的结果,由于我将j end 和addi $2, $0,1交换了,并将addi指令改为addi $2,$2,1使得2号寄存器最终的值是-3,而不是-4,所以内存地址21处的值为-3而不是-4。
笔者很关心阻塞的情况,故对三种阻塞详细阐述一下。
3.1 beq跳转
从上到下依次为PC1(即PC所在地址,使用十进制),beqStall(预测失败的阻塞信号),state(预测状态,初始为3)。剩下的分别为jumpStall,MemRead,BranchM(比BranchE慢一个时钟周期)。
可以发现在PC为24(十六进制为18)处,发生第一次beq跳转,即预测为发生,PC跳转到68(一个错误的地址),实际上不应该发生,在两个周期后(即beq指令进入了Exe阶段),发现预测错误,则产生beq阻塞信号,同时state状态转为10,阻塞后PC恢复到正确的地址28(十六进制为1c)处,继续运行。
PC在地址32(十六进制为20)处,发生第二次beq跳转,此时state为2依然预测跳转,跳转到40(正确的地址),40处应该是被第一次的预测错误阻塞了两个周期。两个周期后发现预测正确,state转移到11。
3.2 load-use阻塞
为了测试loadStall的效果,将j end 和addi $2, $0,1 交换,并将addi指令改为addi $2,$2,1,这样addi的上一条指令是lw $2, $80( $0),这样再2号寄存器上就发生了load-use数据冒险。当PC为64(十六进制为40)时,检测到了数据冒险,阻塞了一个周期。
从最终结果来看,2号寄存器的-4(即lw的数据)确实是被正确载入了,并只存在了两个周期后,立刻被正确更新为了$2 +1 = -4 +1=-3,阻塞正确,转发正确。
3.3 jump阻塞
jump发生在ID阶段,需要阻塞IF阶段。
可以看到PC被成功的阻塞了,由于跳转地址就是jump的下一条指令,故PC没有变化,但接着能正确运行
检查各个寄存器的最终值,也和期望值相同,仿真成功。
# | Assembly | Address | Machine |
---|---|---|---|
main: | addi $2,$0,5 | 0 | 2002005 |
addi $3,$0,12 | 4 | 2003000c | |
addi $7, $3,-9 | 8 | 2067fff7 | |
or $4, $7, $2 | c | 00e22025 | |
and $5, $3, $4 | 10 | 00642824 | |
add $5, $5, $4 | 14 | 00a42820 | |
beq $5, $7,end | 18 | 10a7000a | |
slt $4, $3,$4 | 1c | 0064202a | |
beq $4, $0,around | 20 | 10800001 | |
addi $5, $0,0 | 24 | 20050000 | |
around: | slt $5,$7, $2 | 28 | 00e2282a |
add $7, $4, $5 | 2c | 00853820 | |
sub $7, $7, $2 | 30 | 00e23822 | |
sw $7,68($3) | 34 | ac670044 | |
lw $2, $80( $0) | 38 | 8c020050 | |
addi $2, $2,1 | 40 | 20420001 | |
j end | 3c | 08000011 | |
end: | sw $2,84($0) | 44 | ac020054 |
ori $10,$2,-1 | 48 | 044affff |
为了测试loadStall的效果,将j end 和addi $2, $0,1,交换,并将addi指令更换为addi $2, $2,1其实j end没用了,但仍然可以用来测试jumpStall的效果。
4.遇见的问题
在整个流水线的实验中,笔者遇见了许多问题,其中有价值的问题记载在了日志中,在此处重新规范的罗列一次。
4.1 寄存器的数据冒险
发现在读取寄存器中的数据时,即使相差三个周期依然存在冒险的现象,详情请见2.2.2
4.2 beq的异常跳转
在使用的汇编代码中,第一个beq时不应该发生跳转的,但实际上发生了,经过检查,发现5号和7号寄存器在参与运算时的值是正确的,ALUout也是0,但就是发生了跳转。
最后发现是我的PCsrc定义错误了,branch的条件应该是branch控制信号为1且zero为1,但我写的是
P
C
s
r
c
=
{
j
u
m
p
M
,
B
r
a
n
c
h
M
}
;
PCsrc=\{jumpM,BranchM\};
PCsrc={jumpM,BranchM};
也就是对于每个branch都直接跳转,这显然是错误的,更正后为:
P
C
s
r
c
=
{
j
u
m
p
M
,
B
r
a
n
c
h
M
&
Z
e
r
o
M
}
;
PCsrc=\{jumpM,BranchM\&ZeroM\};
PCsrc={jumpM,BranchM&ZeroM};
随后便能进行正确跳转了
4.3 阻塞时PC被莫名清0
在beq进行阻塞时,仿真结果中出现了奇怪的现象:stall后,PC变为0,随后从0开始加4加4,直到又遇到了beq,又清0,不停循环。
最后排查后发现我stall得不对,对于控制信号是清0,而不能将PC清0,修正后,PC能够正确更新了。
4.4 jump后的异常执行
在仿真时发现,jump后本应跳过的addi依然被执行了,思考后发现jump是ID阶段,此时jump的下一条指令处于IF阶段,已经处于被执行的序列中,那么为了清除这条指令,我们需要阻塞一个周期,即新增一个阻塞信号jumpStall。
详情可见2.2.3 ,jumpStall被添加到load-use检测部件中,因为它们都是阻塞一个周期的
4.5 阻塞后指令依然被执行
beq指令阻塞两个周期,并清除掉错误执行的指令,但仿真结果显示,那些本应被阻塞掉的指令依旧被执行了。排查后发现我阻塞时对于指令并未清0,而只是原地阻塞,这样会导致指令只是被暂停,而不是被清除,暂停两个周期后依旧会被执行。
将阻塞时流水段中储存的指令清0后,阻塞成功了。
4.6 lw指令的异常
在仿真调试时,发现lw指令并不能很好的执行,即使address正确的被计算出来了,即使寄存器中的值或是转发过来的值是正确的,也无法正确的lw到对应内存地址上。
进一步排查发现DataMemory模块中,输入的DataIn异常,全为x,在vivado中查看数据通路后,发现DataIn是接地的,也就是没有数据传递给他,再往回看Ex阶段,发现本应传递给DataIn的ReadData2在经过ALUSrcB的MUX4处的四选一后,只产生了ALU的B口操作数,而我们需要寄存器的值。
将MUX4更改为一个MUX4和MUX2,第一个MUX4产生寄存器值(含转发),由ALUSrcB[1:0]控制,第二个MUX2在寄存器值和立即数进行选择,由ALUSrc控制。第一个MUX4产生的记为WriteDataE,直接传递到Ex/Mem流水段寄存器中,并随后传入到DataMemory中。
仿真后问题解决,运行正确。详情可见2.3.2
5.Main(顶层模块)
`timescale 1ns / 1ps
module main(
input clk,reset,
output [31:0] WriteData,
output [31:0] addr,
output MemWrite//不用在意的输出
);
logic[1:0] ALUSrcA,ALUSrcB;
//IF阶段
logic [31:0] InstrF,PCPlus4F,PC,PCnext,PC1;
logic PCWr,stall,beqStall,jumpStall;
floprn#(32) PCreg(clk,reset,stall,beqStall,jumpStall,PCnext,PC);
imem im(PC1[7:2],InstrF);//PC[7:2]
Adder PCadd4(PC,32'b100,PCPlus4F);
logic[31:0] InstrD,PCPlus4D,PCjumpD;
IF_ID ifid(clk,reset,stall,beqStall,jumpStall,InstrF,PCPlus4F,PC1[31:28],InstrD,PCPlus4D,PCjumpD);
//Reg/Decode阶段
logic ZeroD,MemToRegD,MemWriteD,RegDstD,RegWriteD,jumpD,ALUsrcAD,BranchD,IorD,PCWrite;
logic zeroE,MemToRegE,MemWriteE,RegDstE,RegWriteE,jumpE,ALUsrcAE,BranchE,ALUSrcE;
logic RegWriteW,IRWrite,RD1Src,RD2Src;
logic[1:0] PCsrcD,ALUsrcBD,PCsrcE,ALUsrcBE;
logic[2:0] ALUcontrolD,ALUcontrolE;
logic[4:0] WriteRegW,RtE,RdE;
logic[31:0] resultW,RegReadData1,RegReadData2,ReadData1D,ReadData2D,signimmD,PCBranchD;
logic[31:0] ReadData1E,ReadData2E,signimmE,PCPlus4E,PCjumpE,PCBranchE,BranchAdderD;
Controller c(clk,reset,InstrD[31:26],InstrD[5:0],Zero,MemToRegD,MemWriteD,PCsrcD,ALUsrcAD,
ALUsrcBD,RegDstD,RegWriteD,IRWrite,jumpD,ALUcontrolD,IorD,PCWrite,BranchD);
Registers rf2(clk,RegWriteW,InstrD[25:21],InstrD[20:16],WriteRegW,resultW,RegReadData1,RegReadData2);
MUX2#32 RD1mux(RegReadData1,resultW,RD1Src,ReadData1D);
MUX2#32 RD2mux(RegReadData2,resultW,RD2Src,ReadData2D);
SignExt se(InstrD[15:0],signimmD);
sl2 branchadd(signimmD,BranchAdderD);
Adder PCbranchAdd(BranchAdderD,PCPlus4D,PCBranchD);
logic[4:0] RsD,RtD,RdD,RsE;
assign RsD=InstrD[25:21],RtD=InstrD[20:16],RdD=InstrD[15:11];
ID_Ex id_ex(clk,reset,stall,beqStall,jumpStall,jumpD,RegWriteD,MemToRegD,MemWriteD,BranchD,ALUsrcAD,RegDstD,ReadData1D,ReadData2D,signimmD,PCPlus4D,PCjumpD,PCBranchD,RsD,InstrD[20:16],InstrD[15:11],ALUcontrolD,jumpE,RegWriteE,MemtoRegE,MemWriteE,BranchE,ALUSrcE,RegDstE,ReadData1E,ReadData2E,signimmE,PCPlus4E,PCjumpE,PCBranchE,RsE,RtE ,RdE,ALUcontrolE);
LoadUseDetection lu(jumpE,MemtoRegE,RsD,RtD,RtE,stall,jumpStall);
//Exec阶段
logic[4:0] WriteRegE,WriteRegM;
logic[31:0] BranchAdderE,srcBE,ALUOutE,PCBranchM,PCjumpM,ALUOutM,ReadData2M;
logic overflowE,ZeroE,signE,ZeroM,overflowM,RegWriteM,MemtoRegM,MemWriteM,BranchM,jumpM;
MUX2#5 RegMux(RtE,RdE,RegDstE,WriteRegE);
//sl2 branchadd(signimmE,BranchAdderE);
//Adder PCbranchAdd(BranchAdderE,PCPlus4E,PCBranchE);
logic [31:0] A_adder,B_adder,WriteDataE,B_adder1;
MUX4#32 SrcAmux(ReadData1E,ALUOutM,resultW,32'b0,ALUSrcA,A_adder);
MUX4#32 SrcBmux1(ReadData2E,ALUOutM,resultW,32'b0,ALUSrcB,WriteDataE);
MUX2#32 SrcBmux2(WriteDataE,signimmE,ALUSrcE,B_adder);
//MUX2#32 alusrcBmux(ReadData2E,signimmE,ALUSrcE,srcBE);
ALU alu2(A_adder,B_adder,ALUcontrolE,ALUOutE,ZeroE,overflowE,signE);
Ex_Mem ex_mem(clk,reset,PCBranchE,PCjumpE,ALUOutE,WriteDataE,WriteRegE,ZeroE,overflowE,RegWriteE,MemtoRegE,MemWriteE,BranchE,jumpE,
PCBranchM,PCjumpM,ALUOutM,ReadData2M,WriteRegM,ZeroM,overflowM,RegWriteM,MemtoRegM,MemWriteM,BranchM,jumpM);
beqForsee beqsee(clk,reset,BranchE,ZeroE,BranchD,PCBranchD,PC,beqStall,PC1);
ForwardDetection fd(RegWriteM,RegWriteW,ALUSrcE,WriteRegM,WriteRegW,RsE,RtE,RsD,RtD,ALUSrcA,ALUSrcB,RD1Src,RD2Src);
//Mem阶段,完成PC的选择
logic[1:0] PCsrc;
//assign PCsrc={jumpM,BranchM&ZeroM}==2'bx0 ? 2'b00:{jumpM,BranchM&ZeroM};//保证在前期未解码前也能正确更新PC
//assign PCsrc=({jumpE,BranchE&ZeroE}== 2'bx0 ? 2'b00:{jumpE,BranchE&ZeroE});//将branch和jump提前到exe阶段
assign PCsrc = {jumpE,BranchE&ZeroE};
logic MemtoRegW,overflowW;
logic [31:0] DataOutM,ALUOutW,DataOutW;
//logic [4:0] WriteRegW;
//MUX4#32 PCmux(PCPlus4F,PCBranchM,PCjumpM,32'b0,PCsrc,PCnext);
MUX4#32 PCmux(PCPlus4F,PCBranchE,PCjumpE,32'b0,PCsrc,PCnext);
DataMemory dm3(clk,MemWriteM,ReadData2M,ALUOutM,DataOutM);
Mem_Wr mem_wr(clk,reset,RegWriteM,MemtoRegM,overflowM,ALUOutM,DataOutM,WriteRegM,
RegWriteW,MemtoRegW,overflowW,ALUOutW,DataOutW,WriteRegW);
//Wr阶段,
MUX2#32 registerDatainMux(ALUOutW,DataOutW,MemtoRegW,resultW);
endmodule