基于verilog的流水线CPU(无中断异常)

news2024/12/28 20:16:51

突然发现这个还没传,懒得写了,直接把实验报告传上来吧。

流水线CPU实验报告

​ 本实验最终实现了11条指令,完成了2位控制的分支预测,以及load-use的阻塞,jump的阻塞,beq预测失败的阻塞,还有RAW问题的转发控制。处理了《计算机组成与系统结构》(第3版 清华大学出版社 袁春风)一书中可能忽略的地方。

1.设计思路

指令周期:

  1. 取指令(IF):取出指令,PC自动增长,如果有地址转移,则把地址送入PC
  2. 指令译码(ID):对取指令操作中得到的指令进行分析和译码,产生相应的控制信号
  3. 指令执行(EXE):跟踪指令译码得到的控制信号,具体的执行指令动作
  4. 存储器访问(MEM):写入存储器,或读存储器
  5. 结果写回(WB):指令执行的结果或者访问存储器中得到的数据写回相应的目的寄存器

​ 以此将流水线分为5段,各类数据、控制信号在不同分段分别加上后缀F、D、E、M、W,用来区分是哪一段的。

1.1 指令集

基本形式(R-type,I-type,J-type)

表1 指令集表
31-2625-2120-1615-1110-65-0
op(6bits)rs(5bits)rt(5bits)rd(5bits)shamt(bits)funct(6bits)
oprsrtimmediate(16bits)
optarget address(26bits)

实现的11条指令:

表2 指令具体形式
指令对应的码
add000000_rs_rt_rd_shamt_100000
sub000000_rs_rt_rd_shamt_100010
subu000000_rs_rt_rd_shamt_100011
slt000000_rs_rt_rd_shamt_101010
sltu000000_rs_rt_rd_shamt_101011
ori000001_rt_rs_imm
addiu001000_rt_rs_imm
lw100011_rt_rs_imm
sw101011_rt_rs_imm
beq000100_rs_rt_imm
j000010_addr
表3 控制信号表
指令OpCodeRegWriteRegDstALUSrcBranchMemWriteMemtoRegALUOp
R-TYPE00000011000010
lw10001110100100
sw1010110X101X00
beq0001000X010X01
addiu00100010100X00
ori00000110100X11
j0000100X000000

1.2 CPU内部控制信号

表4 控制信号表
变量名含义
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预测器件中用来控制是否预测进行跳转
RD1SrcRs中读出的数据的来源,具体内容在数据转发中会详细说明
RD2SrcRt中读出的数据的来源

​ 对于前六个控制信号,随着所处流水段不同,有不同的后缀: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}

表5 指令的汇编机器码
#AssemblyAddressMachine
main:addi $2,$0,502002005
addi $3,$0,1242003000c
addi $7, $3,-982067fff7
or $4, $7, $2c00e22025
and $5, $3, $41000642824
add $5, $5, $41400a42820
beq $5, $7,end1810a7000a
slt $4, $3,$41c0064202a
beq $4, $0,around2010800001
addi $5, $0,02420050000
around:slt $5,$7, $22800e2282a
add $7, $4, $52c00853820
sub $7, $7, $23000e23822
sw $7,68($3)34ac670044
lw $2, $80( $0)388c020050
addi $2, $0,14020020001
j end3c08000011
end:sw $2,84($0)44ac020054
ori $10,$2,-148044affff

​ 为了测试loadStall的效果,将j end 和addi $2, $0,1,交换,其实j end没用了,但仍然可以用来测试jumpStall的效果。

2.数据通路

在这里插入图片描述

图1 五段流水线数据通路基本框架

​ 基本框架使用书上的框架,将控制信号合并的一个控制器件controller中,且随流水线传递。在此基础上有比较大的改动,按流水段的顺序来介绍。

2.1 IF阶段

在这里插入图片描述

在这里插入图片描述

图2 IF阶段数据通路

​ 在这个阶段需要完成指令的取出以及PC的正确更新。其余部分都是平凡的,需要重点说一下beq预测的器件,为了完成预测,需要尽可能将Branch的地址提前计算,由于译码是在ID阶段的,所以至多提前到ID阶段,而在ID阶段完成译码后,将BranchD传递给beqForsee,开始进行预测。

