P5_流水线CPU

指令分析

支持10指令

  • 运算类:add sub nop lui ori
  • 访存类:sw lw
  • 分支跳转类:beq jal jr

数据冒险

阻塞

根据转发中的第2,3点分析,我们知道每条指令都有相应的产生新数据的时间Tnew(从刚进入D级算起,如果有的话),也有需要用到数据的时间Tuse(从刚进入D级算起,如果有的话)。这样就会构成如下的表格:

如果用不到数据我们就认为是一个很大的时间max(3)

add sub nop ori lui sw lw beq jal jr
Tuse_RS 1 1 max 1 max 1 1 0 max 0
Tuse_RT 1 1 max max max 2 max 0 max max

另一个指标是Tnew,用于体现要回写的新数据产生并存入最近的流水级寄存器堆的时间(从刚进入D级算起)

如果不会产生要回写的数据就记为一个很小的时间min(0)

add sub nop ori lui sw lw beq jal jr
Tnew_DEin 2 2 min 2 1 min 3 min 1 min

转发

D级,E级,M级都需要获取最新的,正确的数据。这种正确性是由两点保证的:阻塞 + 产生了数据立即转发走

转发来源是在他们之后的流水级寄存器。

例如在刚进入D级的时候我们就需要获得正确的数据,这会需要三条转发路径,分别是

1
A1 = WriteBack_DEout / WriteBack_EMout / WriteBack_MWout

同理,在刚进入E级时会有两条转发路径,刚进入M级时会有一条转发路径。总共六条转发路径

  1. 我们要在所有使用数据(0-31号寄存器的值)的地方都保证数据的正确性。

    转发需要满足如下条件:

    • 转发数据来自于流水级寄存器堆,即DEreg,EMreg,MWreg

    • 供给者要写入该数据

      1
      RegWrite_DEout === 1;
    • 需求者使用的寄存器编号 = 供给者将要写入的编号

      1
      rs_DEin === RegAddr_DEout;
    • 要写入的不是0号寄存器

      1
      RegAddr_DEout !== 0;
  2. 供给者:

    为了规整,我们将每级可能用到的要回写的数据都存入WriteBack里,并且各级提供数据的都是WriteBack,所有要回写数据的指令有:

    add sub ori lui lw jal

    • 回写数据产生于D级的指令:lui jal

      回写的数据只能是immExt或当前pc + 8

      1
      2
      3
      assign WriteBack_DEin = (isJal_DEin == 1) ? pc_FDout + 32'h8 : immExt_DEin;
      assign RegAddr_DEin = (RegDst_DEin == 2'b00) ? rt_DEin :
      (RegDst_DEin == 2'b01) ? rd_DEin : 5'h1f ;
    • 回写数据产生于E级的指令:add sub ori

      回写的数据只能是ALU_ans或继承上一流水级输出

      1
      2
      assign WriteBack_EMin = (isJal_DEout === 1 || isLui_DEout === 1) ? WriteBack_DEout : ALUans_EMin;
      assign RegAddr_EMin = RegAddr_DEout;
    • 回写数据产生于M级的指令: lw

      回写的数据只能是WD或继承自上一流水级的输出

      1
      2
      assign WriteBack_MWin = (MemtoReg_EMout == 1) ? WD : WriteBack_EMout;
      assign RegAddr_MWin = RegAddr_EMout;
  3. 需求者:

    • 在D级需要rsrt数据:beq jal jr
    • 在E级需要rsrt数据:add sub ori sw lw
    • 在M级需要rsrt数据:sw
    • 不需要使用rsrt数据:nop lui

思考题

  1. 我们使用提前分支判断的方法尽早产生结果来减少因不确定而带来的开销,但实际上这种方法并非总能提高效率,请从流水线冒险的角度思考其原因并给出一个指令序列的例子。

    答:当分支指令要跳转时,提前分支判断不能提高效率。

    1
    2
    3
    4
    ori $t0, $0, 1
    ori $t1, $0, 1
    beq $t0, $t1, jump
    nop
  2. 因为延迟槽的存在,对于jal等需要将指令地址写入寄存器的指令,要写回PC + 8请思考为什么这样设计?

    由于延迟槽的存在,jaljal的下一条指令之间插入了一条无关指令,因此要想跳回jal的下一条指令应当对31号寄存器写入pc + 8

  1. 我们要求所有转发数据都来源于流水寄存器而不能是功能部件(如DMALU)请思考为什么?

    防止时钟周期过长降低流水线整体速率。

  1. 我们为什么要使用 GPR 内部转发?该如何实现?

    保证计算使用的数据的正确性。

    比较即将写入GRF的寄存器编号是否是即将读取的寄存器编号,如果是则转发。

  1. 我们转发时数据的需求者和供给者可能来源于哪些位置?共有哪些转发数据通路?

    需求者:CMP ALU DM

    供给者:

    • EXT + GRF —> WriteBack_DEout
    • WriteBack_DEout + ALU —> WriteBack_EMout
    • WriteBack_EMout + DM —> WriteBack_MWout
  1. 在课上测试时,我们需要你现场实现新的指令,对于这些新的指令,你可能需要在原有的数据通路上做哪些扩展或修改?提示:你可以对指令进行分类,思考每一类指令可能修改或扩展哪些位置。

    • 计算回写类
    • 判断跳转类
    • 判断访存类
  1. 简要描述你的译码器架构,并思考该架构的优势以及不足。

    译码器架构有两种:

    • 为每条指令设置所有控制信号的取值。这种方式很直观,但是码量很大。

    • 为每个信号设置取值条件。例如

      1
      assign RegWrite = (add || sub || ori || lui || jal || lw) ? 1 : 0;

      这种做法有很强的简洁性,但也失去了直观性。