北航计算机组成P5

前言

  • P5工作量确实比P4大了不少,画设计图写文档一天半,搭CPU加测试一天,不过如果课下做好准备过了强测那课上100%能过甚至直接ak走人了

课下

  • 写完P6已经1700行了,主要是我采取了针对指令的译码方式,所以整体代码量会稍微大一点(),主要这样我比较方便debug,所以就没改了

译码方式

  • 集中式译码方式

    • 最大的优点就是方便从P4改过来,其实我选择集中式译码的理由是方便添加一些指令,而且译码行为在D级就可以全完成,所以如果加入一些额外的指令信号不会出现高阻态或者其他的比较抽象的错误(分布式译码课上如果出锅了可能就是这个原因)
    • 最大的缺点就是需要流水的信号实在是太多了,因为所有要用的信号必须在D级就全部生成完毕,所以向E级就会流水各种信号,不过课下认真设计,仔细连线保证没有bug那在课上也没啥,反正课上最多增加两三个新增的端口,反正不会一次写两个寄存器()
  • 分布式译码

    • 最大的优点就是流水的东西少,几乎可以只流水指令,然后每次在接收到指令之后再译码,从里面接出需要的信号,然后提供给这个级
    • 缺点是,缺点是啥来着,我刚开始不想用分布式译码主要是觉得每个级单独译出$T_{new}$信号什么的特别复杂,还不如刚开始就全部翻译处出来了,但是往届学长还是有很多优秀的分布式译码框架的,只是我没有去参考
  • 译码语句没有选assign,那和我搭logisim有什么区别(怒),我verilog就要用不一样的东西,事实是不如assign,代码量还是太大了,不过课上加指令的适合比较方便,毕竟每个指令之间不会互相影响

    always@(*) begin
    NPCSel = `NPC_normal;
    ExtOp = `EXT_zero;
    GRFA3Sel = `GRFA3_zero;
    ...
    case(instr) begin
    `Op_add: begin
    GRFA3Sel = `GRFA3_rd;

    calr = 1'b1;
    end
    ...
    endcase
  • 事实上也可以像logisim一样用与或门阵列的方式,但是我不使用的原因是P3课上加指令的时候加的太慢而且老是搭错线。。。,优点是代码量压缩程度很优秀,尤其适合P6支持更多指令的时候

    wire add, sub.....;
    wire[1: 0] GRFA3Sel...;

    assign add = (Opcode == 6'b0) & (Funct == `Func_add);
    assign GRFA3Sel[0] = (add | ....)
    assign GRFA3Sel[1] = ....
    //或者
    assign GRFA3Sel = (add | ....) ? 2'b00 :
    (ori | .....) ? 2'b01

数据通路设计

F

PC
  • 同理我们先建模最简单(最重要)的PC寄存器,理由同单周期CPU,我们需要PC寄存器指向当前的指令地址
    端口 位宽 方向 描述
    clk 1 input 时钟信号
    reset 1 input 同步复位信号
    we 1 input PC寄存器使能信号
    NPC[31:0] 32 input 传入更新的值
    PC[31:0] 32 output 传出目前的PC值
IM
  • 指令寄存器,同单周期CPU,没有任何改变
    端口 位宽 方向 描述
    PC[31:0] 32 input 传入PC值供取指
    Instr[31:0] 32 input 获得指令

D

D_REG
  • F/D流水寄存器,将F级的信息传递给D级
    端口 位宽 方向 描述
    clk 1 input 时钟信号
    reset 1 input 同步复位信号
    we 1 input D流水寄存器使能信号
    F_PC[31:0] 32 input F级传入PC值
    F_Instr[31:0] 32 input F级传入指令
    D_PC[31:0] 32 output 传入D级PC值
    D_Instr[31:0] 32 output 传入D级别PC值