​ 在阐述beqForsee内部的具体细节之前,先阐述一下为什么能正确更新,由于流水线阶段为 I F → I D IF\rightarrow ID IFID,当在ID阶段完成译码时,下一指令刚从im中被取出来,并被IF/ID寄存器的时钟阻挡在外,也就是说,指令并没有被传递到下一阶段,这个时候将Branch传递回后,立即更新PCbeq,如果发生跳转,则可以正确取出跳转后的第一条指令。

2.1.1 beqForsee(分支预测)

​ 分支预测使用两位预测位的状态转换:

在这里插入图片描述

图3 分支预测状态转移图

​ 为了完成预测以及预测是否正确的判断,需要输入控制信号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阶段

在这里插入图片描述

图4 ID阶段的数据通路

这一阶段主要完成译码,PCbranch的计算(使用一个adder),寄存器数据的读取,以及load-use的阻塞操作。

2.2.1 寄存器的结构冒险

​ 解决是容易的

在这里插入图片描述

​ 上升沿写,下降沿读即可。

2.2.2 寄存器数据的冒险(书中疑似忽略的问题)

#AssemblyAddressMachine
main:addi $2,$0,502002005
addi $3,$0,1242003000c
addi $7, $3,-982067fff7
or $4, $7, $2c00e22025

​ 截取仿真所用机器码的前4个指令,不难发现一个问题,在地址为0的指令中,我们需要向2号寄存器中写入5,在经过三个周期后,我们需要从2号寄存器中读出数据(理应为5),这应该不会导致冒险,因为当addi处于Wr阶段时,or处于ID阶段,刚好完美的赶上。

​ 但在仿真时出现了问题,笔者发现了在or指令时,从reg[2]读出的数据是x,检查reg文件后,发现reg[2]当时没有被写入任何数据!仔细排查后,发现了一个问题,那就是寄存器也有一个时钟,而这个时钟是必要的,因为我们需要这个时钟来控制读写:上升沿写,下降沿读。那么这导致了在addi指令在Wr阶段传回来的DataWrite将会被寄存器文件的时钟阻挡在外面,or指令在ID阶段不能从寄存器文件中读出正确的数据!它们差了一个时钟周期。

​ 书上的文字部分阐述了使用寄存器时钟来解决结构冒险的方法,但在数据通路中却没有画出这个时钟,笔者不知道是意为不需要还是单纯的忽略了,如果使用这个时钟,那么这个数据冒险就是需要解决的。

在这里插入图片描述

图5 Registers示意图

​ 解决办法就是将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阶段

在这里插入图片描述

图6 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阶段操作的寄存器相同仍然需要转发。

​ 最终转发控制式为:
123123

​ 转换成实际代码的话,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。

在这里插入图片描述

图7 书中ALU转发控制的部分数据通路

​ 这样的处理是粗糙的,因为当执行sw指令时,我们需要将Rt中寄存器的值存在内存中,而Rt寄存器的值应该是由00,01,10这三个转发控制得到的,也就是说,我们需要先经过转发控制得到一个WriteData,并将其传递到Mem阶段,紧接着,再进入由ALUsrc控制的二选一复用器,决定是寄存器值还是立即数。

即使书上只是部分数据通路,但不可能对srcB进行四选一的处理,书上这一部分可能有误。

在这里插入图片描述

图8 SrcB的正确转发

2.4 Mem阶段

在这里插入图片描述

图9 Mem阶段的数据通路

​ 这一阶段是容易的,只需要根据MemWr判断是否要写内存,并读出内存值,传递到下一阶段即可。

2.5 Wr阶段

在这里插入图片描述

图10 Wr阶段的数据通路

​ 这一阶段只需要判断最终写到寄存器的值是来自ALU计算结果还是内存即可,没有太多可说的。

最终的数据通路

在这里插入图片描述

3.仿真结果

​ 仿真程序就是将表5中的机器码存入im中执行,与之前的实验相同,不再展示。

在这里插入图片描述

