BUAA-CO P5设计文档
设计草稿
根据P4CPU设计,将CPU划分为F,D,E,M,W五级。
前置准备
由于延迟槽的存在,需将mips主模块中的PCplus4改为PCplus8.
模块层面
Controller
为了使每一级都能及时读取Controller传递出的信号,可以在每一级都设置一个单独的Controller模块。
同时,在Controller模块中定义每个指令的t_rs,t_rt,t数值
| 指令 | trs | trt | t |
|---|---|---|---|
| add | 1 | 1 | 2 |
| sub | 1 | 1 | 2 |
| ori | 1 | F | 2 |
| lui | F | F | 2 |
| lw | 1 | F | 3 |
| sw | 1 | 2 | F |
| beq | 0 | 0 | F |
| jal | F | F | 0 |
| jr | 0 | F | F |
| nop |
F表示不需要的信号,在Verilog中定义为4’hf.
添加流水级之间的寄存器
-
FDreg
- Instr
- PC/PCplus8
-
DEreg
- Instr及Controller信号
- PC/PCplus8
- RD1/RD2(ALU中用到)
- A3
- ext32
-
EMreg
- Instr及Controller信号
- PC/PCplus8
- ALUres
- RD2(DM中用到)
- A3
-
MWreg
- Instr及Controller信号
- PC/PCplus8
- ALUres
- A3
- DMdata
为了便于进行阻塞和转发的管理,还要为每一个中间寄存器加上en信号和reset信号:由D_stall信号调控,D_stall == 1时,PC_en = 0且FD_en = 0且DE_clear = 0,用于模拟阻塞,将PC和F中指令拦住,并将D到E的指令置nop。(D_stall如何调控下文再谈)
PC修改
- 加入使能信号
PC_en
NPC修改
- 将输入
PC改为F_PC - 相对跳转指令不需再 + 4(F_PC本身就等于PC + 4了)
ALU & CMP
此前相对跳转的指令在E级才能出结果,实在太晚了,现在将ALU其中的部分移动到D级的新模块——CMP中。
主模块
导线
- F级
- PC/NPC/PCplus8
- Instr
- D级
- Controller信号
- trs,trt,t
- Instr及其分线
- GRF(A1,A2,A3,RD1,RD2)
- EXT(ext32)
- CMP(zero)
- F to D
- E级
- PC/PCplus8
- Controller信号
- trs,trt,t
- Instr及其分线
- ALU(inputA,inputB,ALUres)
- D to E
- M级
- PC/PCplus8
- Controller信号
- trs,trt,t
- Instr及其分线
- DM(MemAddr,MemData,DMdata)
- E to M
- W级
- PC/PCplus8
- Controller信号
- trs,trt,t
- Instr及其分线
- GRF(inputA,inputB,ALUres)
- M to W
转发与阻塞导线
-
十五条通路:
- 起点:E_PCplus8 , M_PCplus8 , ALUres , DMdata
- 终点:D_RD1 , D_RD2 , E_RD1 , E_RD2 , MemAddr
- 去除五条同级&向后转发的
-
五个修正值:
- D_fixedRD1
- D_fixedRD2
- E_fixedRD1
- E_fixedRD2
- M_fixedRD2
-
判断转发起点是否为PCplus8
- E_isPCplus8
- M_isPCplus8
-
tuse & tnew (D,E,M,W)
-
en & clear & D_stall
导入模块
- PC
- NPC
- IM
- FDreg,DEreg,EMreg,MWreg
- [D,E,M,W]Controller
- GRF
- EXT
- CMP
- ALU
- DM
wire变量的赋值
大体与P4相同:
1 | assign D_A1 = D_rs; |
(仅列出部分关键的赋值)
转发与阻塞
1 | //优先级 E > M > W |
清空延迟槽:FDclear = (D_opcode == 6'bXXXXXX & !D_zero & !D_stall) ? 1'b1 : 1'b0
测试方案
用了往届学长博客中的MIPS自动生成评测机进行测试。
另手动构造了一些特殊数据,如:$0寄存器,接近16位和32位立即数范围边缘的数,向前,自身,向后跳转等。
1 | # LUI, ORI |
执行 $8 的计算时:观察 $7 的结果是否直接从 ALU 的输出(或者流水线寄存器)“旁路”到了 $8 的 ALU 输入端。如果不转发,$8 可能会算错。
执行 $15 的计算时:观察 PC 是否在 add $15… 这一行保持不变一个周期(或者插入了一个 nop 气泡),等待 $14 从数据存储器读出来。
执行 beq $2, $2 时:观察下一条指令是否被清除(Flush)或者是否正确执行了延迟槽指令(取决于你的 CPU 是否实现了延迟槽)。
执行 jr $31 时:观察 PC 是否正确跳转回了 jal 指令的下下条地址。
思考题
-
我们使用提前分支判断的方法尽早产生结果来减少因不确定而带来的开销,但实际上这种方法并非总能提高效率,请从流水线冒险的角度思考其原因并给出一个指令序列的例子。
- 提前分支判断可能引发控制冒险,当分支预测错误时,流水线中已取出的后续指令需要 flush,导致气泡产生,反而降低效率。
1
2
3
4beq $t0, $t1, label
add $t2, $t3, $t4
sub $t5, $t6, $t7
label: and $t8, $t9, $t10 -
因为延迟槽的存在,对于 jal 等需要将指令地址写入寄存器的指令,要写回 PC + 8,请思考为什么这样设计?
- 在 MIPS 流水线中,jal指令执行时,延迟槽的指令会被执行,jal指令的下下条指令地址是PC + 8。
-
我们要求所有转发数据都来源于流水寄存器而不能是功能部件(如 DM、ALU),请思考为什么?
- 流水寄存器保存了每个阶段的稳定数据。功能部件在工作时数据是动态变化的,若从功能部件转发,无法保证数据的稳定性和可预测性,会导致数据冲突或错误。
-
我们为什么要使用 GPR 内部转发?该如何实现?
- 原因:当寄存器的写操作和读操作在同一周期发生时,避免数据冲突,保证读取到的是最新写入的数据。
- 实现:增加内部转发通路,当检测到同一周期内有对同一寄存器的写和读操作时,将写数据直接转发给读端口。
-
我们转发时数据的需求者和供给者可能来源于哪些位置?共有哪些转发数据通路?
- 十五条通路:
- 起点:E_PCplus8 , M_PCplus8 , ALUres , DMdata
- 终点:D_RD1 , D_RD2 , E_RD1 , E_RD2 , MemAddr
- 去除五条同级&向后转发的
- 十五条通路:
-
在课上测试时,我们需要你现场实现新的指令,对于这些新的指令,你可能需要在原有的数据通路上做哪些扩展或修改?提示:你可以对指令进行分类,思考每一类指令可能修改或扩展哪些位置。
- ALU & EXT 等功能模块中的计算逻辑
- 定义新的trs,trt,t
- Controller中添加新的信号
- 添加新的阻塞信号
- 爆改通路,比如将DM中数据接到寄存器的选择等
-
简要描述你的译码器架构,并思考该架构的优势以及不足。
- 我使用控制信号驱动型,代码量小但可读性差,不易于bug排查

![[炽吾生平]初雪记](/img/cover_xue.jpg)