体系结构

指令集: add, sub, ori, lw, sw, beq, lui, nop, jr, jal 端口:

信号方向描述
clkI时钟信号
resetI同步复位信号

基本模块

F 级

IFU

PC & NPC

  • Moore 状态机:
    • 下一状态:NPC
      • PC + 4
      • PC + 4 + Offset
      • Jump
    • 输出:PC
PC
  • 元件: 寄存器
  • 端口:
信号方向描述
clkI时钟信号
resetI同步复位信号
enI写使能信号, 阻塞时不可写入 NPC
NPC[31:0]I下一指令地址
PC[31:0]O取指令地址
NPC
  • 端口:

    信号方向描述
    F_PC[31:0]IF 级指令当地址
    ra[31:0]Ijr指令, 寄存器寻址, 跳转地址
    offset[15:0]I地址偏移量
    imm[25:0]IJ 型指令,相对 PC 寻址, 跳转地址
    NPCSel[1:0]I下一 PC 选择信号
    ZeroIrs 和 rt 相等标志(1=相等, 0=不等)
    PCPlus4[31:0]O当前指令地址+4
    NPC[31:0]O下一指令地址

由于延迟槽, 跳转指令在 D 级时, 决定跳转与否时, 后一条指令已经取出, 也就是 PC 已经加 4, 因此偏移量无需 PC + 4 + offset, 而是 F_PC + offset
同理, J 型指令, 需要的是 PC + 4 的高四位, 只需要 F_PC 的高四位即可
jr 指令要存储的是 PC + 8

  • NPCSel编码:

    NPCSel含义
    00顺序执行
    01J 型跳转
    10JR 寄存器跳转
    11分支跳转

IM

  • 元件: ROM

  • 地址范围: 0x00003000 ~ 0x00006FFF

  • 实际地址宽度:

    • 0x00006FFF - 0x00003000 + 1 = 0x3FFF + 1 = 0x4000
    • 0x4000 -> 16384Byte
    • 16384 / 4 = 4096 = 2 ** 12
  • 按照指令寻址: ROM_addr[11:0] = (PC - 0x00003000) >> 2

  • 端口:

    信号方向描述
    A[31:0]I取指令地址
    RD[31:0]O取出指令

FDReg

端口:

信号方向描述
clkI时钟信号
resetI同步复位信号
enI写入使能信号, 阻塞时无效, 不得进入 D 级
clrI清除信号, 阻塞时有效, 清除 F 级要进入 D 级中的信息, 插入气泡
F_instr[31:0]IF 级取出的指令
F_PCplus8[31:0]IF 级取出的指令对应的 PC + 8
D_instr[31:0]O传入 D 级的指令
D_PCplus8[31:0]O传入 D 级的指令对应的 PC + 8

D 级

RF

信号方向描述
clkI时钟信号
resetI同步复位信号
clrI清空当前错误分支预测
WEI写使能信号
A1[4:0]I地址输入信号,读出到 RD1
A2[4:0]I地址输入信号,读出到 RD2
A3[4:0]I地址输入信号,写入的目标寄存器
WD[31:0]I32 位数据输入信号
RD1[31:0]O输出 A1 指定的寄存器数据
RD2[31:0]O输出 A2 指定的寄存器数据

EXT

信号方向描述
imm[15:0]I指令中的立即数
Ext_opI扩展控制信号(0=无符号扩展,1=符号扩展)
Ext_out[31:0]O扩展后的结果

DEReg

信号方向描述
clkI时钟信号
resetI同步复位信号
clrI清除信号, 阻塞时有效, 清除 D 级要进入 E 级中的信息, 插入气泡
D_PCplus8[31:0]I本条指令对应的 PC + 8
D_instr[31:0]ID 级译码的指令
D_RD1[31:0]I从寄存器读出的 RD1 数据
D_RD2[31:0]I从寄存器读出的 RD2 数据
D_A3[4:0]I本条指令目的寄存器
D_ext[31:0]ID 级立即数扩展结果
E_PCplus8[31:0]O进入 E 级的 PC + 8
E_instr[31:0]O进入 E 级的指令编码
E_RD1[31:0]O进入 E 级的 RD1 数据
E_RD2[31:0]O进入 E 级的 RD2 数据
E_A3[4:0]O进入 E 级的目的寄存器
E_ext[31:0]O进入 E 级的扩展立即数结果

CMP

信号方向描述
inA[31:0]I比较数 A
inB[31:0]I比较数 B
cond[5:0]I比较类型
zeroO比较结果

E 级

ALU

ALUControl编码:

运算编码
ori0001
sll160101
add0110
sub0111

端口:

