跳转至

实验6:单周期CPU与多周期CPU

注意

本实验尚未发布,此文档为2023年文档,实验要求可能更改,请勿提前按本文档完成实验。

在本次实验中,我们将完成Loongarch32R的单周期CPU和多周期CPU的设计,并根据龙芯杯的要求,完成不同的分级测试。

1 单周期CPU

其实真的很简单

大家千万不要被“CPU”这个字符串吓到,也不要被《计算系统概论》中的LC3复杂状态机和数据通路吓到——只支持基础整数指令的单周期CPU是很简单的,大家可以按照下面的实现,用verilog“照猫画猫”地完成这个CPU的设计。

在这里,我们给出龙芯架构32位精简版基础算数指令的单周期CPU的设计,其结构如下图所示:

CPU.drawio_00

下面我们对其中的每个部件进行详细的介绍。(点击这里获取高清无码大图)

1.1 取指单元

  • PC:程序计数器,用于存储当前指令的地址,每次取指令后,如果当前指令不产生跳转,则自动加4,否则根据跳转指令的类型,将跳转地址写入PC。
  • Instrucion Memory:存储指令的存储器,根据PC中的地址,从指令存储器中读取指令。这个存储器我们可以使用DRAM来实现。

1.2 译码与读寄存器堆单元

  • Decode:指令译码器,通过输入的Loongarch32R指令,生成出如下的控制信号:

    • rf_raddr1:寄存器堆读端口1的地址,这个地址由指令中的rj字段给出
    • rf_raddr2:寄存器堆读端口2的地址,这个地址由指令中的rkrd字段给出
    • rf_rd:寄存器堆写端口的地址,这个地址可能由指令中的rd字段给出,但在bl指令中,这个地址为1
    • rf_we:寄存器堆写使能信号。
    • imm:指令中的立即数,这个立即数存在于指令中,均为符号位拓展。其拼接方式和指令的类型有关。特别注意,对于许多跳转指令,这个地址往往省略了最低两位的0
    • alu_op:ALU的操作类型,由译码器根据指令的意义给出
    • alu_src1_sel:ALU第一个操作数的来源,可能来自pcrf_rdata10
    • alu_src2_sel:ALU第二个操作数的来源,可能来自rf_rdata2imm4
    • mem_we:数据存储器写使能信号,当指令为store类型时,这个信号为1
    • br_type:跳转指令的类型,译码器根据指令是否跳转以及跳转的类型给出
    • wb_sel:写回的数据来源,可能来自aludata_memory
  • Register File:寄存器堆,用于存储32个32位的寄存器。寄存器堆的读端口1的地址由译码器给出,读端口2的地址由译码器给出,写端口的地址由译码器给出,写使能信号由译码器给出。

龙芯架构的特别约定

在龙芯架构中,寄存器堆的0号寄存器永远为0,且不可写。思考一下,保留一个永远为0的寄存器是否值得?如果值得,这样做的好处是什么?如果不值得,那么哪些指令的实现需要进行修改?

1.3 执行单元

  • ALU:算术逻辑单元,用于执行指令中的算术逻辑运算。ALU的输入由译码器给出,通过对两个输入进行运算,得到一个32位的运算结果。这个运算结果可能是直接写回寄存器堆,也可能是用于数据存储器的访存地址。

为什么要有“0”和“4”

这是助教一直坚持使用而且比较喜欢的设计,0是为了减少ALU的一个运算器(在应对lui12.w时,可以直接将alu_op置为加法),4是为了减少一次写回寄存器的选择(在应对bl或jirl时,我们可以直接用ALU将PC+4计算出来,不需要再额外使用加法器并在写回之前面临一个3选1的选择)。

  • Branch:分支跳转单元,用于判断是否跳转。分支跳转单元的分支类型由译码器给出,既能够计算出是否需要跳转,还可以计算出跳转的地址。这里需要特别注意的是:
    • beqbnebltbgebltubgeubbl指令的跳转地址为pc + imm
    • jirl指令的跳转地址为rf_rdata + imm

1.4 访存单元

  • Data Memory:数据存储器,用于存储数据。数据存储器的读写使能信号由译码器给出,读写地址由ALU给出,写数据由rf_rdata2。和指令存储器一样,我们可以使用DRAM来实现。

1.5 写回单元

  • WB Mux:寄存器堆写回多选器,用于选择写回的数据。写回的数据可能来自ALU,也可能来自数据存储器。选择信号由译码器给出。

单周期的思考

单周期CPU的设计思路非常简单,代码工作量也并不大,那么为什么我们的处理器不是单周期呢?这是因为单周期CPU的时钟周期非常长——最长通路的延时可以概括为指令存储器读+译码器+寄存器堆读+ALU+数据存储器读+寄存器堆写,这个延时非常长,而且每个指令都需要这么长的时间,这样的CPU时钟频率非常低下。为了解决这个问题,我们需要将CPU分为多个阶段,每个阶段的延时尽可能短,这样就可以提高CPU的时钟频率。下面我们介绍一种基础的解决方案——多周期CPU。

2 多周期CPU

多周期CPU的设计思路是将上述CPU的5个阶段用多个时钟周期来完成,使用寄存器来保存住上一个周期中的结果,这样就可以将每个阶段的延时尽可能短,从而提高CPU的时钟频率。下面我们给出一个多周期CPU的设计,其结构如下图所示:

CPU.drawio

和单周期CPU不同,多周期CPU使用状态机来控制电路。对于一条指令而言,其状态一般有如下五个:

    1. 取指阶段
    1. 译码阶段
    1. 执行阶段
    1. 访存阶段
    1. 写回阶段

每一个阶段都可以设计为一个状态,但对于不同的指令,其所需的阶段可能不同,例如:

  • ADD.W 指令不需要访存阶段
  • BNE指令不需要访存阶段和写回阶段

每一个阶段获得的信号(例如译码阶段产生的控制信号)需要使用寄存器来锁存(见数据通路),以此来降低数据通路的组合延迟。有限状态机需要控制每一个寄存器的写使能,以此来寄存控制信号和数据,以供后续使用。