五级流水线CPU(扩展指令)-Verilog设计

MIPS五级流水线CPU(扩展指令)-Verilog设计

整体架构

  • 整体结构如下
    图片
  • 本次设计与P5主要不同为增加了乘除指令,由于乘除法计算时间较长,不应当只用一个周期进行运算,而应当设计一采用多个周期计算,并且在计算乘除指令时应当将其他涉及乘除模块的指令阻塞于前一个流水极。

设计方案

  • 整体思路采用分布式译码(在每个极分别实例化一个Control模块,并输出相应的选择信号)。
  • 冒险处理采用分布式判断(在每个极分别使用一个模块用于判断是否进行转发和阻塞)。
  • 实际上CPU内部并没有存储器模块,因此我们应当将存储器外置,并为CPU设计访问外部存储器的接口。

流水线模块设计

PC

  1. 输入端口设置
    • [31:0]imm:PC跳转值
    • PCset:beq类PC跳转选择
    • PCjump:J指令跳转选择
    • clk:时钟信号
    • reset:同步复位信号
  2. 输出端口设置
    • [31:0]PCout:当前PC值
  3. 模块内部实现:根据PCset与PCjump选择PC寄存器值的变化行为,reset有效时PC寄存器值设置为0x3000。
  4. 部分代码实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    always @(posedge clk) begin
    if (reset == 1) begin
    pc <= 16'h3000;
    end else begin
    if (freez == 0) begin
    if (PCset == 1) begin
    pc <= imm;
    end else if (PCjump == 1) begin
    pc <= imm;
    end else begin
    pc <= pc + 4;
    end
    end else begin
    pc<=pc;
    end
    end
    end

Control

根据输入的opcode和func判断当前指令,并据此判断各输出端口输出情况,具体如下表。

类型输出 add sub ori beq lw sw jr jal lui and or addi andi lh lb sh sb mult multu div divu mfhi mflo mthi mtlo bne slt sltu nop
RegDst 1 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 1 1 0
ALUsrc 0 0 1 0 1 1 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0
MemtoReg 0 0 0 0 1 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0
RegWrite 1 1 1 0 1 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 0 0 0 1 1 0
MemWrite 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0
PCset 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 10 00 00 00
PCjump 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
EXTop 00 00 00 00 01 01 00 00 10 00 00 01 00 01 01 01 01 00 00 00 00 00 00 00 00 00 00 00 00
PCtoReg 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
RegtoPC 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
ALUop 000 001 010 010 000 000 010 010 000 011 010 000 011 000 000 000 000 010 010 010 010 010 010 010 010 010 100 101 010
extMemWordOp 000 000 000 000 000 000 000 000 000 000 000 000 000 100 010 000 000 000 000 000 000 000 000 000 000 000 000 000 000
mDataByteen 00 00 00 00 00 11 00 00 00 00 00 00 00 00 00 10 01 00 00 00 00 00 00 00 00 00 00 00 00
MULsrc 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 01 01 01 00 00 10 10 00 00 00 00
MULop 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 001 010 011 100 000 000 000 000 000 000 000 000
losrc 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
hisrc 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0
loToReg 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0
hiToReg 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0
mulWrite 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 1 1 0 0 0 0
1
2
3
4
5
6
always @(*) begin
case (code[31:26])
`zero: begin
case (code[5:0])
`addfun: begin
//TODO......

GRF

  1. 输入端口设置
    • [4:0]A1:PD1输出对应寄存器地址
    • [4:0]A2:PD2输出对应寄存器地址
    • [31:0]WD:写入寄存器的值
    • [4:0]A3:写入寄存器地址
    • [31:0]pc:当前pc值,用于输出测试
    • clk:时钟信号
    • reset:同步复位信号
    • WE:写使能信号
  2. 输出端口设置
    • [31:0]PD1:A1寄存器对应值
    • [31:0]PD2:A2寄存器对应值
  3. 模块内部实现
    设置一32位寄存器数组grf[0:31]作为寄存器堆,reset有效时全部归零,若写使能信号有效则系统调用输出所需测试的信息,若A3!=0则写入到对应位置(0号寄存器恒为0)。
  4. 部分代码实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    always @(posedge clk) begin
    if (reset==1) begin
    for (i = 0;i<32 ;i=i+1 ) begin
    grf[i]<=0;
    end
    end
    else begin
    if (WE==1) begin
    if (A3!=0) begin
    grf[A3]<=WD;
    end
    $display("@%h: $%d <= %h", pc, A3, WD);
    end
    end
    end
    assign PD1 = (A3==A1&&WE==1&&A3!=0)?WD:grf[A1];
    assign PD2 = (A3==A2&&WE==1&&A3!=0)?WD:grf[A2];

