源码下载
1 实验项目名称
MIPS五级流水线CPU设计
2 实验原理
2.1 流水线CPU
流水线是数字系统中一种提高系统稳定性和工作速度的方法,广泛应用于高档CPU的架构中。根据MIPS处理器的特点,将整体的处理过程分为取指令(IF)、指令译码(ID)、执行(EX)、存储器访问(MEM)和寄存器回写(WB)五级,对应
多周期的五个处理阶段。一个指令的执行需要5个时钟周期,每个时钟周期的上升沿来临时,此指令所代表的一系列数据和控制信息将转移到下一级处理。
2.2 MIPS指令格式
MIPS 指令系统结构有 MIPS-32 和 MIPS-64 两种。本实验的 MIPS 指令选用MIPS-32。以下所说的 MIPS 指令均指 MIPS-32。MIPS 的指令格式为 32 位,以下为 MIPS 指的 3 种格式。
R 型指令的 op 均为 0,具体操作由 func 指定。rs 和 rt 是源寄存器号,rd 是目的寄存器号。移位指令中使用 sa 指定移位位数。
I 型指令的低 16 位是立即数,计算时需扩展到 32 位,依指令的不同需进行零扩展和符号扩展。
J 型指令的低 26 位是地址,是用于产生跳转的目标地址。
2.3 指令集
本实验中共需要完成 12 条指令,其中 R 指令 4 条,I 指令 7 条,J 指令1条,他们的指令结构如下图所示:
2.4 数据通路
\1) IF级:取指令部分。
包括指令储存器和PC寄存器及其更新模块,负责根据PC寄存器的值从指令存储器中取出指令编码和对PC的值进行更新。
\2) ID级:指令译码部分。
根据独处的指令编码形成控制信号和读寄存器堆输出的寄存器的值。流水线冒险检测也在该级进行,冒险检测电路需要上一条指令的MemRead,即在检测到冒险条件成立时,冒险检测电路产生stall信号清空ID/EX寄存器,插入一个流水线气泡。
\3) EX级:执行部分。
根据指令的编码进行算数或者逻辑运算或者计算条件分支指令的跳转目标地址。此外LW、SW指令所用的RAM访问地址也是在本级上实现。控制信号有ALUCode、ALUSrcA、ALUScrB和RegDst,根据这些信号确定ALU操作、选择两个ALU操作数A、B,并确定目标寄存器。另外,数据转发也在该级完成。数据转发控制电路产生ForwardA和ForwardB两组控制信号。
\4) MEM级:存储器访问部分。
只有在执行LW、SW指令时才对存储器进行读写,对其他指令只起到一个周期的作用。该级只需存储器写操作允许信号MemWrite。
\5) WB级:寄存器堆写回部分。
该级把指令执行的结果回写到寄存器文件中。该级设置信号MemtoReg和寄存器写操作允许信号RegWrite,其中MemtoReg决定写入寄存器的数据来自于MEM级上的缓冲值或来自于MEM级上的存储器。
3 实验内容
3.1 流水线CPU总体设计图
4 指令分析
在这部分,我们要完成的是对于 CPU 的测试,检查结果是否正确,所以我们要首先完成的是检查输出,我们构造顶层文件,输出所有的值,检查对应的结果并分析指令。由于在单周期CPU单周期设计中,大多数模块在流水线中均能使用,且上次实验报告我们已经进行过详细的分析。按照张老师的要求,本次指令分析仅对增添模块内容及解决数据冒险和控制冒险问题进行分析。
整体模块代码如下:
module MAIN(Clk,En,Clrn,stall,condep,Addr,pc4,E_bpc,Pcsrc,Inst,D_Inst,Qa,Qb,Alu_X,Alu_Y,E_ALUR,E_FwdA,E_FwdB,W_Din);
input Clk,En,Clrn;
output stall,condep;
output [31:0]Qa,Qb,Alu_X,Alu_Y,E_bpc,W_Din,pc4,Addr,Inst,D_Inst,E_ALUR;
output [1:0]E_FwdA,E_FwdB,Pcsrc;
wire [5:0]E_Op,M_Op;
wire [4:0]D_Rd,E_Rd,M_Rd,W_Rd;
wire E_Z,M_Z,Regrt,Se,Wreg,Aluqb,Wmem,Reg2reg,E_Wreg,M_Wreg,E_Reg2reg,M_Reg2reg,M_Wmem, E_Wmem,E_Aluqb,W_Reg2reg,W_Wreg;
wire [1:0]Aluc,E_Aluc,FwdA,FwdB;
wire [31:0]E_Qa,E_Qb,Alu_Y1,M_Alu_Y1,E_immediate,D_immediate,D_immediate_L2,W_Dout,W_ALUR,M_ALUR,Dout;
wire [31:0]E_pc4,D_bpc,D_pc4,M_bpc,npc;
//IF级
MUX4X32 mux4x32_first(pc4,0,E_bpc,0,Pcsrc,npc);
PC pc(npc,Clk,En,Clrn,Addr,stall);
PCadd4 pcadd4(Addr,pc4);
INSTMEM instmem(Addr,Inst);
REG_ifid ifid(pc4,Inst,En,Clk,Clrn,D_pc4,D_Inst,stall,condep);
//ID级
CONUNIT conunit(E_Op,D_Inst[31:26],D_Inst[5:0],E_Z,Regrt,Se,Wreg,Aluqb,Aluc,Wmem,Pcsrc,Reg2reg,D_Inst[25:21],D_Inst[20:16],E_Rd,M_Rd,E_Wreg,M_Wreg,FwdA,FwdB,E_Reg2reg,stall,condep);
MUX2X5 mux2x5(D_Inst[15:11],D_Inst[20:16],Regrt,D_Rd);
EXT16T32 ext16t32(D_Inst[15:0],Se,D_immediate);
REGFILE regfile(D_Inst[25:21],D_Inst[20:16],W_Din,W_Rd,W_Wreg,Clk,Clrn,Qa,Qb);
SHIFTER32_L2 shifter2(D_immediate,D_immediate_L2);
CLA_32 cla_32(D_pc4,D_immediate_L2,0,D_bpc,Cout);
REGidex idex(Wreg,Reg2reg,Wmem,D_Inst[31:26],Aluc,Aluqb,D_bpc,Qa,Qb,D_immediate,D_Rd,FwdA,FwdB,En,Clk,Clrn,E_Wreg,E_Reg2reg,E_Wmem,E_Op,E_Aluc,E_Aluqb,E_bpc,E_Qa,E_Qb,E_immediate,E_Rd,E_FwdA,E_FwdB,stall,condep);
//EX
MUX4X32 mux4x32_ex_1(E_Qa,W_Din,M_ALUR,0,E_FwdA,Alu_X);
MUX4X32 mux4x32_ex_2(E_Qb,W_Din,M_ALUR,0,E_FwdB,Alu_Y1);
MUX2X32 mux2x321(E_immediate,Alu_Y1,E_Aluqb,Alu_Y);
ALU alu(Alu_X,Alu_Y,E_Aluc,E_ALUR,E_Z);
REGexmem exmem(E_Wreg,E_Reg2reg,E_Wmem,E_Op,E_bpc,E_Z,E_ALUR,Alu_Y1,E_Rd,En,Clk,Clrn,M_Wreg,M_Reg2reg,M_Wmem,M_Op,M_bpc,M_Z,M_ALUR,M_Alu_Y1,M_Rd);
//MEM
DATAMEM datamem(M_ALUR,M_Alu_Y1,Clk,M_Wmem,Dout);
REGmemwb memwb(M_Wreg,M_Reg2reg,M_ALUR,Dout,M_Rd,En,Clk,Clrn,W_Wreg,W_Reg2reg,W_ALUR,W_Dout,W_Rd);
//WB
MUX2X32 mux2x322(W_Dout,W_ALUR,W_Reg2reg,W_Din);
endmodule
控制部件接口图,其中的大部分代码都是于单周期相同的,我们将在下部分展示我们于单周期不同的代码。
4.1 数据冒险解决策略
\1) 将寄存器堆的写操作提前半个周期,意味着我们需要将写操作变成时钟周期下降沿实现,实现思路就是将始终信号取反即可:
D_FFEC32 q1(D,~Clk,En[1],Clrn,Q1);
D_FFEC32 q2(D,~Clk,En[2],Clrn,Q2);
D_FFEC32 q3(D,~Clk,En[3],Clrn,Q3);
D_FFEC32 q4(D,~Clk,En[4],Clrn,Q4);
…
\2) 内部前推,通过两个多路选择器的选择信号来判断前推位置,代码如下:
always@(E_Rd,M_Rd,E_Wreg,M_Wreg,Rs,Rt)begin
FwdA=2'b00;
if((Rs==E_Rd)&(E_Rd!=0)&(E_Wreg==1))begin
FwdA=2'b10;
end else begin
if((Rs==M_Rd)&(M_Rd!=0)&(M_Wreg==1))begin
FwdA=2'b01;
end
end
end
always@(E_Rd,M_Rd,E_Wreg,M_Wreg,Rs,Rt)begin
FwdB=2'b00;
if((Rt==E_Rd)&(E_Rd!=0)&(E_Wreg==1))begin
FwdB=2'b10;
end else begin
if((Rt==M_Rd)&(M_Rd!=0)&(M_Wreg==1))begin
FwdB=2'b01;
end
end
end
\3) 解决lw的数据冒险,在满足FwdA=10条件下,需要再对lw的下一条指令阻塞一个时钟周期,阻塞判断条件代码如下:
assign Reg2reg = I_add|I_sub|I_and|I_or|I_addi|I_andi|I_ori|I_sw|I_beq|I_bne|I_J;
assign stall=((Rs==E_Rd)|(Rt==E_Rd))&(E_Reg2reg==0)&(E_Rd!=0)&(E_Wreg==1);
4.2 控制冒险解决策略
缩短分支延迟,先将M_Z信号和分支目标地址的输出阶段从MEN级前移到EX级,然后再进行两次阻塞就可以解决因为,分支语句引发的冒险问题,与lw数据冒险一样,需要对阻塞条件进行判断,代码如下:
assign condep=(M_beq&M_Z)|(M_bne&~M_Z);
5 结论和体会
5.1 体会感悟
通过此次的CPU设计实验,让我们对CPU内部组成以及指令在CPU部件上如何运作有了一个更深的理解。在实验过程中,我们遇到了各种问题,但是从一开始的迷茫状态,到自己设计CPU的各个部件,再到将指令在器件上运行,对每个部件的功能进行模拟仿真,我们在不断的进步,最后圆满的完成本次实验带给我们的成就感是无以复加的,通过此次实验,让我们对CPU有了更深的理解,对CPU流水线运作过程有了更清晰的认识。
5.2 对本实验过程及方法、手段的改进建议:
过程中,各部件应该尽量再拆分为更小的部件组合而成,每个部件都分别进行调试之后再组装在一起,这样可以降低出错率,便于进行修改,对控制信号的检测应该更加多元化,这样更有利于分析CPU流水线运作过程。