实验6:单周期CPU与多周期CPU
注意
本实验尚未发布,此文档为2023年文档,实验要求可能更改,请勿提前按本文档完成实验。
在本次实验中,我们将完成Loongarch32R的单周期CPU和多周期CPU的设计,并根据龙芯杯的要求,完成不同的分级测试。
1 单周期CPU
其实真的很简单
大家千万不要被“CPU”这个字符串吓到,也不要被《计算系统概论》中的LC3复杂状态机和数据通路吓到——只支持基础整数指令的单周期CPU是很简单的,大家可以按照下面的实现,用verilog“照猫画猫”地完成这个CPU的设计。
在这里,我们给出龙芯架构32位精简版基础算数指令的单周期CPU的设计,其结构如下图所示:
下面我们对其中的每个部件进行详细的介绍。(点击这里获取高清无码大图)
1.1 取指单元
- PC:程序计数器,用于存储当前指令的地址,每次取指令后,如果当前指令不产生跳转,则自动加4,否则根据跳转指令的类型,将跳转地址写入PC。
- Instrucion Memory:存储指令的存储器,根据PC中的地址,从指令存储器中读取指令。这个存储器我们可以使用DRAM来实现。
1.2 译码与读寄存器堆单元
-
Decode:指令译码器,通过输入的Loongarch32R指令,生成出如下的控制信号:
- rf_raddr1:寄存器堆读端口1的地址,这个地址由指令中的
rj
字段给出 - rf_raddr2:寄存器堆读端口2的地址,这个地址由指令中的
rk
或rd
字段给出 - rf_rd:寄存器堆写端口的地址,这个地址可能由指令中的
rd
字段给出,但在bl
指令中,这个地址为1 - rf_we:寄存器堆写使能信号。
- imm:指令中的立即数,这个立即数存在于指令中,均为符号位拓展。其拼接方式和指令的类型有关。特别注意,对于许多跳转指令,这个地址往往省略了最低两位的0。
- alu_op:ALU的操作类型,由译码器根据指令的意义给出
- alu_src1_sel:ALU第一个操作数的来源,可能来自
pc
、rf_rdata1
或0
- alu_src2_sel:ALU第二个操作数的来源,可能来自
rf_rdata2
、imm
或4
- mem_we:数据存储器写使能信号,当指令为
store
类型时,这个信号为1 - br_type:跳转指令的类型,译码器根据指令是否跳转以及跳转的类型给出
- wb_sel:写回的数据来源,可能来自
alu
或data_memory
。
- rf_raddr1:寄存器堆读端口1的地址,这个地址由指令中的
-
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:分支跳转单元,用于判断是否跳转。分支跳转单元的分支类型由译码器给出,既能够计算出是否需要跳转,还可以计算出跳转的地址。这里需要特别注意的是:
beq
、bne
、blt
、bge
、bltu
、bgeu
、b
、bl
指令的跳转地址为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不同,多周期CPU使用状态机来控制电路。对于一条指令而言,其状态一般有如下五个:
-
- 取指阶段
-
- 译码阶段
-
- 执行阶段
-
- 访存阶段
-
- 写回阶段
每一个阶段都可以设计为一个状态,但对于不同的指令,其所需的阶段可能不同,例如:
- ADD.W 指令不需要访存阶段
- BNE指令不需要访存阶段和写回阶段
每一个阶段获得的信号(例如译码阶段产生的控制信号)需要使用寄存器来锁存(见数据通路),以此来降低数据通路的组合延迟。有限状态机需要控制每一个寄存器的写使能,以此来寄存控制信号和数据,以供后续使用。