体系结构
指令集: add, sub, ori, lw, sw, beq, lui, nop, jr, jal
端口:
| 信号 | 方向 | 描述 |
|---|---|---|
clk | I | 时钟信号 |
reset | I | 同步复位信号 |
基本模块
F 级
IFU
PC & NPC
- Moore 状态机:
- 下一状态:NPC
- PC + 4
- PC + 4 + Offset
- Jump
- 输出:PC
- 下一状态:NPC
PC
- 元件: 寄存器
- 端口:
| 信号 | 方向 | 描述 |
|---|---|---|
clk | I | 时钟信号 |
reset | I | 同步复位信号 |
en | I | 写使能信号, 阻塞时不可写入 NPC |
NPC[31:0] | I | 下一指令地址 |
PC[31:0] | O | 取指令地址 |
NPC
-
端口:
信号 方向 描述 F_PC[31:0]I F 级指令当地址 ra[31:0]I jr指令, 寄存器寻址, 跳转地址offset[15:0]I 地址偏移量 imm[25:0]I J 型指令,相对 PC 寻址, 跳转地址 NPCSel[1:0]I 下一 PC 选择信号 ZeroI rs 和 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 = 0x40000x4000 -> 16384Byte16384 / 4 = 4096 = 2 ** 12
-
按照指令寻址:
ROM_addr[11:0] = (PC - 0x00003000) >> 2 -
端口:
信号 方向 描述 A[31:0]I 取指令地址 RD[31:0]O 取出指令
FDReg
端口:
| 信号 | 方向 | 描述 |
|---|---|---|
clk | I | 时钟信号 |
reset | I | 同步复位信号 |
en | I | 写入使能信号, 阻塞时无效, 不得进入 D 级 |
clr | I | 清除信号, 阻塞时有效, 清除 F 级要进入 D 级中的信息, 插入气泡 |
F_instr[31:0] | I | F 级取出的指令 |
F_PCplus8[31:0] | I | F 级取出的指令对应的 PC + 8 |
D_instr[31:0] | O | 传入 D 级的指令 |
D_PCplus8[31:0] | O | 传入 D 级的指令对应的 PC + 8 |
D 级
RF
| 信号 | 方向 | 描述 |
|---|---|---|
clk | I | 时钟信号 |
reset | I | 同步复位信号 |
clr | I | 清空当前错误分支预测 |
WE | I | 写使能信号 |
A1[4:0] | I | 地址输入信号,读出到 RD1 |
A2[4:0] | I | 地址输入信号,读出到 RD2 |
A3[4:0] | I | 地址输入信号,写入的目标寄存器 |
WD[31:0] | I | 32 位数据输入信号 |
RD1[31:0] | O | 输出 A1 指定的寄存器数据 |
RD2[31:0] | O | 输出 A2 指定的寄存器数据 |
EXT
| 信号 | 方向 | 描述 |
|---|---|---|
imm[15:0] | I | 指令中的立即数 |
Ext_op | I | 扩展控制信号(0=无符号扩展,1=符号扩展) |
Ext_out[31:0] | O | 扩展后的结果 |
DEReg
| 信号 | 方向 | 描述 |
|---|---|---|
clk | I | 时钟信号 |
reset | I | 同步复位信号 |
clr | I | 清除信号, 阻塞时有效, 清除 D 级要进入 E 级中的信息, 插入气泡 |
D_PCplus8[31:0] | I | 本条指令对应的 PC + 8 |
D_instr[31:0] | I | D 级译码的指令 |
D_RD1[31:0] | I | 从寄存器读出的 RD1 数据 |
D_RD2[31:0] | I | 从寄存器读出的 RD2 数据 |
D_A3[4:0] | I | 本条指令目的寄存器 |
D_ext[31:0] | I | D 级立即数扩展结果 |
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 | 比较类型 |
zero | O | 比较结果 |
E 级
ALU
ALUControl编码:
| 运算 | 编码 |
|---|---|
ori | 0001 |
sll16 | 0101 |
add | 0110 |
sub | 0111 |
端口:
| 信号 | 方向 | 描述 |
|---|---|---|
SrcA | I | 操作数 1 |
SrcB | I | 操作数 2 |
ALUControl[3:0] | I | 运算种类控制信号 |
Result[31:0] | O | 运算结果 |
EMReg
| 信号 | 方向 | 描述 |
|---|---|---|
clk | I | 时钟信号 |
reset | I | 同步复位信号 |
E_PCplus8[31:0] | I | 本条指令对应的 PC + 8 |
E_instr[31:0] | I | E 级执行的的指令 |
E_ALUResult[31:0] | I | E 级 ALU 产生的结果 |
E_RD2[31:0] | I | 从寄存器读出的第二个操作数 |
E_A3[4:0] | I | 本条指令的目的寄存器 |
M_PCplus8[31:0] | O | 本条指令对应的 PC + 8 |
M_instr[31:0] | O | M 级执行的的指令 |
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
| 信号 | 方向 | 描述 |
|---|---|---|
clk | I | 时钟信号 |
reset | I | 同步复位信号 |
M_PCplus8[31:0] | I | 本条指令对应的 PC + 8 |
M_instr[31:0] | I | M 级执行的的指令 |
M_ALUResult[31:0] | I | 传入 M 级的, 本条指令的 ALU 计算结果 |
M_RD_Mem[31:0] | I | M 级读出的 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] | I | R 指令的[5:0], funct 部分 |
MemtoReg[1:0] | O | 寄存器回写的数据来源(00=ALU 结果, 01=内存数据, 10=PCPlus8) |
MemWrite | O | DM 写使能信号 |
NPCSel[1:0] | O | 下一 PC 选择信号 |
ALUSrc | O | ALU 第二操作数选择(0=寄存器, 1=立即数) |
RegDst[1:0] | O | 寄存器写入目标(00=rt, 01=rd, 10=0x1F) |
ExtOp | O | 扩展方式(0=无符号, 1=有符号) |
RegWrite | O | 寄存器写使能信号 |
ALUControl[3:0] | O | ALU 运算选择信号 |
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] | MemWrite | NPCSel[1:0] | ALUSrc | RegDst[1:0] | RegWrite | ExtOp | ALUControl | |
R(jr excluded) | 00 | 0 | 00 | 0 | 01 | 1 | 0 | 根据funct |
ori | 00 | 0 | 00 | 1 | 00 | 1 | 0 | 0001(or) |
lw | 01 | 0 | 00 | 1 | 00 | 1 | 1 | 0110(add) |
sw | 00 | 1 | 00 | 1 | 00 | 0 | 1 | 0110(add) |
beq | 00 | 0 | 11 | 0 | 00 | 0 | 1 | 0111(sub) |
lui | 00 | 0 | 00 | 1 | 00 | 1 | 0 | 0101(sll16) |
jr | 00 | 0 | 10 | 0 | 00 | 0 | 0 | 0000 |
jal | 00 | 0 | 01 | 0 | 10 | 1 | 0 | 0000 |
转发与阻塞:
- 考虑:
- , 如果 WA 的话, 一定要检查一下.
- 需不需要转发/被转发
- 需不需要阻塞/被阻塞
- 被阻塞怎么办
- 关于写未定寄存器的访存类
- 应该注意 E, M 级的转发与阻塞. 具体而言:
- 在 W 级已经确认寄存器, 并且和其余数据合流了.
- 而在 M 级到 W 级的时候要修正真正访问的寄存器.
- 在 E 级不要阻塞不可能访问的寄存器, 在 M 级根据确定的寄存器决定阻塞与否.
assign M_A3_F = (M_opcode == OPCODE) ? NEW_A3_VALUE : M_A3;, 所有 M 级出发的转发(除 Plus8), 都用新寄存器地址
- 应该注意 E, M 级的转发与阻塞. 具体而言:
- 关于阻塞:
- 对方写的是 0 号寄存器吗?
- 对方用的寄存器和我们写入的寄存器一样吗?
- 对方会用到这个寄存器吗? (使用时间是不是
4'hf?) - 对方使用的时间是否晚于我们这条指令可以产生数据的时间?
- 关于跳转并清空延迟槽:
- 对于分支: 更改 CMP 模块,决定分支逻辑. 清空延迟操给一个 signal.
- 对于链接: 更改 MIPS 模块,在 Plus8 转发相关判断增加这条指令的 opcode.
- 对于清空延迟槽: 在 FDReg 增加一个
FD_clr信号, 如果需要清空就激活. - 但必须注意和阻塞的配合: 当指令被阻塞在 D 级, 是不能允许 FDReg 被清零的, 否则指令会自我清理, 也就是:
signal_F = (signal & (~stall)),FD_clr = signal_F
- 关于未定写使能:
RegWrite和t均无法由指令名获得.- 因此, 我们默认打开
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实例化接口修改- 模块本身接口修改
- 模块本身内部修改
- 始末点:
- 起点: 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)).
-
转发判断
-
T 值 令 D 级、E 级、M 级、W 级分别为 0 、1 、2 、3
指令 R( jrexcluded)2 1 1 ori2 1 X lw3 1 X swX 1 2 beqX 0 0 lui2 X X jal0 X X jrX 0 X -
转发要求:
- 即:
- 其中, 都是距离需要/可用的级别数
-
测评
关于 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:思考题
-
我们使用提前分支判断的方法尽早产生结果来减少因不确定而带来的开销,但实际上这种方法并非总能提高效率,请从流水线冒险的角度思考其原因并给出一个指令序列的例子。
- 提前判断, 若分支依赖未准备好的寄存器,也会导致 stall 额外停顿.
add r1, r3, r4 # r1 的值在 EX 才算出来 beq r1, r2, L # 但我们在 D 级就想判断 r1 == r2 -> 阻塞一周期- 如果我们还在 E 级判断, 那么届时可以直接使用 M → E 的转发通路, 从而其实是比提前分支判断更快的(分支预测正确的前提下).
-
因为延迟槽的存在,对于 jal 等需要将指令地址写入寄存器的指令,要写回 PC + 8,请思考为什么这样设计?
- 由于延迟槽存在,取指阶段 并且执行了槽指令,因此返回地址必须是延迟槽后的下一条指令,即 D_PC+8 才能正确返回。
-
我们要求大家所有转发数据都来源于流水寄存器而不能是功能部件(如 DM 、 ALU ),请思考为什么?
- 如果从功能部件后面转发, 不能保证信号在需要的时间内到达, 那么接受转发的那一级必须等待转发来源信号稳定, 那么就要把自己的稳定时间加上那个部件的稳定时间, 使得每一级延迟延长, 周期变长, 运行效率变差.
-
我们为什么要使用 GPR 内部转发?该如何实现?
- 写回和读出同周期发生时,可能读到旧值,因此需要内部转发确保读取到最新数据。
- 我的设计方案里, 有 W 级向 D, E, M 级的转发通路(尤其是 D, 就是 GRF 内部的转发), 从而保证读到最新值.
-
我们转发时数据的需求者和供给者可能来源于哪些位置?共有哪些转发数据通路?
- 起点: 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)).
-
在课上测试时,我们需要你现场实现新的指令,对于这些新的指令,你可能需要在原有的数据通路上做哪些扩展或修改?提示:你可以对指令进行分类,思考每一类指令可能修改或扩展哪些位置。
- 算术/逻辑类: ALU 控制扩展, 可能修改转发判断.
- 访存类: 扩展地址生成或 MEM 控制信号. 需要 stall 或提前转发(MEM→EX). 地址来自 ALU,要保留正确的流水寄存器传递.
- 跳转/分支类: 修改分支判断逻辑, 更新 PC 的多路选择. 需要考虑分支冒险. 若新分支类型依赖比较结果,可能新比较器. 形如 jal/jalr 要注意写回地址来源(D_PC+8 或 F_PC+4).
-
确定你的译码方式,简要描述你的译码器架构,并思考该架构的优势以及不足。
- 我是指令驱动型.
- 优点是比较清晰, 每条指令对应的各个信号, 并且可以清楚看到支持的每一条指令, 并且容易和设计表格对应. 并且在扩展时只需要增加指令, 不需要修改原有代码.
- 缺点是指令集更大的时候, 过于冗长复杂.