在这里插入图片描述

图11 仿真结果

​ 第二张图是datamemory中的结果,由于我将j end 和addi $2, $0,1交换了,并将addi指令改为addi $2,$2,1使得2号寄存器最终的值是-3,而不是-4,所以内存地址21处的值为-3而不是-4。

​ 笔者很关心阻塞的情况,故对三种阻塞详细阐述一下。

3.1 beq跳转

在这里插入图片描述

图12 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阻塞

在这里插入图片描述

图13 load部分的仿真

​ 为了测试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没有变化,但接着能正确运行

​ 检查各个寄存器的最终值,也和期望值相同,仿真成功。

表5 指令的汇编机器码
#AssemblyAddressMachine
main:addi $2,$0,502002005
addi $3,$0,1242003000c
addi $7, $3,-982067fff7
or $4, $7, $2c00e22025
and $5, $3, $41000642824
add $5, $5, $41400a42820
beq $5, $7,end1810a7000a
slt $4, $3,$41c0064202a
beq $4, $0,around2010800001
addi $5, $0,02420050000
around:slt $5,$7, $22800e2282a
add $7, $4, $52c00853820
sub $7, $7, $23000e23822
sw $7,68($3)34ac670044
lw $2, $80( $0)388c020050
addi $2, $2,14020420001
j end3c08000011
end:sw $2,84($0)44ac020054
ori $10,$2,-148044affff

​ 为了测试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


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2130478.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《2024中国数据要素产业图谱2.0版》重磅发布

数据猿出品 本次“数据猿2024年度三大媒体策划活动——《2024中国数据要素产业图谱2.0版》”的发布,下一次版本迭代将于2024年12月底发布2024年3.0版,敬请期待,欢迎报名。 大数据产业创新服务媒体 ——聚焦数据 改变商业 随着技术不断革新&a…

C++函数在库中的地址

本文讲述C如何直接调用动态库dll或者so中的函数。 首先我们准备一个被调用库,这个库里面有两个函数,分别是C98 与 C11 下的,名称是run2和run1。 被调用库 相关介绍请看之前的文章《函数指针与库之间的通信讲解》。 //dll_ex_im.h #ifndef…

【通俗理解】二项分布的均值与方差——从成功与失败的概率看分布

【通俗理解】二项分布的均值与方差——从成功与失败的概率看分布 关键词提炼 #二项分布#均值#方差#成功概率#失败概率#伯努利试验 公式解释与案例 二项分布的基本公式 二项分布描述的是在n次独立重复的伯努利试验中,成功次数的概率分布。每次试验的成功概率为p&…

【Android安全】Ubuntu 16.04安装GDB和GEF

1. 安装GDB sudo apt install gdb-multiarch 2. 安装GEF(GDB Enhanced Features) 官网地址:https://github.com/hugsy/gef 2.1 安装2021.10版本 但是在Ubuntu 16.04上,bash -c "$(curl -fsSL https://gef.blah.cat/sh)"等命令不好使&…

如何用 OBProxy 实现 OceanBase 的最佳路由策略

引言 OBProxy,即OceanBase Database Proxy,也简称为ODP,是 OceanBase数据库的专属服务代理。通过应用OBProxy,由后端OceanBase集群的分布式特性所带来的复杂性得以屏蔽,从而使得访问分布式数据库的体验如同访问单机数…

linux上用yolov8训练自己的数据集(pycharm远程连接服务器)