IM_SPL
  • D级分线器,将指令分为若干部分
    端口 位宽 方向 描述
    Instr[31:0] 32 input 传入解析指令
    Opcode[5:0] 6 output 指令操作码
    Funct[5:0] 6 output R型指令函数操作码
    Rs[4:0] 5 output 源寄存器
    Rt[4:0] 5 output 目标寄存器
    Rd[4:0] 5 output 目的寄存器
    Shamt[4:0] 5 output 移位值
    Imm_i[15:0] 16 output I型指令立即数
    Imm_j[25:0] 26 output J型指令立即数
GRF
  • D级寄存器堆,取出对应寄存器值
    端口 位宽 方向 描述
    clk 1 input 时钟信号
    reset 1 input 同步复位信号
    A1[4:0] 5 input 第一个读寄存器
    A2[4:0] 5 input 第二个读寄存器
    A3[4:0] (W) 5 input 写寄存器
    WD[31:0] (W) 32 input 寄存器写入值
    RD1[31:0] 32 output 读寄存器值(已内部转发)
    RD2[31:0] 32 output 读寄存器值(已内部转发)
EXT
  • D级拓展单元
    • 端口定义

      端口 位宽 方向 描述
      Imm[15:0] 16 input 拓展立即数
      ExtOp[1:0] 2 input 控制立即数拓展
      Ext_imm[31:0] 32 input 拓展后立即数
    • 控制信号定义

      信号名 取值 描述
      EXT_zero 2’b00 控制ExtOp信号零拓展
      EXT_sign 2’b01 控制ExtOp信号符号位拓展
      EXT_lui 2’b10 控制ExtOp信号高位加载
NPC
  • D级次地址计算单元
    • 端口定义

      端口 位宽 方向 描述
      F_PC[31:0] 32 input F级PC值
      D_PC[31:0] 32 input D级PC值
      Rs[31:0] 32 input 跳转寄存器值
      Imm[25:0] 26 input 立即数
      NpcSel[1:0] 2 input 控制NPC
      NPC[31:0] 32 output 次地址
    • 控制信号定义

      信号名 取值 描述
      NPC_normal 2’b00 NpcSel正常跳转
      NPC_branch 2’b01 NpcSel条件跳转
      NPC_jadder 2’b10 NpcSel跳转地址
      NPC_jrs 2’b11 NpcSel跳转寄存器
CMP
  • D级branch指令前移比较器
    • 端口定义
      端口 位宽 方向 描述
      Rs_val[31:0] 32 input rs寄存器读出值
      Rt_val[31:0] 32 input rt寄存器读出值
      CmpOp 1 input 比较类型
      Flag 1 output 比较结果
    • 控制信号定义
      信号名 取值 描述
      CMP_eq 1’b0 控制CmpOp信号相等比较
      CMP_ne 1’b1 控制CmpOp信号不等比较
Control
  • D级集中译码器
    端口 位宽 方向 描述
    Opcode[5:0] 6 input 指令操作码
    Funct[5:0] 6 input 函数码
    Tuse_rs[1:0] 2 output rs寄存器的Tuse
    Tuse_rt[1:0] 2 output rt寄存器的Tuse
    Tnew[1:0] 2 output 指令Tnew
    NpcSel[1:0] 2 output 控制NPC
    CmpOp 1 output 控制CMP
    ExtOp[1:0] 2 output 控制EXT
    GRFA3Sel[1:0] 2 output 控制GRFA3Mux
    ALUASel 1 output 控制ALUAMux
    ALUBSel[1:0] 2 output 控制ALUBMux
    ALUOp[1:0] 2 output 控制ALU
    DMWe 1 output DM使能
    DMOp[2:0] 3 output DM写入方式
    GRFWDSel[1:0] 2 output 控制GRFWDMux
MUX
  • GRFA3Mux
    • 选择GRF写入地址的多路选择器
      信号名 取值 描述
      GRFA3_rt 2’b00 选择rt寄存器值
      GRFA3_rd 2’b01 选择rd寄存器值
      GRFA3_ra 2’b10 选择$ra寄存器
      GRFA3_zero 2’b11 默认不写寄存器
  • RSFDMux
    • 对于读出的rs寄存器进行转发
  • RTFDMux
    • 对于读出的rt寄存器进行转发

E

