P5

Coooookie hahah

1 P5 流水线CPU设计方案

1.1 设计概述

本文所设计的为Verilog实现的流水线MIPS架构CPU,该CPU支持10条指令,为此,笔者设计了PC,IM,D_Reg,D_MCU,CMP,GRF,EXT,NPC,E_Reg,E_MCU,ALU,M_Reg,M_MCU,DM,W_Reg,W_MCU模块。整体搭建采用自下而上的方式完成,该CPU的译码方式是分布式译码。
顶层模块设计图如下
1
信号控制如下
1

1.2 实现指令说明

  • cal_R : add,sub
  • cal_I :ori,lui(这里lui视为将立即数extend后的加法指令)
  • load :lw
  • store : sw
  • B类指令 : beq
  • J类指令 : jal
  • 特殊 : jr

2 工程模块定义

2.1 功能模块定义

2.1.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.1.2 IM

该模块由4096个32位寄存器组成,大小4096*32bits,用于存储指令
需要注意的是,因为指令的开始地址是0x00003000,所以在IM中取指令时,对应的应该是F_pc - 0x00003000,且由于是以字(4byte,32bits)为单位存储的,所以取addr = temp[13:2]

  • 端口定义
    |信号名|方向|位宽|描述|
    |F_pc|I|32|F级指令的地址|
    |F_instr|O|32|F级指令|

2.1.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.1.4 CMP(用于B类指令)

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

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

2.1.5 EXT

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

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

2.1.6 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或者beq
    01:jal
    10:jr
    D_npc I 32 跳转指令生效下一条指令地址

2.1.7 ALU

该模块实现算术运算

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

2.1.8 DM

该模块由3072个32位寄存器组成,容量3072*32bit

  • 端口定义
    信号名 方向 位宽 描述
    clk I 1 时钟信号
    reset I 1 同步复位信号
    MemWrite I 1 寄存器堆写使能信号
    pc I 32 当前M级执行指令的pc
    Addr I 32 需要写入或者读取的地址
    WriteData I 32 需要写入的数据
    RD O 32 从相应地址读取的数据

2.2 流水寄存器模块定义

2.2.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.2.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.2.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_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_V2 O 32 M级的V2输出
    M_pc8 O 32 M级的pc8输出
    M_pc O 32 M级的pc输出

2.2.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输入 DM RD
    M_pc8 I 32 M级的pc8输入 M_Reg 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_pc O 32 W级pc输出

2.3 控制模块定义

2.3.1 MCU(主控制模块)

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

  • 端口定义
    信号名 方向 位宽 描述
    instr I 32 当前指令,流水级寄存器输出
    RegDst O 2 选择D_A3
    Branch O 1 当前是否时beq指令
    EXTCtrl O 3 立即数扩展控制信号
    JCtrl O 2 NPC模块跳转控制信号
    ALUCtrl O 3 ALU模块算术运算控制信号
    ALUSrcBSel O 1 ALU_Srcb选择信号
    MemWrite O 1 DM写使能信号
    RegWrite O 1 GRF写使能信号
    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.3.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.4 选择模块

2.4.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_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.4.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级:

  • 冻结PC的值
  • 冻结D级流水寄存器的值
  • 将E级流水寄存器清空

3.2.2 转发Forward

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

4 可能的拓展

