P6

Coooookie hahah

1 P5 流水线CPU设计方案

1.1 设计概述

本文所设计的为Verilog实现的流水线MIPS架构CPU,该CPU支持28条指令,为此,笔者设计了PC,D_Reg, CMP,GRF,EXT,NPC,E_Reg,MDU,ALU,M_Reg,Load,Store,W_Reg,HCU模块,其中IM和DM通过外设实现。整体搭建采用自下而上的方式完成,该CPU的译码方式是分布式译码。
顶层模块设计图如下
1

1.2 实现指令说明

  • cal_R : add,sub,and,or,slt,sltu,lui
  • cal_I : addi,andi,ori
  • load : lb,lh,lw
  • store : sb,sh,sw
  • B类指令 : beq,bne
  • J类指令 : jal,jr
  • md类 :mult,multu,div,divu
  • mf类 : mfhi,mflo
  • mt类 : mthi,mtlo
    所有指令均不考虑运算溢出

2 工程模块定义

2.1 顶层模块

  • 端口定义
    信号名 方向 位宽 描述
    clk I 1 时钟信号
    reset I 1 同步复位信号
    i_inst_rdata I 32 F级读入指令F_instr
    m_data_rdata I 32 M级读入数据
    i_inst_addr O 32 F_pc
    m_data_addr O 32 M_AR DM取数据地址
    m_data_wdata O 32 M_WD 写入DM数据
    m_data_byteen O 4 DM写入数据的字节使能
    m_inst_addr O 32 M_pc
    w_grf_we O 1 W_RegWrite
    w_grf_addr O 5 W_A3
    w_grf_wdata O 32 W_Data 写入GRF的数据
    w_inst_addr O 32 W_pc

2.2 功能模块定义

2.2.1 PC

  • 端口定义
    信号名 方向 位宽 描述
    clk I 1 时钟信号
    reset I 1 同步复位信号
    stall I 1 暂停信号
    F_npc I 32 下一条指令地址
    F_pc8 O 32 F级pc+8
    F_pc4 O 32 F级pc+4
    F_pc O 32 F级pc
  • 功能定义
    序号 功能名称 功能描述
    1 同步复位 时钟上升沿到来且reset信号有效,PC寄存器中的值置为0x00003000
    2 停止 时钟上升沿到来且stall信号有效,PC保持当前的值不变
    3 写PC寄存器 时钟上升沿到来且reset和stall信号均失效,将npc写入PC寄存器

2.2.2 CMP

该模块通过比较两个输入,输出信号是否执行B类指令的跳转。

  • 端口定义
    信号名 方向 位宽 描述 来源
    r1 I 32 需要比较的第一个数据 cmp1_Fwd转发后所得
    r2 I 32 需要比较的第二个数据 cmp2_Fwd转发后所得
    D_Branch I 2 00:非B类指令
    01:beq
    10:bne
    D_MCU输出
    cmp_out O 1 是否执行B类跳转

2.2.3 GRF

该模块包含32个具有写使能的32位寄存器,对应MIPS架构的$0~$31。
该模块可以实现同步复位和内部转发。

  • 端口定义
    信号名 方向 位宽 描述
    clk I 1 时钟信号
    reset I 1 同步复位信号
    A1 I 5 需要读取的第一个寄存器编号
    A2 I 5 需要读取的第二个寄存器编号
    A3 I 5 需要写入的寄存器编号,来自W级
    WriteData I 32 需要写入的数据,来自W级
    RegWrite I 1 寄存器堆写使能信号,来自W级
    pc I 32 当前D级执行的指令
    RD1 O 32 从A1寄存器中读取的数据
    RD2 O 32 从A2寄存器中读取的数据
  • 功能定义
    序号 功能名称 功能描述
    1 同步复位 时钟上升沿道到来且reset信号有效,GRF中数据清零
    2 读数据 读出A1,A2编号对应的寄存器中的数据并将它分别加载到RD1和RD2
    3 写数据 时钟上升沿到来且RegWrite信号有效,将WriteData写入A3对应寄存器
    4 内部转发 当(A1==A3) && RegWrite && A1 != 0,将WriteData直接加载至RD1