E_REG
  • D/E流水寄存器

    端口 位宽 方向 描述
    clk 1 input 时钟信号
    reset 1 input 同步复位信号
    we 1 input 写使能信号
    D_rs[4:0] 5 input D级rs编号
    D_rt[4:0] 5 input D级rt编号
    D_a3[4:0] 5 input D级A3
    D_rs_val[31:0] 32 input D级rs寄存器值
    D_rt_val[31:0] 32 input D级rt寄存器值
    D_ext_imm[31:0] 32 input D级ext_imm值
    D_shamt[4:0] 5 input D级shamt值
    D_pcwith8[31:0] 32 input D级PC+8
    E_rs[4:0] 5 output E级rs编号
    E_rt[4:0] 5 output E级rt编号
    E_a3[4:0] 5 output E级A3
    E_rs_val[31:0] 32 output E级rs寄存器值
    E_rt_val[31:0] 32 output E级rt寄存器值
    E_ext_imm[31:0] 32 output E级ext_imm值
    E_shamt[4:0] 5 output E级shamt值
    E_pcwith8[31:0] 32 output E级PC+8
    D_ALUASel 1 input 控制ALUAMux
    D_ALUBSel[1:0] 2 input 控制ALUBMux
    D_ALUOp[1:0] 2 input 控制ALU
    D_DMWe 1 input DM使能
    D_DMOp[2:0] 3 input DM写入方式
    D_GRFWDSel[1:0] 2 input 控制GRFWDMux
    E_ALUASel 1 output 控制ALUAMux
    E_ALUBSel[1:0] 2 output 控制ALUBMux
    E_ALUOp[1:0] 2 output 控制ALU
    E_DMWe 1 output DM使能
    E_DMOp[2:0] 3 output DM写入方式
    E_GRFWDSel[1:0] 2 output 控制GRFWDMux
    D_Tnew[1:0] 2 input Tnew
    E_Tnew[1:0] 2 output Tnew
ALU
  • E级计算单元
    • 端口定义

      端口 位宽 方向 描述
      A[31:0] 32 input ALU第一个操作数
      B[31:0] 32 input ALU第二个操作数
      ALUOp[1:0] 2 input ALU进行的操作
      ALU_out[31:0] 32 output ALU结果数
    • 控制信号定义

      信号名 取值 描述
      ALU_add 2’b00 加法操作
      ALU_sub 2’b01 减法操作
      ALU_or 2’b10 或运算操作
MUX
  • ALUAMux

    信号名 取值 描述
    ALUA_rs 2’b0 选择rs寄存器(一般而言)
    ALUA_rt 2’b1 选择rt寄存器(移位操作)
  • ALUBMux

    信号名 取值 描述
    ALUB_rt 2’b00 选择rt寄存器(一般R型)
    ALUB_imm 2’b01 选择ext_imm
    ALUB_shamt 2’b10 选择shamt
    ALUB_rs 2’b11 选择rs寄存器(R型移位)
  • RSFEMux

  • RTFEMux


M

M_REG
  • E/M流水寄存器
    端口 位宽 方向 描述
    clk 1 input 时钟信号
    reset 1 input 同步复位信号
    we 1 input 写使能信号
    E_rt[4:0] 5 input rt寄存器的编号
    E_rt_val[31:0] 32 input rt寄存器的值
    E_alu_out[31:0] 32 input ALU运算结果
    E_a3[4:0] 5 input A3结果
    E_imm[31:0] 32 input lui结果
    E_pcwith8[31:0] 32 input PC+8结果
    M_rt[4:0] 5 output rt寄存器的编号
    M_rt_val[31:0] 32 output rt寄存器的值
    M_alu_out[31:0] 32 output ALU运算结果
    M_a3[4:0] 5 output A3结果
    M_imm[31:0] 32 output lui结果
    M_pcwith8[31:0] 32 output PC+8结果
    E_DMWe 1 input DM写使能信号
    E_DMOp[2:0] 3 input DM选择
    E_GRFWDSel[1:0] 2 input 选择GRFWD
    M_DMWe 1 input DM写使能信号
    M_DMOp[2:0] 3 input DM选择
    M_GRFWDSel[1:0] 2 input 选择GRFWD
    E_Tnew[1:0] 2 input Tnew
    M_Tnew[1:0] 2 input Tnew
