在本页中,我们将对实验中的六级流水线进行介绍,以方便大家进行实验。

1 流水线概述

流水线数据通路如下:
基于BRAM的RISCV六级流水线设计 - 图1

整个流水线是基于BRAM的哈佛架构流水线处理器。主要分为以下几个流水级:

  • IF1:取指第一阶段,pc从这里发起取指请求,并进行分支预测(目前流水线是静态预测不跳转)
  • IF2:取指第二阶段,得到ICache中的指令
  • ID:译码,得到指令的操作数、操作类型、分支类型、访存类型等信息,同时读取寄存器堆的两个源操作数。
  • EX:执行,根据指令类型进行运算,同时对分支情况进行核验。这一级也会发起对于DCache的访存请求。
  • MEM:访存,获取DCache中读取的数据,并通过地址和访存类型对数据进行移位处理。
  • WB:写回,将EX和MEM阶段的结果写回寄存器堆。

2 流水线设计详解

2.1 IF1

IF1 阶段是取指第一阶段,它的主要任务是从 PC 中取出指令地址,并进行分支预测。

由于 BRAM 的读是同步的,因此对 ICache 的访问需要在这一级发起,并在下一流水段获取到指令。

输入 Cache 的 flush 和 stall 信号
在 IF1 阶段,我们需要将 IF1-IF2 段间寄存器的 flush 和 stall 信号传递给 ICache。这两个信号的含义如下:

  • flush:表示当前指令无效,需要 ICache 送出一条 nop 指令。
  • stall:表示当前指令需要停顿,Cache 应保持当前输出。
    在单周期 CPU 中,之所以不需要这两个信号,是因为 DRAM 的读取是异步的,只要保持地址不变,就可以保持当前输出。但是在 BRAM 中,读取是同步的,因此需要这两个信号来控制 Cache 的输出。请阅读 ICache 的代码,了解这两个信号是如何使用的。
    DCache 并没有这两个信号,因为 EX-LS 寄存器在当前阶段不可能被停顿或冲刷。但在之后的设计中,随着异常、停顿的加入,这两个信号会被使用。

2.2 IF2

IF2 阶段是取指第二阶段,它没有任何计算,只是将ICache中的指令送到IF2-ID寄存器。

2.3 ID

ID 阶段是译码阶段,它的主要任务是译码指令,得到指令的操作数、操作类型、分支类型、访存类型等信息,同时读取寄存器堆的两个源操作数。 译码得出的一些信号解释如下:

  • mem_access:5 位宽信号,[2:0] 位表示访存的长度(是单次访问字节数的对数),[3] 位为真表示当前操作是读操作,[4] 位为真表示当前操作是写操作。
  • br_type:5 位宽信号,[4] 位为真表示当前指令是一条跳转或分支指令。[3] 位用来区分 jal/jalr 和分支指令,[2] 位用来区分 jal 和 jalr。同时我们也复用了第 [2] 位,如果当前指令是分支指令,则 [2:0] 位表示当前指令的分支类型。
  • rf_we:寄存器写使能。如果当前目的寄存器是 x0,则该信号直接置 0,以简化后续流水线对于相关的处理。
  • alu_op:ALU 操作类型,用来区分当前指令的操作类型,详见 config.sv。
  • alu_rs1_sel/alu_rs2_sel:用来区分 ALU 操作数的来源,详见 config.sv。
  • wb_sel:判断当前指令写回操作数来自于 ALU 还是访存,详见 config.sv。

2.4 EX

EX 阶段是执行阶段,它的主要任务是根据指令类型进行运算,同时对分支情况进行核验。此外,这一级也会发起对于 DCache 的访存请求。

Branch 模块会将是否跳转(jump)和跳转地址(jump_target)送到 Hazard 模块统一进行处理。

ALU 的源操作数将从若干输入选择:

  • src1:有 0, pc, rf_rdata1 三个选项,其中 rf_rdata1 可能通过前递从 LS/WB 阶段获取。
    0:表示 ALU 的左操作数为 0。这是为了支持 lui 指令。
    pc:表示 ALU 的左操作数为 pc。这是为了支持 jal 和 jalr 指令。
    rf_rdata1:表示 ALU 的左操作数为从寄存器堆中读取的数据。

  • src2:有 4, imm, rf_rdata2 三个选项,其中 rf_rdata2 可能通过前递从 LS/WB 阶段获取。
    4:表示 ALU 的右操作数为 4。这是为了支持 jal 和 jalr 指令。
    imm:表示 ALU 的右操作数为立即数。
    rf_rdata2:表示 ALU 的右操作数为从寄存器堆中读取的数据。

乘除法和取模运算的处理
在 ALU 中,我们直接使用了 *, /, % 来处理乘除法和取模运算,这是因为我们的整个处理器只在仿真环境下运行,乘除法和取模运算完成前时钟不会变化。如果在实际的处理器中,我们需要使用单独的乘法器和除法器来完成这些运算。

2.5 LS

LS 阶段是访存阶段,它的主要任务是获取 DCache 中读取的数据,并通过地址和访存类型对数据进行移位处理。

移位操作是基于访存类型的,针对不同的访存类型和字内地址偏移,对数据进行不同的移位操作。例如,对于一个字内偏移为 2 的半字访存,我们需要将数据左移 16 位,这样才能保证数据在寄存器中的位置正确。

2.6 WB

WB 阶段是写回阶段,它通过 ID 段生成的 wb_sel 信号,判断当前指令写回操作数来自于 ALU 还是访存,同时将写回的数据送入寄存器堆。

这个阶段也会将 commit_wb 信号送出,用来通知 Difftest 模块,当前指令已经提交。commit 信号在 IF1-IF2 段间寄存器前恒置为有效,之后随着流水线向后传递,当 flush 信号为真时会随着段间寄存器被清空。一旦 commit_wb 为有效,则表示当前指令对寄存器堆的状态改写将在下个上升沿之后可以被观测到。

2.7 Hazard

Hazard 模块是流水线中的一个重要模块,它的主要任务是处理流水线的停顿和分支预测错误,产生各个段间寄存器和 PC 的 stall 和 flush 信号。 目前 Hazard 模块可以处理以下冲突:

  • 前递:在 EX 阶段,如果当前指令需要使用到 MEM 阶段或者 WB 阶段的结果,那么就需要前递。前递的信号是 forward1_en 和 forward2_en,分别表示是否需要前递到第一个源操作数和第二个源操作数。
  • 分支预测错误:由于我们使用了静态不跳转分支预测,因此 EX 阶段一旦产生了跳转信号,分支预测就是错误的,需要进行清空流水线的操作。这个操作是通过 flush 信号实现的。
  • Load-use 冲突:在 ID 阶段,如果当前指令使用了当前 EX 阶段中 load 指令的结果,那么就需要停顿。