单周期CPU设计方案
概述
本文使用logisim搭建了一个支持八条指令的MIPS架构CPU。开发过程首先分析所需指令的RTL,抽象出所需要的功能。然后分别建模并实现恰当的功能部件。再根据不同指令之间的关系合理地组建出简洁的数据通路。最后通过构建真值表构造控制器。
指令说明
本文实现的CPU只包含题目要求的8条指令,但笔者在使用时进行了一些操作以增强可扩展性,下文会在相应位置介绍。
R型指令
add
sub
实际上实现的指令相当于
addu
和subu
,因为题目明确指出不考虑溢出
I型指令
ori
lw
sw
beq
lui
空指令
nop
模块设计
PC
就是一个32位寄存器,没什么特别设计的。
信号名 | 方向 | 位宽 |
---|---|---|
Clk | input | 1 |
reset | input | 1 |
DI | input | 32 |
DO | output | 32 |
NPC
用组合逻辑计算出下一个指令的地址,通常是变为PC+4
。对于跳转指令,本文只包含一个beq,但笔者为jal和jr留出了对应的接口。
信号名 | 方向 | 位宽 |
---|---|---|
PC | input | 32 |
imm16 | input | 16 |
imm26 | input | 26 |
A32 | input | 32 |
NPCOp | input | 2 |
Zero | input | 1 |
NPC | output | 32 |
GRF
寄存器堆,之前在P0已经设计过了。
信号名 | 方向 | 位宽 |
---|---|---|
Clk | input | 1 |
WE | input | 1 |
reset | input | 1 |
WD | input | 32 |
A1 | input | 5 |
A2 | input | 5 |
A3 | input | 5 |
RD1 | output | 32 |
RD2 | output | 32 |
IM
指令存储器,使用ROM实现,此前我因为审题不仔细导致这里卡了很久。要注意ROM的地址是以一个字为单位而不是字节,也就是说两个指令在mars中显示的地址是相隔4,用ROM表示其实只相隔1.
信号名 | 方向 | 位宽 |
---|---|---|
A | input | 12 |
D | output | 32 |
DM
数据存储器,用RAM实现。与指令存储器相比只多了一个写数据的功能。
信号名 | 方向 | 位宽 |
---|---|---|
Clk | input | 1 |
Wr | input | 1 |
A | input | 10 |
DI | input | 32 |
DO | output | 32 |
ALU
本文需要的指令只用得到四个运算:加、减、或和判断相等。其中等于可以用减来完成,减可以用补码来简化。
信号名 | 方向 | 位宽 |
---|---|---|
a | input | 32 |
b | input | 32 |
op | input | 2 |
c | output | 32 |
组建数据通路
取指令
PC与NPC实际上组成了一个MOORE型状态机,除这两个模块的所有部分都可以看成转移电路。
图是gxp老师课件上的,实际电路NPC应该有几根线
指令分解
纯粹体力劳动,把所有可能出现的指令部分全部分离出来。
ADDU & SUBU
最基础的指令,其他指令基本都是通过扩充这一部分的电路。
电路中rs、rt、rd分别给出读写指令,将运算的结果写入rd对应的寄存器中。
ORI
这一段稍微需要注意一下RTL。
由RTL知,我们要把立即数imm16作零扩展到32位后再参与或运算。同时,注意到这里需要输入GRF和ALU模块的信号与上面不同。rt作为了被写入的寄存器,同时进行运算的一个寄存器被换成了立即数。所以要在A3和B接口前使用MUX进行信号选择。每一次出现这类情况我们都用一个标签作为控制信号,最后使用打表方式一次性生成各指令的控制信号。
LW
首次出现了要对DM进行操作的指令。此时ALU的输出作为读DM的地址,DM的输出被写入对应寄存器中。此外,这里需要对立即数imm16进行的是有符号扩展,所以对EXT也要进行信号选择。
SW
写入而非读取,只需要加一个输入的数据信号和读/写控制信号即可。
BEQ
这一部分的逻辑主要是在NPC中完成的,此外只需要利用ALU的减法产生一个判断A、B是否相等的信号即可。
LUI
唯一老师上课没讲的指令,所以白嫖不了结构图了(悲伤)。
但实现非常简单,笔者将imm16拼接了一个16h'0的信号,然后直接连到GRF的WD端,配合MUX和控制信号即可。
控制信号
为了使控制电路便于阅读和debug,我们使用或逻辑搭建电路,这样使得连线的目的非常明确。生成控制信号也许是最重要的一步,因为这一步很容易粗心。笔者就连错了一条。这里直接把我做的电路放上来。
有人可能会问和逻辑呢?因为我用的是比较器所以省略掉了hhh。 跟上面的信号名不完全一样,仅供参考。
测试数据
以下是我测试用过的数据,其中前一部分是pre给出的样例数据
|
|
除此之外,我还使用了大佬分享的评测机进行了测试和对拍。 以下是某一个随机生成的测试文件。 查看文档
思考题
- 上面我们介绍了通过 FSM 理解单周期 CPU 的基本方法。请大家指出单周期 CPU 所用到的模块中,哪些发挥状态存储功能,哪些发挥状态转移功能。 答:发挥状态存储功能的模块:IFU、DM、GRF。发挥状态转移功能的模块:NPC、ALU、Control。
- 现在我们的模块中 IM 使用 ROM, DM 使用 RAM, GRF 使用 Register,这种做法合理吗? 请给出分析,若有改进意见也请一并给出。 答:合理,因为在本次的单周期CPU设计中,指令是只读的,所以使用只读的ROM进行存储。而DM需要频繁进行读写,所以使用RAM。GRF使用Register是标准设计,因为GRF本身就是寄存器堆,使用一堆寄存器搭建寄存器堆显然合理。此外,寄存器有高读写速度,适用于GRF的工作内容。
- 在上述提示的模块之外,你是否在实际实现时设计了其他的模块?如果是的话,请给出介绍和设计的思路。 答:没有。
- 事实上,实现 nop 空指令,我们并不需要将它加入控制信号真值表,为什么? 答:不需要。因为nop相当于sll $0,$0,0。即使在设置了sll指令时,因为这条指令并不改变寄存器值,也不会对运行产生影响。更别说我们这次的CPU压根没有实现sll指令。
- 阅读 Pre 的 “MIPS 指令集及汇编语言” 一节中给出的测试样例,评价其强度(可从各个指令的覆盖情况,单一指令各种行为的覆盖情况等方面分析),并指出具体的不足之处。 答:总体来看相当弱。没有覆盖sub指令。对add指令测试了正正、正负、负负三种情况,较全面,但是没有针对极端值进行测试(虽然本CPU的add和sub不考虑溢出)。对于beq的测试也过于简单,没有测试offset和寄存器值为负和为0的情况。lw也没有针对$0寄存器作出检验。