跳转至

流水线 CPU 的设计与实现 - 气泡和冲刷流水线

在设计流水线 CPU 时,一定会遇到冲突的问题。有时需要向流水线中插入气泡,有时需要冲刷掉流水线的部分阶段,除去错误的指令。

另一方面,在有 Wishbone 总线存在的情况下,无法在单周期内完成访存,一条指令可能要在 IF 段和 MEM 段停留多个周期,这时我们也需要加入气泡来填满流水线。

本节将会提出要考虑的问题以及对这些问题的提示,同学们在设计时可以参考这些思路。

简单情况下的 stall 和 flush 设计

我们先考虑在访存只需要一个周期时的流水线行为。

对于每个流水线段寄存器我们都需要增加以下两个输入:

  • stall_i 为 1 时,表示下个周期将忽略输入,维持输出不变
  • bubble_i 为 1 时,表示下个周期清空该寄存器(变为气泡),输出也为气泡

一段伪代码如下:

// note that outputs are always bound to the regs
always_ff @(posedge clk_i) begin
    if(rst_i) begin
        //reset logics
    end else if(stall_i) begin
        //do nothing
    end else if(bubble_i) begin
        //change regs to bubble
    end else begin
        //change regs to input
    end
end

对于每个流水线段(组合逻辑),我们需要增加以下输出信号:

  • flush_o 为 1 时,表示这个流水线段发出请求,要将所有 之前 的流水线段清空
  • stall_o 为 1 时,表示这个流水线段发出请求,要在这个阶段阻塞流水线

我们观察下面几种情况时,对应信号的值:

  1. ID 阶段发现数据冲突,需要阻塞流水线

    假设在周期 0 发现冲突,此时流水线中指令如下:

    IF ID EXE MEM WB
    A B C D E

    则周期 1 时,流水线中指令应该如下表所示(bub 表示气泡):

    IF ID EXE MEM WB
    A B bub C D

    此时应该为 ID 段在周期 0 发出 stall_o 请求,流水线寄存器的控制信号一个合理的输入如下表:

    寄存器 stall_i bubble_i
    if_id 1 0
    id_ex 0 1
    ex_mem 0 0
    mem_wb 0 0
  2. EXE 需要跳转,需要冲刷流水线

    假设在周期 0 发现冲突,此时流水线中指令如下:

    IF ID EXE MEM WB
    A B C D E

    则周期 1 时,流水线中指令应该如下表所示(bub 表示气泡):

    IF ID EXE MEM WB
    F bub bub C D

    此时应该为 EXE 段在周期 0 发出 flush_o 请求,流水线寄存器的控制信号一个合理的输入如下表:

    寄存器 stall_i bubble_i
    if_id 0 1
    id_ex 0 1
    ex_mem 0 0
    mem_wb 0 0
  3. 上面两种情况同时发生

    此时应该为 ID 段在周期 0 发出 stall_o 请求,同时 EXE 段在周期 0 发出 flush_o 请求。

    流水线寄存器的控制信号一个合理的输入如下表:

    寄存器 stall_i bubble_i
    if_id 0 1
    id_ex 0 1
    ex_mem 0 0
    mem_wb 0 0

大家可以先考虑所有可能的请求情况,然后尝试实现一个单元来根据每段请求,向流水线寄存器发送对应的控制信号。

复杂情况下的 stall 和 flush 设计

注意在引入 Wishbone 总线之后,IF 和 MEM 这两个阶段都可能需要多个周期才能够完成,这让事情变得更加复杂。

跨时钟域

虽然直观地来看,可以使用一个频率更高的时钟驱动 Wishbone 总线,以保证流水线的 MEM 阶段是“单周期”的。 但这样的设计,在跨越时钟域的位置会造成严重的、可能无法被检测出的亚稳态时序问题。

因此,在实验中,除非对自己做的事情有充分的了解和学习(实际上已经超过了本课程的范围),否则不要使用多个时钟驱动你的设计。

比如在 EXE 段发生跳转时,你不能直接更新 PC,因为这会导致 Wishbone 总线上的地址发生改变,从而产生错误。

这里给出两种解决思路:

  • 尝试编写状态机控制流水线,让多个周期的访存“退化”成单周期的。即用一个大状态机,根据 Wishbone 总线上的情况,如果当前有正在进行的 Wishbone 总线请求,则生成信号阻塞整个流水线的执行。这个思路比较容易实现,缺点是会引起大量的流水线暂停。
  • 根据 IF 和 MEM 阶段的 Wishbone 总线请求状态,生成 IF 和 MEM 阶段的输出信号 flush_ostall_o,并处理一些特殊情况,例如上面提到的跳转时正在取指的情况,需要记录下新的 PC,等到之前旧的 Wishbone 请求完成以后,重新取一次指令。这个思路性能更好,实现起来需要考虑更多复杂的情况,因为 Wishbone 总线请求是不可以打断的。

下面给出第一种解决思路的一个例子:

假设在周期 0 时,流水线中指令如下:

IF ID EXE MEM WB
A B C D E

D 指令为 load 指令,C 指令不访存。

xx_master_ready 表示总线当前请求已经完毕,可以开始执行下一条指令。

pipeline_stall 表示是否完全阻塞流水线。

一个合理的波形如下:

time0123456789101112clockif_master_stbif_master_ackif_master_readymem_master_stbmem_master_ackmem_master_readypipeline_stall

注意到 8 时刻流水线不再阻塞,所以在 9 时刻时,流水线中指令如下:

IF ID EXE MEM WB
F A B C D

大家也可以考虑更加复杂的控制逻辑,但这已经超出实验六的要求了。在大实验中,如果想要更好的性能,需要使用性能更好的方法。


最后更新: 2023年7月3日
作者:Jiajie Chen (12.35%), gaoyichuan (84.57%), cuibst (3.09%)