作者:SuiyueYoung
上次修改日期:2024-7-23 1:24
引言
作为一名计算机科学与技术专业的大二学生,我在学习计算机组成原理的过程中,对计算机的运行过程产生了浓厚的兴趣。为了更深入地理解计算机的工作机制,我计划亲自动手设计并实现一个CPU。我希望能够在内存中写入机器码指令,并运行这些程序。
尽管网上有一些相关的资料,但大多不够详细,因此我决定在完成这个项目后,撰写一篇详细的文章,以供其他同学参考。
软件环境:logisim-win-2.7.1
1 CPU设计
1.1 CPU架构
CPU 架构设计如下:
- 寄存器: 两个通用寄存器AC, R; PC 程序寄存器; IR 指令寄存器; AR 地址寄存器。
- 机器字长:8bit。
- 地址字长:16bit。
- 数据字长:8bit。
- 指令字长:8bit。
- 地址生成方式:AR = AC << 8 + R。
1.2 指令设计
1.2.1 指令类型
本项目总共实现 16 条指令,指令类型如下:
TYPE | INSTRUCTION |
---|---|
RR类型指令(寄存器-寄存器) | MOVR、MOVAC、ADD、SUB、INAC、CLAC、AND、OR、XOR、NOT |
RS类型指令(寄存器-存储器) | LDAC(装入字)、STAC(存储字) |
J类型指令(跳转指令) | JUMP、JMPZ、JPNZ |
O类型指令(其他指令) | NOP |
1.2.2 指令格式
在MIPS(Microprocessor without Interlocked Pipeline Stages)架构中,单周期指令设计是一种简单但有效的方式,用于理解基本的计算机处理器的操作。在单周期处理器中,每条指令在一个时钟周期内完成。这种设计通常用于教学和简单的处理器设计,因为它的实现相对简单,尽管在现代高性能处理器中通常采用流水线和多周期设计来提高效率但本项目仅采用单周期指令设计。指令格式设计如下:
SEQUENCE | INSTRUCTION | TYPE[7:6] | OP[5:2] | REVERSE[1] | REVERSE[0] | HEX | FUNCTION |
---|---|---|---|---|---|---|---|
0 | NOP | 00 | 0000 | 0 | 0 | 00 | NO OPERATION |
1 | MOVAC | 01 | 0000 | 0 | 0 | 40 | R <- AC |
2 | MOVR | 01 | 0001 | 1 | 0 | 46 | AC <- R |
3 | ADD | 01 | 0010 | 1 | 0 | 4A | AC <- AC + R |
4 | SUB | 01 | 0011 | 1 | 0 | 4E | AC <- AC - R |
5 | INAC | 01 | 0100 | 1 | 0 | 52 | AC <- AC + 1 |
6 | CLAC | 01 | 0101 | 1 | 0 | 56 | AC <- 0 |
7 | AND | 01 | 0110 | 1 | 0 | 5A | AC <- AC and R |
8 | OR | 01 | 0111 | 1 | 0 | 5E | AC <- AC or R |
9 | XOR | 01 | 1000 | 1 | 0 | 62 | AC <- AC xor R |
10 | NOT | 01 | 1001 | 1 | 0 | 66 | AC <- not AC |
11 | LDAC | 10 | 0000 | 1 | 0 | 82 | AC <- M[AR] |
12 | STAC | 10 | 0001 | 0 | 0 | 84 | M[AR] <- AC |
13 | MOVT | 10 | 0010 | 0 | 0 | 88 | AR <- AC << 8 + R |
14 | JUMP | 11 | 0000 | 0 | 0 | C0 | GOTO AR |
15 | JMPZ | 11 | 0001 | 0 | 0 | C4 | IF(Z == 1) GOTO AR |
16 | JPNZ | 11 | 0010 | 0 | 0 | C8 | IF(Z == 0) GOTO AR |
已知指令长度为 8bit,对上表解释如下:
- SEQUENCE 为序号。
- INSTRUCTION: 为指令。
- TYPE: 为指令类型占据指令高两位。
- OP: 为操作码,占据指令第 3 位到第 6 位。
- REVERSE[1], REVERSE[0]: 做指令扩展,分别占据第 2 位和第 1 位。
- HEX: 为指令转化为十六进制格式。
- FUNCTION: 为指令对应操作。
1.3 控制单元设计
1.3.1 CU 控制单元
作为整个 CPU 的控制中心,CU 控制单元会将 IR 寄存器存储的指令(Instruction)取出的同时进行译码操作,在一个时钟周期内控制CPU部件运行。CU 控制单元译码设计如下:
指令 | ARwrite | AR | RegDst | RegWrite | RegData | MemWrite | ALU_OP | NPC_OP |
---|---|---|---|---|---|---|---|---|
NOP | 0 | 0 | x | 0 | 1 | 0 | 1111 | 00 |
MOVAC | 0 | 0 | 0 | 1 | 1 | 0 | 0000 | 00 |
MOVR | 0 | 0 | 1 | 1 | 1 | 0 | 0001 | 00 |
ADD | 0 | 0 | 1 | 1 | 1 | 0 | 0010 | 00 |
SUB | 0 | 0 | 1 | 1 | 1 | 0 | 0011 | 00 |
INAC | 0 | 0 | 1 | 1 | 1 | 0 | 0100 | 00 |
CLAC | 0 | 0 | 1 | 1 | 1 | 0 | 0101 | 00 |
AND | 0 | 0 | 1 | 1 | 1 | 0 | 0110 | 00 |
OR | 0 | 0 | 1 | 1 | 1 | 0 | 0111 | 00 |
XOR | 0 | 0 | 1 | 1 | 1 | 0 | 1000 | 00 |
NOT | 0 | 0 | 1 | 1 | 1 | 0 | 1001 | 00 |
LDAC | 0 | 1 | 1 | 1 | 0 | 0 | 1111 | 00 |
STAC | 0 | 1 | x | 0 | 1 | 1 | 1111 | 00 |
MOVT | 1 | 0 | x | 0 | 1 | 0 | 1111 | 00 |
JUMP | 0 | 0 | x | 0 | 1 | 0 | 1111 | 01 |
JMPZ | 0 | 0 | x | 0 | 1 | 0 | 1111 | 10 |
JPNZ | 0 | 1 | x | 0 | 1 | 0 | 1111 | 11 |
对上表的解释如下:
- ARwrite: 控制 AR 寄存器写操作,ARwtie为 1 时,AR寄存器可被写入。
- *AR: *
- RegDest: 选择通用寄存器操作,为 0 时选择 AC 寄存器,为 1 时选择 R 寄存器。
- RegWrite: 控制通用寄存器写操作,RegWrite为 1 时,通用寄存器可被写入。
- RegData: 控制寄存器读取数据来源为 0 时选择数据来源为内存,为 1 时选择数据来源为寄存器。
- MemWRrite: 内存读写控制线,为 0 时从内存读数据,为 1 时向内存写数据。
- ALU_OP: 编码当前时钟周期 ALU 操作。
- NPC_OP: 编码下一个时钟周期 PC 的操作。
注意:x表示任意值均可(此时该控制信号无效)。
1.3.2 ALU 控制单元
本项目的 ALU 仅能进行简单算数和逻辑运算,并未设计乘法和除法操作。但是读者可以自行手搓机器码,用加减法的方式实现乘法和除法操作。ALU控制单元可将 CU 控制单元传入的 4bit 指令进行译码后在同一时钟周期控制 ALU 相关操作。ALU 控制单元译码如下:
指令 | ALU_OP | Cin | S6 | S5 | S4 | S3 | S2 | S1 | S0 |
---|---|---|---|---|---|---|---|---|---|
MOVAC | 0000 | 0 | 0 | x | x | 1 | 0 | 0 | 0 |
MOVR | 0001 | 0 | 0 | x | x | 0 | 0 | 1 | 0 |
ADD | 0010 | 0 | 0 | x | x | 0 | 0 | 0 | 0 |
SUB | 0011 | 1 | 0 | x | x | 0 | 1 | 0 | 0 |
INAC | 0100 | 1 | 0 | x | x | 1 | 0 | 0 | 0 |
CLAC | 0101 | x | 1 | 0 | 0 | 1 | 0 | 0 | 0 |
AND | 0110 | x | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
OR | 0111 | x | 1 | 0 | 1 | 0 | 0 | 0 | 0 |
XOR | 1000 | x | 1 | 1 | 0 | 0 | 0 | 0 | 0 |
NOT | 1001 | x | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
NOP | 1111 | x | x | x | x | x | x | x | x |
对上表的解释如下:
- Cin: 如果 Cin 为 1 (全加器最低位会置为 1) 则加法器会将其结果加一。
- S1, S0: 对数据的选择。00为 (AC), 01 为 ~(AC), 10 为 00, 11 为 FF。
- S3, S2: 对数据的选择。00为 (R), 01 为 ~(R), 10 为 00, 11 为 FF。
- S5, S4: 对与、或、异或、取反操作的选择。00 选择与操作,01 选择或操作,10 选择异或操作,11 选择取反操作。
- S6: 选择输出数据,为 0 选择算数运算数据,为 1 选择逻辑运算数据。
注意:
- (X)代表取 X 寄存器数据,例如 ~(AC) 代表从 AC 寄存器中取数据并取反。
- 此处与、或、异或、取反均按位执行与 C 语言中
&, |, ^, ~
运算符含义相同。 - x 表示任意值均可(此时该控制信号无效)。
1.3.3 PC 控制单元
PC 由 NPC_OP, AR, Z共同控制。但具体功能可直接参考 NPC_OP。PC 控制译码如下:
NPC_OP | FUNCTION |
---|---|
00 | PC + 1 |
01 | AR |
10 | IF(Z==1) AR |
11 | IF(Z==0) AR |
对上表的解释:功能就是解释。
2 Logisim 实现
2.1 CPU 整体结构设计
CPU 整体设计图如下:
CPU运行过程解释如下:
- 点击 rst 按钮,初始化所有寄存器和内存。
- 在初始化完成后,PC 寄存器指向内存 0x0000 位置,我们的程序就从这里开始写入。
- 在写入程序后,点击时钟产生一个时钟周期(点两下),此后的过程均在这一个时钟周期内完成。
- IR (指令寄存器) 从 PC (程序计数器) 指向的地址读入数据,CU (控制单元) 对 IR 寄存器中的内容解码。在程序开始时,IR 寄存器的初始数据为 0x00,此时CU将其解释为空操作。随后,IR 寄存器中的 0x00 会被新读取的机器码覆盖。需要注意的是,这个过程并不是在 IR 寄存器读取内存中数据的同时 CU 控制单元对 IR 寄存器中内容解码。CU 控制端元总是对上一时刻 IR 寄存器中的内容进行解码。
- CU 控制单元输出控制信号控制几乎所有原件的运行,可以说 CU 控制单元输出控制信号的同时所有部件接受信号并指向相应操作。
- 最后 NPC 解析出下一时钟周期 PC 指向的位置,在第一次产生时钟周期的同时 PC 下一时钟周期的值就被写入到 PC 寄存器中。
作者实现一个非常简单的程序,实现 $4\times8$ 然后将结果写入内存 0x0200 中。
2.2 其余部分设计
本项目中 CU 控制单元, ALU 算数逻辑单元, NPC PC计算单元, Regs 寄存器组单元的设计都不困难。基本上都是根据运行逻辑处理电路设计,相信读者有实力理解其中的内容。
CU 控制单元设计图如下:
ALU 控制单元设计图如下:
ALU 操作码解码单元设计图如下:
NPC PC计算单元设计图如下:
Regs 寄存器组单元设计图如下:
ARG 内存地址计算单元设计图如下:
3 源代码获取
郑重声明:未经作者允许禁止转载
郑重声明:未经作者允许禁止转载
郑重声明:未经作者允许禁止转载
Logisim 源代码以及指令设计获取: Gitee源代码获取,Github源代码获取
原文地址: Logisim模拟8位CPU