信号方向描述
SrcAI操作数 1
SrcBI操作数 2
ALUControl[3:0]I运算种类控制信号
Result[31:0]O运算结果

EMReg

信号方向描述
clkI时钟信号
resetI同步复位信号
E_PCplus8[31:0]I本条指令对应的 PC + 8
E_instr[31:0]IE 级执行的的指令
E_ALUResult[31:0]IE 级 ALU 产生的结果
E_RD2[31:0]I从寄存器读出的第二个操作数
E_A3[4:0]I本条指令的目的寄存器
M_PCplus8[31:0]O本条指令对应的 PC + 8
M_instr[31:0]OM 级执行的的指令
M_ALUResult[31:0]O传入 M 级的 ALU 计算结果
M_RD2[31:0]O传入 M 级的第二个操作数
M_A3[4:0]O传入 M 级的目的寄存器序号

M 级

DM

  • 元件: RAM, 读写双端口分离

  • 地址范围: 0x00000000 ~ 0x00002FFF

  • 端口:

    信号方向描述
    clkI时钟信号
    resetI同步复位信号
    WEI写使能信号
    A[31:0]I读写地址
    WD[31:0]I写入数据
    RD[31:0]O读出数据

MWReg

信号方向描述
clkI时钟信号
resetI同步复位信号
M_PCplus8[31:0]I本条指令对应的 PC + 8
M_instr[31:0]IM 级执行的的指令
M_ALUResult[31:0]I传入 M 级的, 本条指令的 ALU 计算结果
M_RD_Mem[31:0]IM 级读出的 DM 数据
M_A3[4:0]I本条指令的目的寄存器序号
W_instr[31:0]O传入 W 级的指令
W_PCplus8[31:0]O本条指令对应的 PC + 8
W_ALUResult[31:0]I传入 W 级的, 本条指令的 ALU 计算结果
W_RD_Mem[31:0]I传入 W 级的, 本条指令读出的 DM 数据
W_A3[4:0]I传入 W 级的,本条指令的目的寄存器序号

Controller

端口:

信号方向描述
Opcode[5:0]I指令的[31:26], op 部分
Funct[5:0]IR 指令[5:0], funct 部分
MemtoReg[1:0]O寄存器回写的数据来源(00=ALU 结果, 01=内存数据, 10=PCPlus8)
MemWriteODM 写使能信号
NPCSel[1:0]O下一 PC 选择信号
ALUSrcOALU 第二操作数选择(0=寄存器, 1=立即数)
RegDst[1:0]O寄存器写入目标(00=rt, 01=rd, 10=0x1F)
ExtOpO扩展方式(0=无符号, 1=有符号)
RegWriteO寄存器写使能信号
ALUControl[3:0]OALU 运算选择信号
t_rs[2:0]O距离需要 rs 寄存器的值的周期数
t_rt[2:0]O距离需要 rt 寄存器的值的周期数
t[2:0]O距离产生可用数值的周期数

指令

指令集: add, sub, ori, lw, sw, beq, lui, nop, jr, jal

  • 控制信号
指令信号
MemtoReg[1:0]MemWriteNPCSel[1:0]ALUSrcRegDst[1:0]RegWriteExtOpALUControl
R(jr excluded)0000000110根据funct
ori00000100100001(or)
lw01000100110110(add)
sw00100100010110(add)
beq00011000010111(sub)
lui00000100100101(sll16)
jr00010000000000
jal00001010100000

