流水线 CPU 的设计和实现
流水线 CPU 的设计和实现较为复杂,下面的描述可能较为粗略。如果想看非常具体的描述讲解,则请同学们仔细听老师上课讲解,同时也可以去看计算机组成与设计 硬件/软件接口(RISC-V,第 5 版,学校书店有)这本书第 4 章的相关内容,上面有非常详细的流水线 CPU 的设计(以及习题)。
下面还是以 addi 为例,重新回顾流水线 CPU 的设计方法。
流水线 CPU 中的大部分的控制信号是由 ID 段生成,一段一段传到后续阶段进行处理的。因此,在设计的时候,我们可以从 WB 阶段开始,倒着考虑。这样可以保证在设计信号的时候不会漏下信号。
老师上课讲以及书上都是正着的方式,这个可能会好理解一些,因为这是指令执行的时间顺序。我们在这里默认大家都已经对流水线有了一定的了解,如果还不太清楚流水线的工作原理的话,请先听老师讲解或看书学习。
WB 阶段
对于 addi 指令的 WB 阶段,需要做的是将 alu 计算得到的 result 写回寄存器中,因此 WB 阶段需要的信号如下:
- rf_wen = 1: 表示是否写寄存器(ID 段在解析指令后给出)
- rf_wdata = alu_result: 写寄存器的数据(EXE 段计算后得出)
- rf_waddr = rd: 写寄存器的编号(ID 段在解析指令后给出)
具体实现方式可以是将 mem_wb_reg 的对应信号直接引到 regfile 的写端口上。
MEM 阶段
对于 addi 指令的 MEM 阶段,不需要做任何事情,因此需要的信号如下:
- mem_en = 0: 表示是否有内存操作(ID 段在解析指令后给出)
EXE 阶段
对于 addi 指令的 EXE 阶段,需要生成立即数,同时在 ALU 中进行加法运算,因此需要的信号如下:
- rf_rdata_a/rf_rdata_b: 表示寄存器读取 rs1,rs2 地址的结果(ID 段在读取寄存器后给出)
- inst: 指令,用于生成立即数(IF 段在读取 SRAM 后给出)
- imm_type = I: 立即数种类(ID 段在解析指令后给出)
- alu_op = ADD: alu 运算的种类(ID 段在解析指令后给出)
- use_rs2 = false: 表示第二个 operand 是立即数还是 rs2 的读取结果(ID 段在解析指令后给出)
同时需要给出 rf_wdata = alu_result,以供 WB 阶段使用
ID 阶段
这个阶段对于所有指令都需要生成对应的控制信号,同时完成寄存器的读取。对于 addi 指令,需要的信号和对应的值如下:
- inst = inst
- rf_raddr_a = rs1 segment of instruction
- rf_raddr_b = rs2 segment of instruction
- imm_type = I
- alu_op = ADD
- use_rs2 = false
- mem_en = 0
- rf_wen = 1
- rf_waddr = rd segment of instruction
这些信号的含义在上面已经介绍过了,因此在这里不再重新说明。
注意到需要进行可能的冲突处理,因此,若位于 EXE/MEM/WB 阶段的指令要写寄存器,且 ID 阶段的指令需要读这个寄存器,我们则不应该让 ID 阶段的指令进入下一阶段 (EXE)。否则这里将会读到错误(旧)的寄存器数据。
为此我们需要插入气泡。这里给出一个较为简单的气泡实现方案:使用指令addi zero, zero, 0
填充流水线(输出的 inst 为32'h00000013
,再生成对应的控制信号) 即可。当然,你可以换成任意一个既不写寄存器也不访问内存的指令来填充。注意 zero 寄存器的特殊处理。
IF 阶段
这个阶段对于所有指令都需要进行 SRAM 的读取,从而获取对应的指令。
这里的实现相当于在实验 5 中实现的 wishbone master 的一部分。你需要根据拿到的 pc,访问内存,获取到对应的指令。如果忘记了 wishbone master 的实现方法,请重新回顾 lab5 的相关内容。
注意到 wishbone 请求无法在一个周期内完成,因此需要向流水线中插入气泡。
在 IF 阶段还要完成 pc 的更新,由于这里只介绍 addi 指令的实现,因此直接让pc <= pc + 4
即可。但由于还有其他的指令会修改 pc,因此我建议大家将 pc_mux 实现成一个单独的模块,通过控制信号控制 pc 的更新。
最后,在实现这些模块之后,在 top 中完成连线即可。
这样我们就完成了一个能执行 addi 指令的流水线 CPU 了。
总结
addi 指令需要的流水线信号如下表:
信号名 | 值 | 生成阶段 | 最后使用阶段 | 含义 |
---|---|---|---|---|
inst | 指令 | IF | EXE | 指令编码 |
rf_raddr_a | rs1 | ID | ID | 读取寄存器编号 |
rf_raddr_b | rs2 | ID | ID | 读取寄存器编号 |
rf_rdata_a | ?? | ID | EXE | 读取寄存器结果 |
rf_rdata_b | ?? | ID | EXE | 读取寄存器结果 |
imm_type | I | ID | EXE | 立即数种类 |
alu_op | ADD | ID | EXE | ALU 操作 |
use_rs2 | 0 | ID | EXE | 使用立即数/rs2 |
mem_en | 0 | ID | MEM | 是否在 MEM 段访存 |
rf_wen | 1 | ID | WB | 是否写寄存器 |
rf_waddr | rd | ID | WB | 写寄存器编号 |
rf_wdata | result | EXE | WB | 写寄存器数据 |
以上仅是一种对于 addi 指令可行的设计,同学们在实现时可以按照需要,自己设计对应的流水线信号。同时也可以自己调整功能所在的流水线段。