Featured image of post 北航CO_P3-logisim搭建单周期CPU

北航CO_P3-logisim搭建单周期CPU


单周期CPU设计方案

概述

本文使用logisim搭建了一个支持八条指令的MIPS架构CPU。开发过程首先分析所需指令的RTL,抽象出所需要的功能。然后分别建模并实现恰当的功能部件。再根据不同指令之间的关系合理地组建出简洁的数据通路。最后通过构建真值表构造控制器。

指令说明

本文实现的CPU只包含题目要求的8条指令,但笔者在使用时进行了一些操作以增强可扩展性,下文会在相应位置介绍。

R型指令

  • add
  • sub

实际上实现的指令相当于addusubu,因为题目明确指出不考虑溢出

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应该有几根线

alt text

指令分解

纯粹体力劳动,把所有可能出现的指令部分全部分离出来。 alt text

ADDU & SUBU

最基础的指令,其他指令基本都是通过扩充这一部分的电路。 电路中rs、rt、rd分别给出读写指令,将运算的结果写入rd对应的寄存器中。 alt text

ORI

这一段稍微需要注意一下RTL。 alt text 由RTL知,我们要把立即数imm16作零扩展到32位后再参与或运算。同时,注意到这里需要输入GRF和ALU模块的信号与上面不同。rt作为了被写入的寄存器,同时进行运算的一个寄存器被换成了立即数。所以要在A3和B接口前使用MUX进行信号选择。每一次出现这类情况我们都用一个标签作为控制信号,最后使用打表方式一次性生成各指令的控制信号。 alt text

LW

alt text 首次出现了要对DM进行操作的指令。此时ALU的输出作为读DM的地址,DM的输出被写入对应寄存器中。此外,这里需要对立即数imm16进行的是有符号扩展,所以对EXT也要进行信号选择。 alt text

SW

alt text 写入而非读取,只需要加一个输入的数据信号和读/写控制信号即可。 alt text

BEQ

这一部分的逻辑主要是在NPC中完成的,此外只需要利用ALU的减法产生一个判断A、B是否相等的信号即可。 alt text

LUI

唯一老师上课没讲的指令,所以白嫖不了结构图了(悲伤)。 alt text 但实现非常简单,笔者将imm16拼接了一个16h'0的信号,然后直接连到GRF的WD端,配合MUX和控制信号即可。

控制信号

为了使控制电路便于阅读和debug,我们使用或逻辑搭建电路,这样使得连线的目的非常明确。生成控制信号也许是最重要的一步,因为这一步很容易粗心。笔者就连错了一条。这里直接把我做的电路放上来。

有人可能会问和逻辑呢?因为我用的是比较器所以省略掉了hhh。 跟上面的信号名不完全一样,仅供参考。

alt text

测试数据

以下是我测试用过的数据,其中前一部分是pre给出的样例数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
.macro save(%dst)
sw %dst,0($v0)
lui $v1,0
ori $v1,$v1,4
add $v0,$v0,$v1
.end_macro

ori $a0, $0, 123
ori $a1, $a0, 456
lui $a2, 123            # 符号位为 0
lui $a3, 0xffff         # 符号位为 1
ori $a3, $a3, 0xffff    # $a3 = -1
add $s0, $a0, $a2      # 正正
add $s1, $a0, $a3      # 正负
add $s2, $a3, $a3      # 负负
ori $t0, $0, 0x0000
sw $a0, 0($t0)
sw $a1, 4($t0)
sw $a2, 8($t0)
sw $a3, 12($t0)
sw $s0, 16($t0)
sw $s1, 20($t0)
sw $s2, 24($t0)
lw $a0, 0($t0)
lw $a1, 12($t0)
sw $a0, 28($t0)
sw $a1, 32($t0)
ori $a0, $0, 1
ori $a1, $0, 2
ori $a2, $0, 1
beq $a0, $a1, loop1     # 不相等
beq $a0, $a2, loop2     # 相等
loop1:sw $a0, 36($t0)
loop2:sw $a1, 40($t0)

#以下为新写的
sub $v0,$v0,$v0
save($0)
save($0)
save($0)
save($0)
save($0)
save($0)
save($0)
save($0)
save($0)
save($0)
save($0)
save($0)
sub $v0,$v0,$v0
lui $t0,0x7fff
ori $t0,$t0,0xffff
lui $t1,0xffff
ori $t1,$t1,0xffff
lui $t2,0
ori $t2,$t2,1
lui $t3,0x8000
ori $t3,$t3,0
add $s4,$t0,$t2
save($s4)
add $s4,$t3,$t1
save($s4)
sub $s4,$t0,$t1
save($s4)
sub $s4,$t0,$t2
save($s4)

lui $s0,0
beq $a1,$a1,equal
ori $s0,$s0,0x1111
equal:
ori $s0,$s0,1
save($s0)

lui $s0,0
ori $s0,$s0,1
equal2:
add $s0,$s0,$s0
beq $a1,$a1,equal2

save($s0)

除此之外,我还使用了大佬分享的评测机进行了测试和对拍。 以下是某一个随机生成的测试文件。 查看文档

思考题

  1. 上面我们介绍了通过 FSM 理解单周期 CPU 的基本方法。请大家指出单周期 CPU 所用到的模块中,哪些发挥状态存储功能,哪些发挥状态转移功能。 答:发挥状态存储功能的模块:IFU、DM、GRF。发挥状态转移功能的模块:NPC、ALU、Control。
  2. 现在我们的模块中 IM 使用 ROM, DM 使用 RAM, GRF 使用 Register,这种做法合理吗? 请给出分析,若有改进意见也请一并给出。 答:合理,因为在本次的单周期CPU设计中,指令是只读的,所以使用只读的ROM进行存储。而DM需要频繁进行读写,所以使用RAM。GRF使用Register是标准设计,因为GRF本身就是寄存器堆,使用一堆寄存器搭建寄存器堆显然合理。此外,寄存器有高读写速度,适用于GRF的工作内容。
  3. 在上述提示的模块之外,你是否在实际实现时设计了其他的模块?如果是的话,请给出介绍和设计的思路。 答:没有。
  4. 事实上,实现 nop 空指令,我们并不需要将它加入控制信号真值表,为什么? 答:不需要。因为nop相当于sll $0,$0,0。即使在设置了sll指令时,因为这条指令并不改变寄存器值,也不会对运行产生影响。更别说我们这次的CPU压根没有实现sll指令。
  5. 阅读 Pre 的 “MIPS 指令集及汇编语言” 一节中给出的测试样例,评价其强度(可从各个指令的覆盖情况,单一指令各种行为的覆盖情况等方面分析),并指出具体的不足之处。 答:总体来看相当弱。没有覆盖sub指令。对add指令测试了正正、正负、负负三种情况,较全面,但是没有针对极端值进行测试(虽然本CPU的add和sub不考虑溢出)。对于beq的测试也过于简单,没有测试offset和寄存器值为负和为0的情况。lw也没有针对$0寄存器作出检验。
使用 Hugo 构建
主题 StackJimmy 设计