2.2.4 EXT

该模块根据EXTCtrl将指令中的16位立即数扩展位32位

  • 端口定义
    信号名 方向 位宽 描述
    imm16 I 16 需要位扩展的16位立即数
    EXTCtrl I 3 000:符号扩展
    001:0扩展
    010:将立即数加载到高位
    011:signExt(imm + 00)
    E32 O 32 位扩展结果

2.2.5 NPC

该模块通过控制信号和F_pc计算下一条指令的地址

  • 端口定义
    信号名 方向 位宽 描述
    E32 I 32 位扩展后的32位立即数,B类指令使用
    imm26 I 26 指令中的26位立即数,jal指令使用
    pc I 32 D_pc
    ra I 32 cmp1_Fwd转发后得到的D_V1,jr指令使用
    JCCtrl I 2 00:pc+4或者B类指令
    01:jal
    10:jr
    D_npc I 32 跳转指令生效下一条指令地址

2.2.6 MDU

该模块实现乘除法相关运算

  • 端口定义
    信号名 方向 位宽 描述
    clk I 1 时钟信号
    reset I 1 同步复位信号
    E_start I 1 start为1表示当前E级为乘除运算指令
    SrcA I 32 参与运算的第一个数据
    SrcB I 32 参与运算的第二个数据
    MDCtrl I 4 乘除槽运算控制
    0000:mult
    0001:multu
    0010:div
    0011:divu
    0100:mfhi
    0101:mflo
    0110:mthi
    0111:mtlo
    MDR O 32 mf类指令输出结果
    busy O 1 当前乘除槽正在进行md类指令,表示模拟延迟

2.2.7 ALU

该模块实现算术运算

  • 端口定义
    信号名 方向 位宽 描述
    SrcA I 32 参与运算的第一个数据
    SrcB I 32 参与运算的第二个数据
    ALUCtrl I 3 000:A&B
    001: A或B
    010: A+B
    110: A-B
    011:slt
    100:sltu

2.2.8 Load

该模块实现load类指令

  • 端口定义
    信号名 方向 位宽 描述
    m_data_rdata I 32 从外部输入的DM读取初始数据
    byte_Addr I 2 M_AR[1:0],字节地址
    loadOp I 2 00:lw
    01:lh
    10:lb

2.2.9 Store

该模块实现store类指令

  • 端口定义
    信号名 方向 位宽 描述
    WD_temp I 32 准备向DM写入的初始数据
    byteen I 4 向DM写入数据的字节使能
    WD O 32 向DM写入的处理好的数据

2.3 流水寄存器模块定义

2.3.1 D级流水寄存器(IF/ID)

  • 端口定义
    信号名 方向 位宽 描述 来源
    F_instr I 32 F级instr输入 IM F_instr
    F_pc I 32 F级pc输入 PC F_pc
    F_pc8 I 32 F级pc8输入 PC F_pc8
    reset I 1 同步复位
    stall I 1 暂停信号,时钟上升沿到来且stall有效,D级流水寄存器数据保持不变 HCU stall
    clk I 1 时钟信号
    D_instr O 32 D级instr输出
    D_pc8 O 32 D级pc8输出
    D_pc O 32 D级pc输出