4.1 draft

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
43
44
45
//正数 原码 = 反码 = 补码
//负数 原码最高位为1;反码是原码符号位不变,其他位取反;补码是反码+1
//互为相反数
((A + B == 32'd0) && (!((A == 32'h8000_0000) && (B == 32'h8000_0000))))

//条件跳转,在CMP模块中增加判断功能

//无条件链接,在MCU中更改RegDst、RegWrite、jal(或新设置一个信号)、MemtoReg
//条件链接/条件写,在D级生成D_check信号然后流水它,每一级根据这个信号判断写入地址和写入值
D_check = D_bgezalc & D_b_jump;
assign RFDst = //...
bgezalc ? (check ? 5'd31 : 5'd0) :
5'd0;


//不跳转时清空延迟槽
//如果当前处于stall状态,不能清空延迟槽,因为stall说明前面指令的Tnew大于Tuse,即需要传入CMP模块的两个值的最新值还没计算出来,因此还无法转发到CMP中
wire D_Reg_clr = check_D & ~D_CMP_out & ~stall;
wire clr = reset || D_Reg_clr;
//或者插入空泡
F_instr = (~D_CMP_out && check_D) ? 32'd0 : im[pc[13:2] - 12'hc00];


//条件存储
//check信号表示当前流水级是新指令
//condition满足写入rt,否则写31号
assign stall_rs_E = (D_A1 != 5'd0) & (check_E ? (D_A1 == E_A3 | D_A1 == 5'd31) : D_A1 == E_A3) & RegWrite_E & (Tuse_rs < Tnew_E);
wire D_A3_new = check_D ? (condition ? rt : 5'd31) : D_A3 ;

//condition满足写入31号,否则不写(写入$0)
assign stall_rs_E = (D_A1 != 5'd0) & (check_E ? (D_A1 == 5'd31 | D_A1 == 5'd0) : D_A1 == E_A3) & RegWrite_E & (Tuse_rs < Tnew_E);
wire D_A3_new = check_D ? (condition ? 5'd31 : 5'd0) : D_A3

//condition满足时写入DM读取值的低五位(不知道要写谁就stall吧)
assign stall_rt_M = (D_A2 != 5'd0) & (check_M ? 1'b1 : D_A2 == M_A3) & RegWrite_M & (Tuse_rt < Tnew_M);
wire M_A3_new = check_M ? DM_out[4:0] : M_A3;

//直接Tnew = 0 才转发的条件转发
assign Fwd_cmp1 = ((D_A1 != 5'd0) & (D_A1 == E_A3) & RegWrite_E & (Tnew_E == 2'b00)) ? 2'b10 :
((D_A1 != 5'd0) & (D_A1 == M_A3) & RegWrite_M & (Tnew_M == 2'b00)) ? 2'b01 :
2'd0 ;

//需要考虑的信号
EXTCtrl ALUCtrl JCtrl RegDst MemtoReg ALUSrcBSel M_jal D_Branch RegWrite MemWrite npcSel
Tuse_rs Tuse_rt D_Tnew E_Tnew M_Tnew

5 思考题解答

  1. 我们使用提前分支判断的方法尽早产生结果来减少因不确定而带来的开销,但实际上这种方法并非总能提高效率,请从流水线冒险的角度思考其原因并给出一个指令序列的例子。
    答:因为提前分支预判所需的数据需要从后续流水级转发而来,所以可能存在提前分支预判时正确数据并没有产生的情况
    1
    2
    add $1,$1,$2
    beq $1,$2,label
    是否提前分支预判,即CMP在D级或者在E级比较$1$2减法结果是否为0,所需时钟周期都是一样的,因为都需要完成add指令E级的计算,而后把计算结果存入M级流水寄存器。
  2. 因为延迟槽的存在,对于 jal 等需要将指令地址写入寄存器的指令,要写回 PC + 8,请思考为什么这样设计?
    答:因为编译优化后,jal的下一条指令是pc+4,如果写回pc+4,当出现jr $ra时,将跳转到延迟槽,这样会导致重复执行延迟槽中的指令,所以要写回pc+8.
  3. 我们要求大家所有转发数据都来源于流水寄存器而不能是功能部件(如 DM 、 ALU ),请思考为什么?
    答:因为流水寄存器中存储的数据是上一级已经计算出来的数据,在当前周期内稳定输出。而功能部件的输出可能有延迟(详细看理论课PPT),如果从功能部件提供转发数据,可能在正确转发数据生成前就转发了错误数据。
  4. 我们为什么要使用 GPR 内部转发?该如何实现?
    答:这样可以将W级将要写入的数据及时加载到GRF的输出端口,从而减少数据冒险。实现如下:
    1
    2
    3
    assign RD1 = (A1 == A3 && RegWrite && A1 != 0) ? WriteData : //内部转发
    (A1 != 0) ? rf[A1] :
    32'h0000_0000;
  5. 我们转发时数据的需求者和供给者可能来源于哪些位置?共有哪些转发数据通路?
    答:需求者:D级,E级,M级
    供给者:E级,M级,W级
    数据通路:
    E->D,M->D,W->D;
    M->E,W->E;
    W->M;
MUX名 描述 输出信号 控制信号
HMUX_cmp1 将数据转发到D_V1端口 D_V1 cmp1_Fwd
00:RD1
10:M_Data
HMUX_cmp2 将数据转发到D_V2端口 D_V2 cmp2_Fwd
00:RD2
10:M_Data
HMUX_ALUa 将数据转发到ALUSrcA端口 E_SrcA ALUa_Fwd
00:E_V1
01:W_out
10:M_Data
HMUX_ALUb 将数据转发到ALUSrcBtemp端口 E_SrcB_temp ALUb_Fwd
E_V2
01:W_out
10:M_Data
HMUX_DM 将数据转发到DM_WD端口 M_WD DM_Fwd
0:M_V2
01: W_out
6. 在课上测试时,我们需要你现场实现新的指令,对于这些新的指令,你可能需要在原有的数据通路上做哪些扩展或修改?提示:你可以对指令进行分类,思考每一类指令可能修改或扩展哪些位置。
①cal_R:修改ALU模块,MCU模块
②cal_I:修改EXT模块,ALU模块,MCU模块
③shift:修改ALU模块,MCU模块,EXT模块
④shiftv:修改ALU模块,MCU模块
⑤load:修改MCU模块,修改DM模块或者在DM模块外加一个对于字节的处理模块
⑥store:修改MCU模块,修改DM模块或者在DM模块外加一个对于字节的处理模块
⑦B类:修改CMP模块,MCU模块,NPC模块,EXT模块
⑧J类:修改MCU模块,NPC模块
7. 简要描述你的译码器架构,并思考该架构的优势以及不足。
答:我的译码器采用分布式译码。
不足:分布式译码关键路径更长,速度较慢。
优势:译码信息模块化,不需要流水传递信号。

祝P5上机一切顺利!

11/15 P5第一次上机

失败了失败了失败了
失败是成功之母,下周一定能过!
总的来说还是和往年题一样,条件存储 + 链接条件跳转 + 计算,注意题目顺序吧

  1. 条件存储但是题目记不清楚了qaq,最难的放第一个了
  2. 原题,题目如下
    1
    跟同学交流还是在判断D级当前指令是bonall且符合条件,那么就把F_instr 变成nop
    maybe课下有bug或者哪里连错了?
    这次上机好慌乱,顶层模块有一个注释直接把下一句话干没了。还有不要急着提交,应该每个信号都连好,自己把数据通路走一遍看有没有问题。有一些信号没连上的错误然后一直提交评测也好搞心态。
    啊啊啊啊啊,判断相反数也写错了,应该是(r1 + r2 == 32'd0)&& (!((r1 == 32'h8000_0000)&&(r2 == 32'h8000_0000)))不能同时为32'h8000_0000写成都不能为32'h8000_0000,错的好离谱
  3. addoi
    一个算术指令GPR[base] + imm,然后取反得temp,输出temp原码(还是补码,忘记了qaq)
    1
    取反是取相反数不是按位取反啊啊啊啊啊
    黑底的设计稿图片课上看的很难受,而且信号也不明显
    要自己写简单的testbench,测了再交。不要着急提交。
    我今天晚上在干什么呀

11/22 P5第二次上机

实在惊险,20:58提交通过

  1. 保持良好的心态,不要慌乱
  2. 一定要写测试程序,只要暂停转发这些课下做好,课上只用检测该条指令的功能
  3. 愚蠢的错误实在太多:
    ①第一次上机不好好读RTL,根本不是题目要表达的意思。对着设计图,读好RTL,想好各种控制信号再动笔,一点一点改。
    ②if的条件语句的括号后面打了“;”
    ③r2[31]打成r2[1],写的时候不要着急,慢慢的
    ④忘记声明变量位宽,导致出现xxx
    ⑤信号没连上
  • Post title:P5
  • Post author:Coooookie
  • Create time:2023-01-02 20:30:05
  • Post link:https://coooookie0913.github.io/2023/01/02/P5/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.