五级流水线 MISP CPU Verilog Design 与 hazard 处理

数字电路和计算机组成原理的课程设计。

在 GitHub 阅读源代码:bazingaterry/Mipu

一个 16bit 的精简指令集的 CPU 简介

1

CPU 的状态转移图,只有当 Enable = 1 && start = 1 时,CPU 才会运行。

2

如图为此次设计的 CPU 架构图,为五级流水线,分为 IF、ID、EX、MEM、WB 五个阶段。 iaddr 为指令内存的地址,idatain 为指令内存根据地址取出的指令。daddr 为数据内存的地址, ddataout 为写入到数据内存的数据,ddatain 为数据内存根据地址取出的数据,dwe 为写入使能 信号,高电平代表写入,低电平为读取。

操作指令通过 idir、exir、memir、wbir 一级一级传下去。

3

IF:计算下一条指令的地址 PC,并从存储器读取当 前 PC 指向的指令,写入 idir。若是跳转指令,从 memir 和 flag 判断是否需要跳转,从 regC 读取地址;其余 指令均是顺序执行,即每一周期 PC + 1。而在本次设计 中,对 JUMP 指令做了优化,JUMP 指令在 ID 阶段便 可以进行无条件跳转,所以 idir 也回接入回 MUX 中。

4

ID:对指令进行译码,从通用寄存器中取出源操作 数,并根据指令将数据传给 regA 和 regB。其中 regA 的数据来自通用寄存器,regB 的数据来自指令 (立即数)或是通用寄存器。若是 STORE 指令,则将数据内存写入地址传给 smdr。

5

EX:若当指令是运算类指令时执行运算,regA 和 regB 传入 ALU 模 块,并且输出 ALUo 给 reg_C 并处理 ZF、NF、CF,若当指令是转移类指令时进行有效地址计算并将地址传到 PC。其中 ALU 是组合逻辑,根据指令计算结果。若是 STORE 指令,则将 smdr 传给 smdr1。

6

MEM:若是 STORE 指令或者是 LOAD 指令则 从内存储存或读取数据。数据内存的时钟必须快于 CPU 时钟,即在 CPU 看来数据内存是可以瞬间读写的,否则流水线需要多加一个流程。

7

WB:将 regC1 数据写回到对应的通用寄存器,regC1 来自 reg_C 或者是数据内存。

Hazard

hazard 的产生下面用 add 指令和 load 指令举例:

  1. Data hazards
  2. Control hazards
Data hazards
不涉及到 LOAD 指令的 hazard
add gr0 gr1 gr2
add gr4 gr0 gr1

最简单的例子,当第二行指令执行到 ID 阶段,第一行代码才执行到 EX 阶段,此时第一行代码的运算结果还没有写入到 gr0,但是第二行代码会从 gr0 读取,这样数据就出错了。

解决办法:加判断直接从 ALUo 读数据。

同样地,其他的情况也可以这样解决。这里有一个小坑就是,判断从哪里读取是有顺序的,一般是从最近的指令开始判断,发现 hazard 就直接 data forward,不要判断下去了。

涉及到 LOAD 的 hazard
load gr0 gr1 val
add gr4 gr0 gr1

按着上面的思路,还是需要 data forward。结果发现,add 到 ID 的阶段,load 才到 ex 阶段,还未从内存读出数据,没法 forward。

解决办法:在 IF 阶段,发现这类冲突,pc 不要往下跳,保持不动,idir 全为 0,等同于插入一个 nop 指令。即变为 load nop add,下一时钟周期直接从 ddatain 读取。

同样地,这里也需要注意刚才提到的判断顺序。即先判断 ex 再判断 mem、wb。

Control hazards
JUMP 指令

无脑直接跳,没坑没难度。

其余跳转指令

其余跳转指令需要 EX 结束,得到运算结果,才能决定需不需要跳转,此时跳转指令已经在 MEM 阶段。此时 PC 按顺序取址,仍有两个指令进入了 ID 和 EX 阶段,如果发生跳转,这两条指令仍然会在流水线继续运行,这样可能会修改到寄存器或者数据内存的值。

解决办法有两种:

  • 确定需要跳转时,将 ID 和 EX 里面的指清零掉,即改为 NOP 指令。例如即将 ADD -> ADD -> BN 改为 NOP -> NOP -> BN。由于未到 MEN 和 WB 阶段,无用的指令是不会影响数据的。
  • IF 读取到可能需要跳转的指令的时,IF 模块忽略 i_datain 读取到的指令,强制在流水线插入两个 NOP,PC 不动。若跳转指令进行结束 EX 阶段,运算结果需要跳转,则 PC 跳转,流水线继续运行;若不需要跳转,则 PC 继续 +1,此时流水线插入了三个 NOP。
  • 分支预测

第一种方法是还没有上 Jun Wang 大大的课之前想出来的,第二种方法是 Jun Wang 大大课堂上介绍的,第三种方法是 Jun Wang 大大让我们开开眼界的。

言归正传,第三种方法效率是最高的,它需要一次读取多条指令,但对整体架构改动太大,超出讨论(能力)范围。对比前两种方法,当都需要跳转的时候,效率是一样的,因为都是跳转后插进了三个 NOP。但是当不需要跳转的时候,第一种方法就有效率优势了,因为没有插进去 NOP,第二种仍然插进去三个 NOP。

同样地,这里也会有优先级的问题,在 IF 判断跳转的时候,如果 MEM 里面的 JMPR、BNZ 等需要判断的跳转指令需要跳,id_ir 里面的 JUMP 也需要跳,此时 MEM 的优先级比 ID 高。因为 JUMP 是在后面,如果前面的跳转指令需要跳转,JUMP 就不需要运行。

Instruction Memory 和 Data Memory

在跑测试的时候,instruction memory 可以写成 ROM 的形式,为逻辑电路。data memory 由于要进行读写操作,所以要设计成时序逻辑。而且读取和写入都是一个是时钟内完成的,所以 data memory 的时钟频率要比 CPU 的要高,否则读写需要增加一级流水线。