2.3.2 E级流水寄存器(ID/EX)

  • 端口定义
    信号名 方向 位宽 描述 来源
    clk I 1 时钟信号
    reset I 1 同步复位信号
    stall I 1 暂停信号,时钟上升沿到来且stall信号有效,E级流水寄存器清空 HCU stall
    D_instr I 32 D级instr输入 D_Reg D_instr
    D_A1 I 5 D级A1输入 D_Reg D_instr[25:21]
    D_A2 I 5 D级A2输入 D_Reg D_instr[20:16]
    D_A3 I 5 D级A3输入 经RegDst信号选择后的D_A3
    D_V1 I 32 D级的V1输入 经cmp1_Fwd转发后的数据
    D_V2 I 32 D级的V2输入 经cmp2_Fwd转发后的数据
    D_pc I 32 D级的pc输入 D_Reg D_pc
    D_pc8 I 32 D级的pc8输入 D_Reg D_pc8
    D_E32 I 32 D级的E32输入 EXT E32
    E_instr O 32 E级的instr输出
    E_A1 O 5 E级的A1输出
    E_A2 O 5 E级的A2输出
    E_A3 O 5 E级的A3输出
    E_V1 O 32 E级的V1输出
    E_V2 O 32 E级的V2输出
    E_E32 O 32 E级的E32输出
    E_pc8 O 32 E级的pc8输出
    E_pc O 32 E级的pc输出

2.3.3 M级流水寄存器(EX/MEM)

  • 端口定义
    信号名 方向 位宽 描述 来源
    clk I 1 时钟信号
    reset I 1 同步复位信号
    E_instr I 32 E级instr输入 E_Reg E_instr
    E_A2 I 5 E级A2输入 E_Reg E_A2
    E_A3 I 5 E级A3输入 E_Reg E_A3
    E_AR I 32 E级ALU计算结果 ALU AR
    E_Data I 32 E级当前指令的有效数据(MDR/AR)
    E_V2 I 32 E级V2输入 经ALUb_Fwd转发后得到的E_ALUSrcB_temp
    E_pc8 I 32 E级pc8输入 E_Reg E_pc8
    E_pc I 32 E级pc输入 E_Reg E_pc
    M_instr O 32 M级instr输出
    M_A2 O 5 M级的A2输出 HCU模块确定转发信号DM_Fwd时会使用
    M_A3 O 5 M级的A3输出
    M_AR O 32 M级的AR输出
    M_Datae O 32 E级的Data输出到M级
    M_V2 O 32 M级的V2输出
    M_pc8 O 32 M级的pc8输出
    M_pc O 32 M级的pc输出

2.3.4 W级流水寄存器(MEM/WB)

  • 端口定义
    信号名 方向 位宽 描述 来源
    clk I 1 时钟信号
    reset I 1 同步复位信号
    M_instr I 32 M级instr输入 M_Reg M_instr
    M_A3 I 5 M级的A3输入 M_Reg M_A3
    M_AR I 32 M级的AR输入 M_Reg M_AR
    M_RD I 32 M级的RD输入 Load RD
    M_pc8 I 32 M级的pc8输入 M_Reg M_pc8
    M_Data I 32 M级指令对应的有效数据 M_Datae/M_pc8
    M_pc I 32 M级的pc输入 M_Reg M_pc
    W_instr O 32 W级instr输出
    W_A3 O 5 W级A3输出
    W_AR O 32 W级AR输出
    W_RD O 32 W级RD输出
    W_pc8 O 32 W级pc8输出
    W_Datam O 32 M级Data输出
    W_pc O 32 W级pc输出

2.4 控制模块定义

2.4.1 MCU