DM
  • M级数据存储器
    • 端口定义

      端口 位宽 方向 描述
      clk 1 input 时钟信号
      reset 1 input 同步复位信号
      we 1 input 写使能
      Adder[31:0] 32 input 地址
      WD[31:0] 32 input 写入值
      RD[31:0] 32 output 读出值
      DMOp[2:0] 3 input 选择DM读写方式
    • 控制信号定义

      信号名 取值 描述
      DM_w 3’b000 字单位写入读出
      DM_h 3’b001 半字单位写入,符号拓展读出
      DM_hu 3’b010 半字单位写入,无符号拓展读出
      DM_b 3’b011 字节单位写入,符号拓展读出
      DM_bu 3’b100 字节单位写入,无符号拓展读出
MUX
  • RTFMMux

W

W_REG
  • M/W级流水寄存器
    端口 位宽 方向 描述
    clk 1 input 时钟信号
    reset 1 input 同步复位信号
    we 1 input 写使能信号
    M_alu_out[31:0] 32 input ALU运算结果
    M_dm_rd[31:0] 32 input 内存读出结果
    M_a3[4:0] 5 input A3结果
    M_imm[31:0] 32 input lui结果
    M_pcwith8[31:0] 32 input PC+8结果
    W_alu_out[31:0] 32 output ALU运算结果
    W_dm_rd[31:0] 32 output 内存读出结果
    W_a3[4:0] 5 output A3结果
    W_imm[31:0] 32 output lui结果
    W_pcwith8[31:0] 32 output PC+8结果
    M_GRFWDSel[1:0] 2 input 选择GRFWD
    W_GRFWDSel[1:0] 2 output 选择GRFWD
GRF
  • W级寄存器堆

    端口 位宽 方向 描述
    clk 1 input 时钟信号
    reset 1 input 同步复位信号
    A1[4:0] (D) 5 input 第一个读寄存器
    A2[4:0] (D) 5 input 第二个读寄存器
    A3[4:0] 5 input 写寄存器
    WD[31:0] 32 input 寄存器写入值
    RD1[31:0] (D) 32 output 读寄存器值(已内部转发)
    RD2[31:0] (D) 32 output 读寄存器值(已内部转发)
MUX
  • GRFWDMux

    信号名 取值 描述
    GRFWD_alu 2’b00 选择alu_out写入
    GRFWD_dm 2’b01 选择dm_rd写入
    GRFWD_pcwith8 2’b10 选择PC+8写入
    GRFWD_imm 2’b11 选择ext_imm写入
  • 说了一大堆,主要注意D级的若干部件还是最大的D_reg,E_reg两个流水寄存器,最好写的时候像我一样将数据、控制信号什么的分开来,而且一定要先写设计文档,详细设计!!!,然后定义端口写模块照着你的设计文档就好了,数据通路的连接最好依照你的设计图,不管是手画还是用其他软件画的,


关于阻塞与转发

  • 为了方便加入指令,我们将所有指令分为若干类别
    • ALU计算R型指令cal_r: add, sub
    • ALU计算I型指令cal_i: ori
    • 特殊指令lui: lui
    • 存储指令store: sw
    • 访存指令load: lw
    • 分支指令branch: beq
    • 跳转链接指令j_l: jal
    • 跳回指令j_r: jr