转发与阻塞:

  1. 考虑:
  • , 如果 WA 的话, 一定要检查一下.
  • 需不需要转发/被转发
  • 需不需要阻塞/被阻塞
  • 被阻塞怎么办
  • 关于写未定寄存器的访存类
    • 应该注意 E, M 级的转发与阻塞. 具体而言:
      • 在 W 级已经确认寄存器, 并且和其余数据合流了.
      • 而在 M 级到 W 级的时候要修正真正访问的寄存器.
      • 在 E 级不要阻塞不可能访问的寄存器, 在 M 级根据确定的寄存器决定阻塞与否.
    • assign M_A3_F = (M_opcode == OPCODE) ? NEW_A3_VALUE : M_A3;, 所有 M 级出发的转发(除 Plus8), 都用新寄存器地址
  • 关于阻塞:
    • 对方写的是 0 号寄存器吗?
    • 对方用的寄存器和我们写入的寄存器一样吗?
    • 对方会用到这个寄存器吗? (使用时间是不是 4'hf?)
    • 对方使用的时间是否晚于我们这条指令可以产生数据的时间?
  • 关于跳转并清空延迟槽:
    • 对于分支: 更改 CMP 模块,决定分支逻辑. 清空延迟操给一个 signal.
    • 对于链接: 更改 MIPS 模块,在 Plus8 转发相关判断增加这条指令的 opcode.
    • 对于清空延迟槽: 在 FDReg 增加一个FD_clr信号, 如果需要清空就激活.
    • 但必须注意和阻塞的配合: 当指令被阻塞在 D 级, 是不能允许 FDReg 被清零的, 否则指令会自我清理, 也就是: signal_F = (signal & (~stall)), FD_clr = signal_F
  • 关于未定写使能:
    • RegWritet 均无法由指令名获得.
    • 因此, 我们默认打开 RegWrite, 根据他正常写入的阶段编写t.
    • 为了应对不写入需要从 ALU 放出一个signal信号, 不满足写入置为 1. 这个信号随着指令流水到 W 级.
    • 在 W 级, signal 为 1 的时候, 将写使能关闭; 在 E,M,W 级, signal为 1 的时候, t设置为4'hf: assign W_WriteReg_F = (W_RegWrite & (~W_signal));, assign E_t = (E_signal) ? 4'hf : ...(M_t, W_t同理)
    • 或者: 在 E 级检测到signal为 1, 就把写入寄存器改为 0 号寄存器: assign E_A3_F = signal ? 5'b00000 : E_A3;
  • 一定注意加接口后, 在以下地方修改:
    • mips.v实例化接口修改
    • 模块本身接口修改
    • 模块本身内部修改
  1. 始末点:
  • 起点: DE 寄存器发出的 PC@D+8; EM 寄存器出来的 ALUResult, PC@D+8; W 级 GRF 写入前的 WD.
  • 终点: D 级 GRF 出来的 RD1, RD2; DE 寄存器出来, 进入 Mux 和 ALU 前的 SrcA, SrcB; M 级写入 DM 的 WD. 共 种路线(DE 同级转发不可能(-2); DE, EM 转发给 M 级不可能(-3)).
  1. 转发判断

    • T 值 令 D 级、E 级、M 级、W 级分别为 0 、1 、2 、3

      指令
      R(jr excluded)211
      ori21X
      lw31X
      swX12
      beqX00
      lui2XX
      jal0XX
      jrX0X
    • 转发要求:

      • 即:
      • 其中, 都是距离需要/可用的级别数

测评

关于 Editor: E:\\Microsoft\ VS\ Code\\Code.exe -r . -r -g $1:$2

start:
    # 1. 初始化
    lui  $1, 0x0000      # $1 = 0
    ori  $2, $0, 5       # $2 = 5
    ori  $3, $0, 10      # $3 = 10
 
    # 访存测试
    ori  $4, $0, 0x1234
    sw   $4, 100($1)     # Mem[100] = 0x1234
 
    # 初始化测试标志位为失败 (0xBAD),如果测试通过会被覆盖为 0x1
    ori  $30, $0, 0xBAD
    sw   $30, 20($1)     # Mem[20] 初始化
    sw   $30, 24($1)     # Mem[24] 初始化
    sw   $30, 28($1)     # Mem[28] 初始化
 
# Test 1: ALU 运算与转发
# 重点: 验证 E->E 转发,以及 W->M 转发
test_alu:
    add  $5, $2, $2      # $5 = 5 + 5 = 10 (0xA)
    sw   $5, 0($1)       # Mem[0] 应为 0x0000000A
                         # (测试: ADD结果在W级时,SW在M级能否拿到数据 -> W->M Fwd)
 
    sub  $6, $5, $2      # $6 = 10 - 5 = 5 (0x5)
                         # (测试: ADD结果在M级时,SUB在E级能否拿到 -> M->E Fwd)
    sw   $6, 4($1)       # Mem[4] 应为 0x00000005
 
    nop
    add  $7, $5, $3      # $7 = 10 + 10 = 20 (0x14)
                         # (测试: 间隔一条指令,W->E Fwd)
    sw   $7, 8($1)       # Mem[8] 应为 0x00000014
 
# Test 2: 访存暂停与转发
# 重点: LW 后的指令必须暂停 1 周期
test_load_use:
    lw   $8, 100($1)     # $8 = 0x1234
    add  $9, $8, $2      # $9 = 0x1234 + 5 = 0x1239
                         # 必须 STALL 否则 $9 计算结果会错
    sw   $9, 12($1)      # Mem[12] 应为 0x00001239
 
    lw   $10, 100($1)    # $10 = 0x1234
    nop                  # 插入气泡,无需 STALL
    sub  $11, $10, $2    # $11 = 0x1234 - 5 = 0x122F
    sw   $11, 16($1)     # Mem[16] 应为 0x0000122F
 
# Test 3: 分支指令
# 重点: BEQ 在 D 级能否正确拿到 E/M 级前推的数据
test_branch:
    # Case A: 算术指令后接 BEQ (E->D Forwarding)
    add  $12, $2, $2     # $12 = 10
    beq  $12, $3, branch_a_ok # 10 == 10? 跳转
    nop
    # Fail Path
    jal  branch_b_test
    nop
branch_a_ok:
    ori  $20, $0, 1
    sw   $20, 20($1)     # Mem[20] 应为 0x00000001
 
branch_b_test:
    # Case B: Load 指令后接 BEQ (Load-Branch Stall + Forwarding)
    lw   $13, 100($1)    # $13 = 0x1234
    ori  $14, $0, 0x1234 # $14 = 0x1234
    beq  $13, $14, branch_b_ok # 必须 STALL
    nop
    # Fail Path
    jal  test_jump
    nop
branch_b_ok:
    ori  $21, $0, 1
    sw   $21, 24($1)     # Mem[24] 应为 0x00000001
 
# Test 4: 跳转链接 (JAL & JR)
test_jump:
    jal  func_call       # $31 = PC + 8
    nop
    # Return Here
    ori  $22, $0, 1
    sw   $22, 28($1)     # Mem[28] 应为 0x00000001
    jal  end
    nop
 
func_call:
    # 测试 JR 的寄存器转发
    add  $23, $31, $0    # 将 RA 移动到 $23
    nop                  # 简单的延迟
    jr   $23             # 跳回
    nop
 
end:

思考题

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

    • 提前判断, 若分支依赖未准备好的寄存器,也会导致 stall 额外停顿.
     add r1, r3, r4    # r1 的值在 EX 才算出来
     beq r1, r2, L     # 但我们在 D 级就想判断 r1 == r2 -> 阻塞一周期
    • 如果我们还在 E 级判断, 那么届时可以直接使用 M E 的转发通路, 从而其实是比提前分支判断更快的(分支预测正确的前提下).
  2. 因为延迟槽的存在,对于 jal 等需要将指令地址写入寄存器的指令,要写回 PC + 8,请思考为什么这样设计?

    • 由于延迟槽存在,取指阶段 并且执行了槽指令,因此返回地址必须是延迟槽后的下一条指令,即 D_PC+8 才能正确返回。
  3. 我们要求大家所有转发数据都来源于流水寄存器而不能是功能部件(如 DM 、 ALU ),请思考为什么?

    • 如果从功能部件后面转发, 不能保证信号在需要的时间内到达, 那么接受转发的那一级必须等待转发来源信号稳定, 那么就要把自己的稳定时间加上那个部件的稳定时间, 使得每一级延迟延长, 周期变长, 运行效率变差.
  4. 我们为什么要使用 GPR 内部转发?该如何实现?

    • 写回和读出同周期发生时,可能读到旧值,因此需要内部转发确保读取到最新数据。
    • 我的设计方案里, 有 W 级向 D, E, M 级的转发通路(尤其是 D, 就是 GRF 内部的转发), 从而保证读到最新值.
  5. 我们转发时数据的需求者和供给者可能来源于哪些位置?共有哪些转发数据通路?

    • 起点: DE 寄存器发出的 PC@D+8; EM 寄存器出来的 ALUResult, PC@D+8; W 级 GRF 写入前的 WD.
    • 终点: D 级 GRF 出来的 RD1, RD2; DE 寄存器出来, 进入 Mux 和 ALU 前的 SrcA, SrcB; M 级写入 DM 的 WD. 共 种路线(DE 同级转发不可能(-2); DE, EM 转发给 M 级不可能(-3)).
  6. 在课上测试时,我们需要你现场实现新的指令,对于这些新的指令,你可能需要在原有的数据通路上做哪些扩展或修改?提示:你可以对指令进行分类,思考每一类指令可能修改或扩展哪些位置。

    • 算术/逻辑类: ALU 控制扩展, 可能修改转发判断.
    • 访存类: 扩展地址生成或 MEM 控制信号. 需要 stall 或提前转发(MEMEX). 地址来自 ALU,要保留正确的流水寄存器传递.
    • 跳转/分支类: 修改分支判断逻辑, 更新 PC 的多路选择. 需要考虑分支冒险. 若新分支类型依赖比较结果,可能新比较器. 形如 jal/jalr 要注意写回地址来源(D_PC+8 或 F_PC+4).
  7. 确定你的译码方式,简要描述你的译码器架构,并思考该架构的优势以及不足。

    • 我是指令驱动型.
    • 优点是比较清晰, 每条指令对应的各个信号, 并且可以清楚看到支持的每一条指令, 并且容易和设计表格对应. 并且在扩展时只需要增加指令, 不需要修改原有代码.
    • 缺点是指令集更大的时候, 过于冗长复杂.