在主控制模块通过解码opcode和func,分为and逻辑和or逻辑,输出控制信号。本CPU采用分布式译码,即在每一个流水级将MCU实例化一次,选择不同的输出信号。

  • 端口定义
    信号名 方向 位宽 描述
    instr I 32 当前指令,流水级寄存器输出
    RegDst O 2 选择D_A3
    Branch O 2 B类指令控制信号
    EXTCtrl O 3 立即数扩展控制信号
    JCtrl O 2 NPC模块跳转控制信号
    ALUCtrl O 3 ALU模块算术运算控制信号
    MDCtrl O 4 MDU模块乘除运算控制信号
    start O 1 表示当前是md类指令
    mf O 1 表示当前是mf类指令
    ALUSrcBSel O 1 ALU_Srcb选择信号
    MemWrite O 1 DM写使能信号
    RegWrite O 1 GRF写使能信号
    loadOp O 1 Load模块load指令控制信号
    m_data_byteen O 4 store类指令字节使能信号
    jal O 1 当前是否是jal信号,从而选择需要转发的data
    MemtoReg O 1 选择需要写入GRF的数据
    Tuse_rs O 2 只在D级输出,当前D级指令还有多久需要使用使用rs寄存器的值
    Tuse_rt O 2 只在D级输出,当前D级指令还有多久需要使用使用rt寄存器的值
    D_Tnew O 2 只在D级输出,当前D级指令还有多久产生需要写入GRF的数据
    E_Tnew O 2 只在E级输出,当前E级指令还有多久产生需要写入GRF的数据
    M_Tnew O 2 只在M级输出,当前M级指令还有多久产生需要写入GRF的数据

    2.4.2 HCU

    在冒险控制模块通过“A-T”法分析转发还是暂停
  • 端口定义
    信号名 方向 位宽 描述
    Tuse_rs I 2 当前D级指令还有多久需要使用rs寄存器的值
    Tuse_rt I 2 当前D级指令还有多久需要使用rt寄存器的值
    E_Tnew I 2 当前在E级的指令还有多久产生写入GRF的数据
    M_Tnew I 2 当前在M级的指令还有多久产生写入GRF的数据
    E_RegWrite I 1 当前E级指令的RegWrite信号
    M_RegWrite I 1 当前M级指令的RegWrite信号
    W_RegWrite I 1 当前W级指令的RegWrite信号
    D_A1 I 5
    D_A2 I 5
    E_A1 I 5
    E_A2 I 5
    E_A3 I 5
    M_A2 I 5
    M_A3 I 5
    W_A3 I 5
    stall O 1 暂停信号
    冻结PC的值
    冻结D级流水寄存器的值
    将E级流水寄存器清空
    cmp1_Fwd O 选择D_V1 00:RD1
    10:M_Data
    cmp2_Fwd O 选择D_V2 00:RD2
    10:M_Data
    ALUa_Fwd O 选择ALU_SrcA 00:E_V1
    01:W_out
    10:M_Data
    ALUb_Fwd O 选择ALU_SrcB 00:E_V2
    01:W_out
    10:M_Data
    DM_Fwd O 选择写入DM的数据 0:M_V2
    01: W_out

2.5 选择模块

2.5.1 功能MUX

MUX名 描述 输出信号 控制信号
MUX_npc 对PC模块F_npc输入信号进行选择 0:F_pc4
1: D_npc
npcSel
MUX_A3 对D级中A3输入信号进行选择
00:D_instr[20:16]
01:D_instr[15:11]
5’b11111
D_A3 E_RegDst
MUX_ALUB 对E级ALU模块SrcB接口的信号进行选择
0:E_ALUSrcB_temp
1:E_E32
E_SrcB ALUSrcBSel
MUX_E_Data 对E级的有效数据进行选择
0:E_AR
1:E_MDR
E_Data E_mf
MUX_M_Data 对M级data进行选择
0:M_V2
1:W_out
M_Data M_jal
MUX_W_Out 对W级data进行选择
00:W_AR
01:W_RD
10;W_pc8
W_out MemtoReg

2.5.2 转发MUX

MUX名 描述 输出信号 控制信号
HMUX_cmp1 将数据转发到D_V1端口 D_V1 cmp1_Fwd
HMUX_cmp2 将数据转发到D_V2端口 D_V2 cmp2_Fwd
HMUX_ALUa 将数据转发到ALUSrcA端口 E_SrcA ALUa_Fwd
HMUX_ALUb 将数据转发到ALUSrcBtemp端口 E_SrcB_temp ALUb_Fwd
HMUX_DM 将数据转发到DM_WD端口 M_WD DM_Fwd

3 重要实现方法