EXT

  1. 输入端口设置
    • EXTop:0时零扩展,1时符号扩展
    • [15:0]input1:需要扩展的数
  2. 输出端口设置
    • [31:0]output1:扩展后的数
  3. 根据上述信息实现即可
  4. 部分代码实现
    1
    2
    3
    assign output1 = (EXTop == 1)?{{16{input1[15]}},input1}:
    (EXTop == 2)?{input1,{16{1'b0}}}:
    {{16{1'b0}},input1};

ALU

  1. 输入端口设置
    • [31:0]input1:输入数
    • [31:0]input2:输入数
    • [1:0]op:为00时做加法,01时做减法,10时做或运算,11比较两个输入相等输出1,否则输出0
  2. 输出端口设置
    • [31:0]output1:输出对应计算结果
  3. 根据上述信息实现即可
  4. 部分代码实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    assign x1 = ($signed(input1)<$signed(input2));
    assign x2 = (input1<input2);
    assign output1 = (op==0)?(input1+input2):
    (op==1)?(input1-input2):
    (op==2)?(input1|input2):
    (op==3)?(input1&input2):
    (op==4)?((x1==1)?1:0):
    (op==5)?((x2==1)?1:0):
    0;

CMP

比较模块,两个输入值相等时output1输出1,不相等时output2输出1。

  • 部分代码实现
    1
    2
    assign output1 = (input1==input2);
    assign output2 = (input1!=input2);

IF_DR

  • 用于将I极产生的指令机器码和pc值传递至D极,内部用寄存器实现分隔I极与D极

DR_EX

  • 用于将D极的机器码,pc值,GRF的两个输出值PD1、PD2,sw指令需要写入内存的值传递至E极

EX_DM

  • 用于将E极的机器码,pc值,ALU计算结果,sw指令需要写入内存的值,MUL中hi,lo寄存器的值,E极指令的tNew传递至M极

DM_WB

  • 用于将M极的机器码,pc值,ALU计算值,内存读出值,hi、lo寄存器的值,M极指令tNew传递至W极

MUL

乘除法计算模块,进行乘除运算,写入hi、lo寄存器等操作。

  1. 输入端口设置
    • [31:0] input1:第一个输入
    • [31:0] input2:第二个输入
    • [2:0] opIn:模块进行的乘除运算种类
    • [1:0] mulsrcIn:选择模块进行乘除法计算或是写入hi、lo寄存器
    • [1:0] losrcIn:选择写入lo寄存器的值
    • [1:0] hisrcIn:选择写入hi寄存器的值
    • mulWriteIn:hi,lo写使能
  2. 输出端端口设置
    • [31:0] outputLo:lo寄存器输出值
    • [31:0] outputHi:hi寄存器输出值
    • startOut:乘除计算启动信号
    • busyOut:正在进行乘除计算的状态信号
  3. 实现
    根据输出的乘除法计算选择信号判断是否启动乘除法计算,若启动则将start置为1,一个周期后start复位为0,同时busy信号置为一,根据乘除运算种类在其各自执行了相应周期数后busy复位为0,将计算值写入hi、lo寄存器。若不启动乘除操作但是需要写入hi,lo寄存器,则直接写入即可。
  4. 部分代码实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    always @(*) begin
    mulu=$signed(input1)*$signed(input2);
    divu=$signed(input1)/$signed(input2);
    restu=$signed(input1)%$signed(input2);
    if (mulsrcIn==1&&busy==0) begin
    //TODO...
    end else if (mulsrcIn==2&&busy==0) begin
    //TODO...
    end else begin
    //TODO...
    end
    end
    always @(posedge clk) begin
    if (reset==1) begin
    //TODO...
    end else begin
    //TODO...
    end
    end

MemWExt

负责对从内存中读出的数据截取出所需要的部分并进行相应的扩展。

  • 部分代码实现
    1
    2
    3
    4
    5
    6
    assign output1 = (op==0)?input1:
    (op==1)?{{24{1'b0}},input1[8*A+:8]}:
    (op==2)?{{24{input1[7+8*A]}},input1[8*A+:8]}:
    (op==3)?{{16{1'b0}},input1[16*A[1]+:16]}:
    (op==4)?{{16{input1[15+16*A[1]]}},input1[16*A[1]+:16]}:
    0;

冒险判断模块设计

为处理D,E,M产生的数据冲突,分别设计dangerD,dangerE,dangerMw三个控制器,控制器中保存指令的tUse,tNew用于判断是否需要转发或阻塞,为了阻塞的方便执行,此CPU阻塞仅在D极执行。

  • dangerD
    • freez信号:判断是否需要阻塞
    • tl与tr信号:判断CMP模块两个输入端是否需要转发,以及sw指令需要写入内存的寄存器的值是否需要转发
    • 特别地:由于sw指令所要使用的两个寄存器的使用时间并不相同,此处采用的方法为:对sw一类指令设计两个tUse,分别表示两个寄存器的使用时间,其中用于计算写入内存的地址的寄存器的值留到E极ALU模块以前随其他指令一同判断转发,而需要写入内存的寄存器则在D极与beq等指令一同判断是否需要转发或阻塞
    • 部分代码实现
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      assign freez = (((code[31:26]==`zero&&code[5:0]==`addfun)||(code[31:26]==`zero&&code[5:0]==`subfun)||code[31:26]==`beq||code[31:26]==`bne||(code[31:26]==`zero&&code[5:0]==`Andfun)||(code[31:26]==`zero&&code[5:0]==`Orfun)||(code[31:26]==`zero&&code[5:0]==`sltfun)||(code[31:26]==`zero&&code[5:0]==`sltufun)||(code[31:26]==`zero&&code[5:0]==`multfun)||(code[31:26]==`zero&&code[5:0]==`multufun)||(code[31:26]==`zero&&code[5:0]==`divfun)||(code[31:26]==`zero&&code[5:0]==`divufun))&&((tUse < tE && (code[25:21] == regE || code[20:16] == regE)&&regE!=0)||(tUse < tM && (code[25:21] == regM || code[20:16] == regM)&&regM!=0)||(tUse < tW && (code[25:21] == regW || code[20:16] == regW)&&regW!=0)))||
      ((code[31:26]==`ori||code[31:26]==`lw||code[31:26]==`sw||(code[5:0]==`jrfun&&code[31:26]==`zero)||code[31:26]==`addi||code[31:26]==`andi||code[31:26]==`lb||code[31:26]==`lh||code[31:26]==`sb||code[31:26]==`sh||(code[31:26]==`zero&&code[5:0]==`mthifun)||(code[31:26]==`zero&&code[5:0]==`mtlofun))&&((tUse < tE && (code[25:21] == regE)&&code[25:21]!=0)||(tUse < tM && (code[25:21] == regM)&&code[25:21]!=0)||(tUse < tW && (code[25:21] == regW)&&code[25:21]!=0)))||
      ((code[31:26]==`sw||code[31:26]==`sb||code[31:26]==`sh)&&((tUseR < tM && (code[20:16] == regM)&&code[20:16]!=0)||(tUseR < tW && (code[20:16] == regW)&&code[20:16]!=0)))||
      (((code[31:26]==`zero&&code[5:0]==`multfun)||(code[31:26]==`zero&&code[5:0]==`multufun)||(code[31:26]==`zero&&code[5:0]==`divfun)||(code[31:26]==`zero&&code[5:0]==`divufun)||(code[31:26]==`zero&&code[5:0]==`mfhifun)||(code[31:26]==`zero&&code[5:0]==`mflofun)||(code[31:26]==`zero&&code[5:0]==`mthifun)||(code[31:26]==`zero&&code[5:0]==`mtlofun))&&(start==1||busy==1));
      assign tL = (tUse >= tM&&tM==0&&RegWriteM==1&& (code[25:21] == regM)&&code[25:21]!=0)?1:
      (tUse >= tW&&tW==0&&RegWriteW==1&& (code[25:21] == regW)&&code[25:21]!=0)?2:
      0;
      assign tR = (RegWriteM==1&&tUse >= tM && tM==0&& (code[20:16] == regM)&&code[20:16]!=0)?1:
      (RegWriteW==1&&tUse >= tW &&tW==0&& (code[20:16] == regW)&&code[20:16]!=0)?2:
      0;
  • dangerE
    与dangerD大体相同,但不进行阻塞判断,同时需要输出tNew的值与当前指令需要写入寄存器堆的地址
    • 部分代码实现
      1
      2
      3
      4
      5
      assign tN=tNew;
      assign regEOut=((code[31:26]==`zero&&code[5:0]==`mfhifun)||(code[31:26]==`zero&&code[5:0]==`mflofun)||(code[31:26]==`zero&&code[5:0]==`addfun)||(code[31:26]==`zero&&code[5:0]==`subfun)||code[31:26]==`beq||code[31:26]==`bne||(code[31:26]==`zero&&code[5:0]==`Andfun)||(code[31:26]==`zero&&code[5:0]==`Orfun)||(code[31:26]==`zero&&code[5:0]==`sltfun)||(code[31:26]==`zero&&code[5:0]==`sltufun))?code[15:11]:
      (code[31:26]==`ori||code[31:26]==`lw||code[31:26]==`addi||code[31:26]==`andi||code[31:26]==`lb||code[31:26]==`lh||code[31:26]==`lui)?code[20:16]:
      (code[31:26]==`jal)?5'b11111:
      0;
  • dangerMw
    输出部分与dangerE相同,但是特别地,由于sw写入内存的值可能由于前一条指令对寄存器堆的修改而改变,故需要在M极判断是否需要转发
    • 部分代码实现
      1
      2
      assign memAddrTrans=(MemWrite==1)?((RegWriteW==1&&code[20:16]==regW&&code[20:16]!=0)?1:0):
      0;

备注

Control各输出端口含义

  • RegDst:有效时选择rd值作为GRF写入地址
  • ALUsrc:有效时选择EXT输出值作为ALU其中一个输入,反之为寄存器输出其中一个输出值
  • MemtoReg:有效时选择DM读出值作为寄存器写入值
  • RegWrite:作为GRF写使能信号
  • MemWrite:作为DM读写控制信号
  • PCset:PC模块对应输入,有效时,若ALU输出结果为1则将imm符号扩展后左移2为的值作为PC模块输入。
  • PCjump:PC模块对应输入,有效时将jump地址作为PC模块输入
  • EXTop:EXT模块op端口输入
  • PCtoReg:有效时将PC值写入31号寄存器
  • RegtoPC:有效时选择相应位置寄存器输出作为jump地址
  • ALUop:作为ALU模块op端口输入
  • extMemWordOp:作为MemWExt模块截取与扩展的控制信号
  • MULsrc:乘除模块启动乘除法计算,写入hi,lo寄存器的选择信号
  • MULop:乘除法种类选择信号
  • losrc:写入lo寄存器选择信号
  • hisrc:写入hi寄存器选择信号
  • loToReg:选择写入寄存器堆的值为lo寄存器读出的值
  • hiToReg:选择写入寄存器堆的值为hi寄存器读出的值
  • mulWrite:hi,lo寄存器写使能信号

部分问题的解释

  1. 结构冒险的处理:此CPU采用哈佛架构,指令存储器与数据存储器分离,不存在此冒险。
  2. 控制冒险的处理:由于分支跳转在D极实现,此时I极已经读出了跳转指令的下一条指令,此指令理论上应当不执行,但其进入了流水线,会对CPU运行造成干扰。此时引入延迟槽的概念,延迟槽即在分支跳转语句之间插入一条无关指令(CPU会执行此指令,但不会对结果造成任何影响),此指令为编译器所加,不需要CPU设计者考虑(即不需要考虑控制冒险)。但是,由于分支跳转语句下一条指令为无关指令,jal指令在跳转时需要将返回值+4再写入31号寄存器。