本设计为一个五级流水线CPU,此CPU结构为MIPS结构。流水线CPU与单周期和多周期CPU相比较,提高了指令的执行速度,改善了CPU的整体吞吐率,提高了CPU的性能。流水线CPU相对单周期CPU和多周期CPU,硬件设计上也更复杂,并且还有许多使流水线断流的因素。在设计中,重点解决影响流水线的数据相关、结构相关、控制相关,做到充分流水。
本设计为一个五级流水线CPU,采用MIPS结构。此CPU支持多种指令集,能够满足基本的功能需求。在此设计中,编写了一个求平均数和实现位反转的程序,以验证CPU功能的正确性以及是否充分流水。
cpu流水线技术是一种将指令分解为多步,并让不同指令的各步操作重叠,从而实现几条指令并行处理,以加速程序运行过程的技术。指令的每步有各自独立的电路来处理,每完成一步,就进到下一步,而前一步则处理后续指令。
取指令(IF)、译码(ID)、执行(EX)、访存(MEM)、回写(WB)
MIPS架构的CPU拥有32个通用寄存器和32个浮点寄存器,每个寄存器的编号、代号以及用途如下:
编号 | 名称 | 用途 |
---|---|---|
$0 | $zero | 常量0,且不会被用户指令修改 |
$1 | $at | 汇编器保留,用于伪指令的编译 |
$2-$3 | v 0 − v0- v0−v1 | 函数调用返回值 |
$4-$7 | a 0 − a0- a0−a3 | 函数调用参数,用于传递参数 |
$8-$15 | t 0 − t0- t0−t7 | 临时变量 |
$16-$23 | s 0 − s0- s0−s7 | 临时变量 |
$24-$27 | t 8 − t8- t8−t9 | 临时变量 |
$28 | $gp | 全局指针 |
$29 | $sp | 堆栈指针 |
$30 | $fp | 栈帧指针 |
$31 | $ra | 返回地址 |
使用了S0到S5这六个寄存器
add $s1,$0,$0; | lw s 0 , 0 ( s0,0( s0,0(s3);
add $s5,$0,$0; | add s 1 , s1, s1,s1,$s0;
addi $s3,$0,m; | addi s 3 , s3, s3,s3,1;
addi $s4,$0,n; | ben s 3 , s3, s3,s2,x;
add s 2 , s 4 , s2,s4, s2,s4,s3; | div s 5 , s5, s5,s1,$s4;
x : add $s0,$0,$0; | sw s 5 , 0 ( s5,0( s5,0(s2);
对应的二进制代码为:
@0000_0000 | 8E700000
00008821 | 02308821
0000A821 | 26730001
24130000 | 12720005
24140009 | 0234A81A
02939021 | AE550001
ALU的功能为对输入的数据进行加减等算数运算,ALU有一个控制端F,根据输入F的不同,对输入的数据进行不同的运算。
对于立即数类的指令,立即数为16位数据,立即数要与RF输出的32位数据进行运算。为了在运算时对数据进行匹配,需要将立即数扩展为32位,对输入的立即数进行补码扩展。
IF_ID:取指令与指令译码之间的寄存器,为配合流水线相关控制,该寄存器有使能端和异步复位端。
ID_EX:指令译码与指令执行阶段之间的寄存器,该寄存器有异步复位端。该寄存器除要传输数据外,还需要传输控制信号以及指令的信息。
EX_MEM:执行阶段与读存储器阶段之间的寄存器,需要传递控制信号与相应的指令信息。
MEM_WB:读存储器与回写阶段之间的寄存器,需要传递控制信号与相应的指令信息。
五级流水线CPU的数据通路为在单周期CPU的数据通路中插入四个流水线寄存器实现。并且要注意在指令执行过程中,指令的相关信息必须沿流水线传递到数据执行结束。
为了使设计更加简洁,构造了一个相关控制器专门用于检测相关并进行处理。在相关控制器中分为检测机构与执行机构,检测机构用于检测相关性,执行机构用于检测到相关时如何进行处理。
结构相关是由于 多条指令需要同时争用同一个功能部件引起的冲突。在次CPU设计中,采用了IM与DM分离的设计,故流水线可以支持读指令与读写数据的并行执行,不存在存储器争用问题。对于同时出现的寄存器堆读写操作来说,虽然寄存器堆只有一个,但其结构采用了读写双端口设计,这样就能同时支持对寄存器堆的读写两个操作,不存在寄存器堆的资源冲突。所以在此设计的CPU中,不存在结构相关。
数据相关是因为后进入流水线的指令需要引用先期进入流水线正在执行的指令计算结果而引起的冲突。相应的解决方案有数据转发和数据暂停机制。
①数据转发:对于数据相关,如果寄存器新值已经出现在流水线中的某级寄存器了,则可以将这个数据从该寄存器直接输入致需要使用该寄存器值的功能部件,这种技术成为转发(或旁路)。
为了实现转发,首先需要分析目的寄存器的新值会出现在那些流水线寄存器中,其次对于一条指令来说,要分析它真正使用新值的地方。
②数据暂停:当出现lw等指令时,如果下一条指令需要lw的执行结果,但lw的执行结果最早也要在其第四个时钟周期才出现,转发无法解决这一问题,办法只有暂停后续指令的执行。
暂停包括两个环节。首先暂停取指令,必须禁止PC继续执行+4计数(PC寄存器的使能端置零)。其次向后级流水线注入空操作,将后级流水线清空(使能寄存器的异步清楚操作)。
因为在程序中存在分支、函数调用等,所以在指令执行过程中会存在导致指令执行方向改变的相关指令。控制相关就是指指令执行方向改变,但流水线在取下一条指令时却不知道该从那个方向取指令。如下例所示如果流水线不对执行过程中做任何处理,那么本不该执行的add与sub均会被错误的执行。可以通过缩短分支延时解决。
缩短分支延时:流水线中,尽早完成分支的决策,可以减少性能损失。(以beq指令为例)假设图中分支目标地址的输出阶段从MEM级前移到EX级,那么图中(下图)的指令序列只需要被阻塞2个时钟周期就能解决控制冒险因为正确的目标地址在EX级形成,EX级结束时就更新了PC寄存器。
采用Modelsim进行仿真,对顶层模块外加时钟信号和复位信号,仿真波形如下:
从使用的寄存器值的变化可以看出指令执行过程正确,最终的的结果正确(图中y0y5代表寄存器s0s5,y为最终存放结果的存储器)。
图中为PC的使能信号与流水线寄存器的使能和控制信号,可见在指令执行过程中,当出现控制相关时,这些控制信号在不断变化,证明当存在指令的控制相关时,这些控制信号在不断解决这些相关。
图中为在相关控制器中解决数据相关的控制信号,在指令执行过程中,在出现数据相关的位置,控制信号不断变化,可以证明解决了数据相关的问题。
module cpu_top(
input clk,
input reset,
);
wire [31:0] pc,npc,ext,exte;
wire m1sel,m2sel,m3sel,rf_wr,dm_wr,zero;
wire m1sele,m2sele,m3sele,dm_wre,rf_wre,m2selw,m2selm;
wire [4:0] rsD,rtD,rdD,rsE,rtE,rdE,dstE,dstM,dstW;
wire [31:0] RDs,RD_o ;
wire [2:0] ALUop,ALUope;
wire [31:0] RD1o,RD2o;
wire PCEnD,FlushD,FlushE,FrsD,FrtD,EnD;
wire [1:0] FrsE,FrtE;
PC PC_inst(
.clk(clk),
.reset(reset),
.di(npc),
.do(pc),
.PCEnD(PCEnD)
);
wire [31:0] ID;
assign rsD=ID[25:21];
assign rtD=ID[20:16];
assign rdD=ID[15:11];
wire [31:0] ALU_out,ALU_outm,ALU_outw,A,B,Bs,Bs_o;
wire [31:0] WD,RD1,RD2,RD1_o,RD2_o;
wire [4:0] A3;
assign RD1_o=(FrsD==1'b0)? RD1:ALU_outm;
assign RD2_o=(FrtD==1'b0)? RD2:ALU_outm;
assign A3=dstW;
assign WD=(m2selw==1'b0)?ALU_outw:RD_o;
assign zero=(RD1_o==RD2_o)?1'b1:1'b0;
RF RF_Inst(
.clk(clk),
.wr(rf_wrw),
.A1(ID[25:21]),
.A2(ID[20:16]),
.A3(A3),
.WD(WD),
.RD1(RD1),
.RD2(RD2),
);
wire [2:0] cs;
NPC NPC_inst(
.pc(pc),
.cs(cs),
.ra(RD1_o),
.imm(ID[25:0]),
.npc(npc),
.zero(zero)
);
wire [31:0] RD;
IM IM_inst(
.A(pc),
.D(RD)
);
IF_ID IF_ID_inst(
.clk(clk),
.FlushD(FlushD),
.EnD(EnD),
.IF(RD),
.ID(ID)
);
EXT EXT_inst(
.in16(ID[15:0]),
.out32(ext)
);
ID_EX ID_EX_inst(
.clk(clk),
.s(FlushE),
.rf_wr(rf_wr),
.dm_wr(dm_wr),
.m1sel(m1sel),
.m2sel(m2sel),
.m3sel(m3sel),
.ext(ext),
.ALUop(ALUop),
.rsD(rsD),
.rtD(rtD),
.rdD(rdD),
.RD1(RD1_o),
.RD2(RD2_o),
.rf_wre(rf_wre),
.dm_wre(dm_wre),
.m1sele(m1sele),
.m2sele(m2sele),
.m3sele(m3sele),
.ALUope(ALUope),
.exte(exte),
.rsE(rsE),
.rtE(rtE),
.rdE(rdE),
.RD1o(RD1o),
.RD2o(RD2o)
);
assign A=(FrsE==2'b00)? RD1o: ((FrsE==2'b01)?WD:ALU_outm);
assign Bs=(FrtE==2'b00)? RD2o: ((FrtE==2'b01)?WD:ALU_outm);
assign B=(m3sele==1'b0)? Bs:exte;
ALU ALU_inst(
.A(A),
.B(B),
.F(ALUope),
.Y(ALU_out)
);
assign dstE=(m1sele==1'b0)?rtE:rdE;
EX_MEM EX_MEM_inst(
.clk(clk),
.rf_wre(rf_wre),
.m2sele(m2sele),
.dm_wre(dm_wre),
.ALU_out(ALU_out),
.wd_i(Bs),
.dste(dstE),
.rf_wrm(rf_wrm),
.m2selm(m2selm),
.dm_wrm(dm_wrm),
.ALU_outm(ALU_outm),
.wd_o(Bs_o),
.dstm(dstM)
);
DM DM_inst(
.A(ALU_outm),
.WD(Bs_o),
.wr(dm_wrm),
.clk(clk),
.RD(RDs),
);
MEM_WB MEM_WB_inst(
.clk(clk),
.rf_wrm(rf_wrm),
.m2selm(m2selm),
.ALU_outm(ALU_outm),
.RD_i(RDs),
.dstm(dstM),
.rf_wrw(rf_wrw),
.m2selw(m2selw),
.ALU_outw(ALU_outw),
.RD_o(RD_o),
.dstw(dstW)
);
CU CU_inst(
.op(ID[31:26]),
.fun(ID[5:0]),
.cs(cs),
.rf_wr(rf_wr),
.dm_wr(dm_wr),
.m1sel(m1sel),
.m2sel(m2sel),
.m3sel(m3sel),
.ALUop(ALUop)
);
CUm CUM_inst(
.rsD(rsD),
.rtD(rtD),
.rsE(rsE),
.rtE(rtE),
.dstE(dstE),
.dstM(dstM),
.dstW(dstW),
.rf_wre(rf_wre),
.rf_wrm(rf_wrm),
.rf_wrw(rf_wrw),
.dm_wre(dm_wre),
.dm_wrm(dm_wrm),
.cs(cs),
.zero(zero),
.PCEnD(PCEnD),
.FlushD(FlushD),
.FlushE(FlushE),
.EnD(EnD),
.FrsD(FrsD),
.FrtD(FrtD),
.FrsE(FrsE),
.FrtE(FrtE),
.m2sele(m2sele)
);
endmodule