3.1 分支转移实现

3.1.1 B类指令

为了减少因控制冲突导致的暂停(stall),我们将B类指令的判断进行前置,单独使用CMP模块进行判断。如果cmp_out==1 && Branch==1,进行跳转。

3.1.2 jal

当jal进入D级后(此时F级的指令为编译优化调度的指令),D_instr中imm26域的数据进入NPC进行处理,如果当前JCCtrl信号为2’b01(说明当前指令为jal指令),NPC输出转移的地址npc,并进入PC的输入端,在下一时钟沿上升时进入F级,实现转移。

jal指令在实现跳转的同时,还需要将下一条指令的地址存入31号寄存器中,因此我们需要在F级中计算出改地址,并随着jal指令进行流水,最终在W级写入GRF的31号寄存器。由于存在延迟槽,pc+4地址中的指令是编译优化机制调度过来的,因此我们要保存的地址应该为pc+8。

3.1.3 jr

当jr进入D级后(此时F级的指令为编译优化调度的指令),D_V1(经过转发后的D_V1值)进入NPC,如果当前JCtrl为2’b10(说明当前指令为jr指令),NPC输出转移的地址npc,并进入PC的输入端,在下一时钟沿上升时进入F级,实现转移。

3.2 冒险Harzard

“A-T”法

3.2.1 暂停Stall

当一个指令到达D级后,我们将它的Tuse与后面每一级Tnew比较以及A值校验,当Tuse < Tnew时,阻塞流水线。
阻塞在D级:

  1. 冻结PC的值
  2. 冻结D级流水寄存器的值
  3. 将E级流水寄存器清空

3.2.2 转发Forward

当一个指令到达D级后,我们需要将它的Tuse与后面每一级Tnew比较以及A值校验,当Tuse >= Tnew,进行转发。
其中GRF实现内部转发。

3.3 乘除槽

设置MDU模块以支持乘除法以及mf类和md类指令。

3.3.1 mf类指令

直接从HI,LO寄存器读取即可