转发输出口

  • 接下来分析转发电路,明白哪些地方会产生寄存器的新值

    • 首先ALU_outDM_rd这两个端口必然会产生寄存器的新值(这里实际上考虑无脑转发可以将DM_rd换为GRF_wd
    • 对于lui指令,在D阶段后的ext_imm端口已经产生了值,可以转发
    • 对于j_l指令,在D阶段后的PC+8端口已经产生了值,可以转发
    转发输出 指令类型
    ALU_out@M cal_r、cal_i
    GRF_wd@W(过度转发) (cal_r)、(cal_i)、load、(lui)、(j_l)
    ext_imm@{E、M} lui
    PC+8@{E、M} j_l
    寄存器内部转发(M -> D)
    • (指令)代表该指令已经转发过正确的值了
    • 由于寄存器的内部转发使得$W$不需要向$D$转发,而$W$向$E$转发的内容除了load指令外都是ALU_out的值,这里在$M$就已经向$D$转发过了,所以本身就是正确的,不过无脑转发有什么错呢,(大家只是拿一个正确的值替换另一个正确的值罢了,硬件电路不需要感情)
    • 根据需要往grf写入什么来判断需要转发什么,这里只是输出转发的内容,还需要配套转发内容的接收端的选择信号
      assign FWD_E = (E_GRFWDSel == `GRFWD_imm) ? E_imm :
      (E_GRFWDSel == `GRFWD_pcwith8) ? E_pcwith8 : 32'b0;

转发接入口

  • 分析转发的接入端口

    • D级可以接收来自E、M、W级别的转发,但是W级转发我们已经在内部实现了
      assign val1 = (A1 == 0) ? 32'b0 : grf[A1];
      assign RD1 = (A1 != 0) & (A1 == A3) ? WD : val1;
    • E级ALU计算前也可以接收转发
    • 最后单独对于store类型指令,rt寄存器在M阶段才会使用,所以DM的输入端口也可以接收转发
    转发输入 指令类型
    GRF_rs@D cal_r、cal_i、store、j_r、branch
    GRF_rt@D cal_r、branch
    GRF_rs@E 同上
    GRF_rt@E 同上(store指令可能也转了)
    DM_wd@M store
    寄存器内部转发(M -> D)
    • 最后同上,正确的值可能被多次转发,例如store类指令的rt寄存器,在D、E阶段可能都被转发了一次,但是没关系,最后还是对的(),况且真的要考虑只转发一次极容易忽略转发路径,而且在考试的时候容易漏情况,所以我使用无脑转发了
      assign D_rs_val = (D_rs == 5'b0) ? 32'b0 :
      (D_rs == E_a3) ? FWD_E :
      (D_rs == M_a3) ? FWD_M :
      (D_rs == W_a3) ? FWD_W : D_grf_rd1;
    • 这里有一个疑惑,万一正确的数据没生成或者目前是错的但是我们已经无脑转发回去了怎么办?首先我们目前实现的指令集不需要考虑第一种情况,因为它对应着阻塞,Tuse < Tnew时会直接阻塞。因为这种情况来不及转发回去,也就是数据没生成就转发了;第二种情况因为Tuse >= Tnew,那么Tuse等于0时,Tnew一定也是0,说明正确的数据已经生成了并且已经转回去了,覆盖率原来错误的数据。
    • 注意:其实可以使用恰当转发,如加入Tnew==0条件,生成数据才转发回去,不过实际上我们目前不会使用,课上倒是又可能,不过也有其他办法规避,至少目前可以认为我们不选恰当转发(我后面会更新的

阻塞

  • 根据指令类别判断$T_{use}$,$T_{new}$

    指令类别 $ T_{use}:rs $(@D) $ T_{use}:rt $(@D) $T_{new} $@E $ T_{new} $@M $ T_{new} $@W
    cal_r $1$ $1$ $1$ $0$ $0$
    cal_i $1$ ($3$) $1$ $0$ $0$
    lui ($3$) ($3$) $0$ $0$ $0$
    store $1$ $2$ ($0$) ($0$) ($0$)
    load $1$ ($3$) $2$ $1$ $0$
    branch $0$ $0$ ($0$) ($0$) ($0$)
    j_l ($3$) ($3$) $0$ $0$ $0$
    j_r $0$ ($3$) ($0$) ($0$) ($0$)
    • ($3$)代表不会需要读这个寄存器,所以不需要关注它的$T_{use}$,直接认为在W级使用即可,反正没有指令会在W级才用寄存器
    • ($0$)代表不会写寄存器,也即不会影响后续的读取,即A3@{E、M、W}都为0,所以转发会被我们排除,同时也不会干扰阻塞
    • 由于$T_{use}$只会在译码阶段使用所以不需要考虑后续流水,但是$T_{new}$信号将参与流水,表达式为$T_{new}$@{N+1} $= \max{$ $T_{new}$@{N} $ -1,0}$
  • 当D级需要使用rs或者rt且满足(以rs举例):$rs \ne 0$ && $rs == rd$@{N} && $T_{use} \lt T_{new}$@{N}则阻塞

  • 由于我们使用的是暴力转发,能转则转,应转尽转,所以不需要考虑先前是否转发过了,后续就算转发的也一定是正确内容,且转发$E \gt M \gt W \gt GRF$

  • ok,言尽于此,少年加油吧,搭完课下再来看课上的内容。跟我念P5一遍过前来还愿


课上

T1 xe

  • R型指令
    xe rd, rs, rt
    temp1 <- GPR[rs]
    temp2 <- GPR[rt]
    for i in range(0, 16):
    temp <- temp1[2*i+1] ^ temp2[2*i+1]
    GPR[rd] <- temp

T2 jtl

  • I型指令
    jtl rs, rt, label
    temp1 <- GPR[rs] + GPR[rt]
    temp2 <- PC + 4 + sign_ext(offset || 00)
    if temp1 > temp2:
    PC <- temp2
    else:
    PC <- temp1
    Nullifycation()

T3 lwoc

  • I型指令
    lwoc rt, offset(base)
    adder <- GPR[base] + sign_ext(offset || 00)
    vadder <- adder[31: 2]
    temp1 <- Mem[vadder]
    temp2 <- temp1[3: 0]
    if temp1 < 0x80000000:
    GPR[temp2] <- temp1
    else:
    GPR[rt] <- temp1
  • D级译码一个check信号然后流水,就可以在流水线中识别有没有这条指令了
  • 这题说一嘴,我刚开始给他设置了lw的阻塞信号搭配暴力阻塞低16个寄存器,但是WA了两个sw的点,其实这里就是无脑转发的问题
    lwoc $3, 124($0)
    sw $3, 124($0)
    • 假设最后没有向$3写入,但是我们在W级才将$3修改为实际写入的寄存器,这个时候lwoc在M级别就向sw转发了一个完全错误的内容,但是没有像一般的lw一样在W级向sw再次转发,导致sw接收到错误的转发值
    • 解决方案一:刚开始就设置GRFA3Sel == `GRFA3_zero这样就不会向前转发,然后把rt和低16个寄存器堵住,等到W级知道要写什么的情况在继续即可
      assign stall_rs_E = (E_check ? (D_rs == E_rt || D_rs < 5'd16) : D_rs == E_a3) & (D_rs != 5'd0) & (Tuse_rs < E_Tnew)
      //assign stall_rt_E
      //assign stall_rs_M
      //assign stall_rt_M
    • 解决方案二;改为恰当转发,当一个指令得到最后正确的结果才会向前转发,详情可见前面讲暴力转发的时候提了一嘴
      assign stall_rs_E = (E_check ? D_rs < 5'd16 : 1'b1) & (D_rs == E_a3) & (D_rs != 0) & (Tuse_rs < E_Tnew)
      //assign stall_rt_E
      //assign stall_rs_M
      //assign stall_rt_M

      assign D_rs_val = (D_rs == 5'b0) ? 32'b0 :
      ((D_rs == E_a3) && (E_Tnew == 3'b0)) ? FWD_E :
      ((D_rs == M_a3) && (M_Tnew == 3'b0)) ? FWD_M :
      (D_rs == W_a3) ? FWD_W : D_grf_rs;
      //W级一定已经产生数据了
  • 最后怎么AC的呢,当然我考场上并没有发现可能出现的错误,但是不能完全暴力阻塞遇到这条指令则阻塞(课程组卡了一下t),所以我把$31寄存器放出来了(纯粹是因为眼缘,毕竟卡着31号寄存器大概率没啥用),所以就过了,总体而言还是暴力阻塞。