118 lines
7.4 KiB
Markdown
118 lines
7.4 KiB
Markdown
# 五级流水线(带冒险和异常处理)
|
||
|
||
### 项目结构
|
||
|
||
```
|
||
multistage_pipeline
|
||
|-> data: IM和DM读入的地方
|
||
|-> figure: 设计图纸,我高兴时可能会写写使用说明
|
||
|-> Python: 一些加速生产的脚本
|
||
|-> mips_code: 用来测试的mips汇编源代码
|
||
|-> sim: 放激励文件的文件夹
|
||
|-> src
|
||
|-> Controller: 控制器
|
||
|-> DataPath: 数据通路
|
||
|-> Hazard: 旁路模块与冒险检测模块
|
||
|-> Pipe: 流水线寄存器
|
||
|-> Utils: 其余组件
|
||
|-> myCpu.v: 顶层模块
|
||
|
||
|-> README.md: Current
|
||
|-> .gitignore 上传文件过滤列表
|
||
```
|
||
|
||
### 设计图纸(2021.5.17更新)
|
||
|
||

|
||
|
||
|
||
|
||
---
|
||
|
||
## 日志
|
||
|
||
### 2021.5.17
|
||
|
||
从前两次的设计中,体会到一点:一个正确的,完备的思路对于一个不小的项目来说是多么的重要。因此,这次为了轻松愉悦的开发过程,我花了许久(6个小时左右)设计了最终带转发,冒险和异常检测的五级流水线图纸。并且吸取了部分DUAN学长的FPGA项目习惯。
|
||
|
||
祝成功!
|
||
|
||
|
||
### 2021.5.18
|
||
|
||
在实现乘除法指令(`MULT,DIV,MULTU,DIVU`)时,我发现了一个问题,那就是乘除法指令的输出是一个63位的数字,虽然实际上乘除法只是改变专用寄存器的值,但是我对专用寄存器的设计使得我每个时钟周期只能改变`$hi`和`$lo`中的某一个。我的电路无法在一个时钟周期内一次性改变`$hi`和`$lo`。总得满足我的乘除法指令吧?怎么办呢?我的做法比较简单粗暴,我为`ALU`增加了一个输出接口`prod`,这个接口值和MIPS32指令手册上描述乘法除法中的prod的含义一样,为了让它能够成功抵达`regfile`,并成功写入,我做出了如下的改变:
|
||
|
||
- `ALU`增加新的输出接口值`prod`,当然,相应的后续流水线寄存器中也会添加暂存prod的接口。
|
||
- 选择目的操作数寄存器编号的`mux1`增加一个选项:34,不同于0-33,34不代表任何一个寄存器的编号,而是告诉我们的regfile,你要拆分并读入流水线存入的prod值。
|
||
- `regfile`增加一个输入接口`MEM_WB_prod`,代表接受最后一个流水线寄存器的存储的prod值。然后在`regfile`内部,我增加了写寄存器的逻辑:增加一个34号,如果接受到的目的操作数编号(在我的设计中为`MEM_WB_mux1_out`)为34,则将prod拆成两块送给`$hi`和`$lo`。
|
||
|
||
> 是否有更好的解决方案呢?
|
||
|
||
### 2021.5.26
|
||
|
||
果然,在实现57条指令的过程中,我发现了一个问题,由于我对专用寄存器`$hi`和`$lo`的是走的专线,所以导致这两个家伙没有进入我的数据冒险系统中。也就是说,如果我的上一条指令改变了专用寄存器的值,而下一条指令又需要使用专用寄存器的值,那么就会发生错误,因为此时下一条指令拿到的`$hi`或者`$lo`并不是最新的。为了解决这个问题,我采用了简单粗暴的方法:为EX阶段的保存专用寄存器的线(`ID_EX_low_out`和`ID_EX_high_out`)增加旁路。需要注意的是,我的垃圾设计的缺点这个时候就体现出来了,因为存储最新的专用寄存器值得地方可以是专用寄存器专用线,也可以是prod,所以嘛。。。在写旁路时旁路单元输出旁路信号的逻辑会有一点点小的改变,旁路选择的选项也从原本的3个(EX_MEM阶段的源操作数值、MEM_WB阶段的源操作数值、原本的值,也就是ID_EX阶段源操作数的值)变为5个(增加了EX_MEM阶段的prod和MEM_WB阶段的prod),因此旁路单元输出的旁路信号的位数从原本的2位变成3位。
|
||
|
||
|
||
### 2021.5.26
|
||
|
||
中间事情真的很多,所以只能断断续续地更新了。在我的想法下,今天完成了57条指令的全数据通路和控制器的连接,接下来就只剩调试了。有时间我得将已经被我改得面目全非的设计图纸的visio原文件修改同步更新一下了。
|
||
|
||
### 2021.5.30
|
||
|
||
开始进行整体测试,今天先测试所有的R型指令,遇到了pc启动的问题,我将reset设置为了上升沿,并且将`pc.v`修改如下:
|
||
|
||
```verilog
|
||
// initial
|
||
always @(posedge reset) begin
|
||
pc_out <= initial_addr;
|
||
end
|
||
|
||
always @(posedge clock && reset == 1) begin
|
||
if (!OR1_out) // OR1_out represents stall or not
|
||
pc_out <= npc_out;
|
||
end
|
||
```
|
||
|
||
也就是将初始化和持续更新分开来。
|
||
|
||
除此之外,用来用来软堵塞pc的`OR1_out`和`OR2_out`一开始都是x,会使得pc在启动时不更新,所以需要对两个冒险模块初始化其输出信号,使得一开始输出的都是不堵塞的信号。
|
||
|
||
### 2021.5.31
|
||
|
||
5月的最后一天,我已经完成了所有R型指令的测试、旁路测试和冒险测试。debug过程中发现一个问题:和之前一样,尽量别写形如`posedge clock or posedge reset`,请把初始化的always块另外写,因为我这边默认是reset上升沿触发模块的初始化操作,但是如果你将reset信号与别的信号混在一起写,就会导致模块永远处于更新状态,对于我的`regfile`,这意味着,每当后续的上升沿来临时,寄存器堆中所有的寄存器都会清零。
|
||
|
||
**第二个值得记载的bug**就是有关branch类型指令的问题,在pro1和pro2中的branch信号中,我最终的目标地址是通过branch当前的相对位移`>>2`和基地址相加得到的,但是pro3在我的设计中,branch在ID阶段得到的`pc_add_out`是当前IF阶段的`pc_add_out`,而不是随着当前这条branch指令得到的。因此在使用branch跳转时使用的`pc_add_out`必须减四才能正确。
|
||
|
||
**第三个值得记载的bug**
|
||
|
||
对于跳转指令的堵塞,有一个很隐蔽的bug,在控制冒险时,当我们为了branch指令的操作数而软阻塞ID阶段和IF阶段的指令时,由于使用的是延迟槽的机制,虽然我们阻塞了ID和IF,但是判断是否冲刷的模块还是在正常工作。所以如果堵塞的branch指令是生效,是需要跳转的,那么在堵塞的第一个周期,IF_ID流水线寄存器中保存的ID阶段的指令就会被冲刷成空指令,这会使得第二个堵塞周期结束,电路回归正常时,branch会去解析空指令,从而使得pc变成0(这根据你提供的空指令的后16位决定,我使用的是`addi $t0, $zero, 0`,其后16位为0).所以有必要控制堵塞IF_ID流水线寄存器的信号和冲刷IF_ID流水线寄存器的信号的优先级:**当且仅当IF_ID流水线寄存器不被堵塞时,IF_ID才能执行冲刷的操作**。根据这样的认识,我们可以只修改代码逻辑而不添加额外的信号得修复这个bug:
|
||
|
||
```verilog
|
||
always @(posedge clock)
|
||
begin
|
||
if (OR4_out)
|
||
IF_ID_im_out = NOP; // use NOP to flush and the pc_add_out won't be used later, so we don't care about pc_add_out there
|
||
|
||
else if (!OR2_out) // update iff IF_ID_Write
|
||
begin
|
||
IF_ID_im_out = im_out;
|
||
IF_ID_pc_add_out = pc_add_out;
|
||
end
|
||
end
|
||
```
|
||
|
||
更改之后
|
||
|
||
```verilog
|
||
always @(posedge clock)
|
||
begin
|
||
if (!OR2_out) // update iff IF_ID_Write
|
||
begin
|
||
if (OR4_out)
|
||
IF_ID_im_out = NOP; // use NOP to flush and the pc_add_out won't be used later, so we don't care about pc_add_out there
|
||
else
|
||
IF_ID_im_out = im_out;
|
||
IF_ID_pc_add_out = pc_add_out;
|
||
end
|
||
end
|
||
``` |