1
2
3
assign MDR = (MDCtrl == 4'b0100) ? HI :
(MDCtrl == 4'b0101) ? LO :
32'h0000_0000;

3.3.2 mt类指令

将数据写入HI,LO寄存器即可

1
2
3
4
5
6
7
8
9
10
11
case (MDCtrl)
4'b0110 : begin
HI <= SrcA;
HI_temp <= SrcA;
end
4'b0111 : begin
LO <= SrcA;
LO_temp <= SrcA;
end
default: ;
endcase

3.3.3 md类指令

对于md指令需要做到以下三点:

  1. 当md类指令到达E级时,start信号置高,表示要开始处理md类指令
  2. start信号置高后,乘法max赋值为5,除法max赋值为10,busy信号置高,cnt置1,运算结果存入temp寄存器
  3. cnt每个周期自增,当达到max时,将temp寄存器的值写入HI,LO寄存器,同时cnt和busy_reg寄存器清零
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    else if (E_start) begin //E级需要进行乘除法运算
    case (MDCtrl)
    4'b0000 : begin
    {HI_temp,LO_temp} <= $signed (SrcA) * $signed (SrcB) ;
    max <= 5;
    end
    4'b0001 : begin
    {HI_temp,LO_temp} <= $unsigned (SrcA) * $unsigned(SrcB) ;
    max <= 5;
    end
    4'b0010 : begin
    LO_temp <= $signed (SrcA) / $signed (SrcB) ;
    HI_temp <= $signed (SrcA) % $signed (SrcB) ;
    max <= 10;
    end
    4'b0011 : begin
    LO_temp <= $unsigned (SrcA) / $unsigned (SrcB);
    HI_temp <= $unsigned (SrcA) % $unsigned (SrcB);
    max <= 10;
    end
    default: ;
    endcase
    end
    # cnt busy
    always @(posedge clk ) begin
    if (E_start) begin
    busy_reg <= 1'b1;
    cnt <= 1;
    end
    else if ((!E_start) && (cnt == max) && busy_reg) begin
    busy_reg <= 0;
    cnt <= 0;
    HI <= HI_temp;
    LO <= LO_temp;
    end
    else if((!E_start) && ((cnt >= 1) && (cnt < max)) && busy_reg) begin
    cnt <= cnt + 1;
    end
    else begin
    cnt <= 0;
    end
    end

3.3.4 冒险

当E级正在进行md类指令时,会把乘除指令均阻塞在D级

1
assign stall_MD = D_MD && (E_busy || E_start);

4 思考题

  1. 为什么需要有单独的乘除法部件而不是整合进 ALU?为何需要有独立的 HI、LO 寄存器?
    答:①因为乘除法计算有延迟,如果把乘除法部件整合进ALU就会影响到后面每一条涉及计算的指令,大大降低CPU的效率;而将乘除法部件单独设置,只会对后面涉及乘除法的指令产生影响
    ②乘除法指令涉及读写HI,LO寄存器,这与其他指令不同,根据“高内聚,低耦合”的原则,单独设立乘除法部件

  2. 真实的流水线 CPU 是如何使用实现乘除法的?请查阅相关资料进行简单说明。
    答:①乘法:如图,将每个右边的加法器的输出作为左边加法器的输入,形成一个高32的加法器栈。将32个加法器组成组织成一个并行树,这样,只用等待log2(32) = 5次32位长加法的时间,而不是等待32次加法的时间(《计算机组成与设计》P124 3.3.3 )
    ②除法:与乘法同理,但是因为除法运算每次迭代前需要知道减法结果的符号,而乘法却可以即可生成32个部分积,因此除法需要10个周期延迟(结合书本,我的思考是这样的)。还有 一些技术可以每步生成不仅一个商位,如SRT算法。(?)

  3. 请结合自己的实现分析,你是如何处理 Busy 信号带来的周期阻塞的?
    答:对于md指令需要做到以下三点:
    ① 当md类指令到达E级时,start信号置高,表示要开始处理md类指令

    ② start信号置高后,乘法max赋值为5,除法max赋值为10,busy信号置高,cnt置1,运算结果存入temp寄存器

    ③ cnt每个周期自增,当达到max时,将temp寄存器的值写入HI,LO寄存器,同时cnt和busy_reg寄存器清零

    ④阻塞条件

    1
    assign stall_MD = D_MD && (E_busy || E_start);
  4. 请问采用字节使能信号的方式处理写指令有什么好处?(提示:从清晰性、统一性等角度考虑)
    答:①清晰性:能够清晰知道哪个字节需要被写入
    ②统一性:对于sw,sh,sb等不同指令写入不同位,只需考虑字节使能信号哪些位置1即可,处理多种情况具有统一性

  5. 请思考,我们在按字节读和按字节写时,实际从 DM 获得的数据和向 DM 写入的数据是否是一字节?在什么情况下我们按字节读和按字节写的效率会高于按字读和按字写呢?
    答:①实际从DM获得的数据和向DM写入数据都是一字,而不是一字节

    ②对于半字访存或者字节访存时,按字节读或写的效率更高。如果此时还采用按字访问,则需要首先将整个字从内存中拿出来,然后再从字中寻找,效率会更低。

  6. 为了对抗复杂性你采取了哪些抽象和规范手段?这些手段在译码和处理数据冲突的时候有什么样的特点与帮助?
    ①将指令分类,分为cal_R,cal_I,load,store,md,mf,mt几类,用这些类来处理对应信号。
    ②用多位宽的信号表示相似信号,比如ALUCtrl,MDCtrl,EXTCCtrl,loadOp等

祝P6上机一切顺利!

  • Post title:P6
  • Post author:Coooookie
  • Create time:2023-01-02 20:30:13
  • Post link:https://coooookie0913.github.io/2023/01/02/P6/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.