pycharm如何远程连接服务器,看之前的文章 首先去GitHub上下载项目地址,然后下载预训练模型放到项目主目录下 然后下载数据集,我这有个推荐的数据集下载网站,可以直接下载yolov8格式的数据集(还支持其他格式的数据集&a…

进程间通信-命名管道

目录 原理 代码 简单通信 回归概念 原理 mkfifo 是 Linux 系统中的一个命令,用于创建命名管道(named pipe),也称为 FIFO(First In, First Out)。命名管道是一种特殊类型的文件,用于进程间通…

从0到1!本地部署一个大语言模型!完整方法!

要想从零开始部署一个**大语言模型(LLM)**到本地,不仅仅是硬件上安装软件包,还需要对模型选择、优化和应用搭建有一定的理解。下面是一份完整教程,手把手带你走过如何在本地环境中部署LLM。 1. 了解部署需求与硬件准备…

交换机链路聚合

一、概述 通过链路聚合,可以提高链路的可靠性,提升链路带宽。链路具有一般有手工模式和LACP模式。 二、拓扑图 三、实操演练 1、手工模式 手工模式一般用于老旧、低端设备。 缺点: (1)为了使链路聚合接口正常工作…

brew install node提示:Error: No such keg: /usr/local/Cellar/node

打开本地文件发现Cellar目录下无法生成 node文件,应该是下载时出现问题,重复下载无法解决问题,只能重新安装brew。 步骤1(安装 brew): /bin/zsh -c “$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/ra…

打造高效实时数仓,从Hive到OceanBase的经验分享

本文作者:Coolmoon1202,大数据高级工程师,专注于高性能软件架构设计 我们的业务主要围绕出行领域,鉴于初期采用的数据仓库方案面临高延迟、低效率等挑战,我们踏上了探索新数仓解决方案的征途。本文分享了我们在方案筛选…

uniapp离线(本地)打包

安卓离线打包 注意:jdk建议选择1.8 下载Android Studio配置gradle仓库地址 第一步:先下载对应的版本,进行压缩包解压 第二步:在电脑磁盘(D盘),创建文件夹存放压缩包并进行解压,并创…

8.8canny算子检测

目录 实验原理 示例代码 运行结果 实验原理 在OpenCV中,Canny边缘检测是一种广泛使用的边缘检测算法。它是由John F. Canny在1986年提出的,并且因其性能优良而被广泛应用。在OpenCV中,Canny边缘检测是通过Canny函数实现的。 函数原型 v…

【爬虫软件】小红书按关键词批量采集笔记,含笔记正文、转评赞藏等!

一、背景介绍 1.1 爬取目标 熟悉我的小伙伴都了解,我之前开发过2款软件: 【GUI软件】小红书搜索结果批量采集,支持多个关键词同时抓取! 【GUI软件】小红书详情数据批量采集,含笔记内容、转评赞藏等! 现在…

HuggingFists算子能力扩展-PythonScript

HuggingFists作为一个低代码平台,很多朋友会关心如何扩展平台算子能力。扩展平台尚不支持的算子功能。本文就介绍一种通过脚本算子扩展算子能力的解决方案。 HuggingFists支持Python和Javascript两种脚语言的算子。两种语言的使用方式相同,使用者可以任选…

C++速通LeetCode简单第3题-相交链表

简单解: /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode(int x) : val(x), next(NULL) {}* };*/ class Solution { public:ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {Li…

ACL-latex模板中参考文献出现下划线---由于宏包的冲突

% \usepackage{ulem} %加入后造成参考文献有下划线,正常情况是没有的。 别的包也可能造成此情况,可以仔细检查。 如下图所示: \usepackage{ulem}在LaTeX中的作用主要是提供了一系列用于文本装饰和强调的命令。ulem宏包由Donald Arseneau…

移动订货小程序哪个好 批发订货系统源码哪个好

订货小程序就是依托微信小程序的订货系统,微信小程序订货系统相较于其他终端的订货方式,能够更快进入商城,对经销商而言更为方便。今天,我们一起盘点三个主流的移动订货小程序,看看哪个移动订货小程序好。 第一、核货宝…

Redis搭建集群

功能概述 Redis Cluster是Redis的自带的官方分布式解决方案,提供数据分片、高可用功能,在3.0版本正式推出。 使用Redis Cluster能解决负载均衡的问题,内部采用哈希分片规则: 基础架构图如下所示: 图中最大的虚线部分…

AI基础 L19 Quantifying Uncertainty and Reasoning with Probabilities I 量化不确定性和概率推理

Acting Under Uncertainty 1 Reasoning Under Uncertainty • Real world problems contain uncertainties due to: — partial observability, — nondeterminism, or — adversaries. • Example of dental diagnosis using propositional logic T oothache ⇒ C av ity • H…