作为计组第一次实验的报告模板进行整理, 内容为使用Logisim开发单周期MIPS处理器, 支持的指令集仅有: addu, subu, ori, lw, wsw, beq, lui这7条, 其中addu以及subu不必要实现溢出.
实验环境
<Logisim-Ita/Logisim: Logisim Italian Fork>
对Linux的支持还算可以, 因而直接在Linux上进行实验即可.
总体设计
下图展示了该单周期处理器的总体设计:
主要涉及到了IFU, GRP, ALU, EXT, DM, Controller这几个模块, 这几个模块后续也会进一步说明.
在顶层布局中, 做一些说明:
-
对于控制信号, 基本均使用了隧道, 为了布局清晰
-
GRP中A3的来源使用了MUX进行选择, 至少在本7条指令集中, 当OpCode为0时A3应该为rd, 不然A3应该为rt
-
DM是一个32x32的RAM, 由于比较简单因而没有进行模块化设计
模块定义
IFU, GPR, ALU ,EXT, DM, Controller
IFU
IFU的基本功能是取指令, 其能根据是否为BEQ指令动态的调整下一条指令的内容.
其内部包含:
-
一个PC寄存器, 这里定义为30位, 虽然实际只使用了低5位, 与ROM大小相对应
-
ROM用于存储指令, 大小为32*32bit.
-
符号拓展器、几个加法器以及逻辑门电路.
接口信号表:
1|-------------|------|--------------------------------|
2| 信号名 | 方向 | 说明 |
3|-------------|------|--------------------------------|
4| Clk | I | 时钟信号 |
5| Rst | I | 复位信号, 置位时PC寄存器归零 |
6| IfBeq | I | 判断当前指令是否为BEQ |
7| Zero | I | 来自于ALU, ALU此前做完减法操作 |
8| | | 1: 意味着beq两个寄存器内容相等 |
9|-------------|------|--------------------------------|
10| Instr[31:0] | O | 输出ROM中存储的一条32位指令 |
11|-------------|------|--------------------------------|
功能定义:
-
复位: 当Rst信号有效时, PC寄存器清零, 因而从头开始取ROM中的指令
-
取指: 在一个周期内, PC寄存器的内容送出, 通过分线器取低5位用于索引ROM, 将ROM的数据输出通过32位引脚输出
-
计算下一条指令: 通常情况下, PC寄存器的内容通过一个加法器执行加一的操作, 并在下一个时钟周期的上升沿到达时将新的结果写回到PC寄存器中; 而如果当前指令为BEQ, 那么IfBeq信号将被置位, 同时如果该指令的rs和rt寄存器通过ALU做减法计算后发现结果为0, 那么Zero信号也将被置位, 此时满足BEQ指令的跳转条件, 则应将当前指令的低16位取出, 通过符号拓展器后与PC寄存器当前的结果相加, 再加一后在下一个时钟周期写回到PC寄存器, 以此完成beq指令的计算操作
GPR
GPR是通用目的寄存器堆, MIPS通用寄存器堆包含了32个32位寄存器, 用于存储计算机程序的数据和指令. 这些寄存器被用于存储临时计算结果、函数调用参数、函数调用返回值以及其他数据.
这些32个寄存器的编号为$0$到$31$, 它们分别被用于不同的目的. 其中$0$号寄存器被硬编码为零, 不能被修改, 用于表示常数值0.
下面说明GRP的具体实现, 其中包含了:
-
32个32位寄存器
-
几个多路选择器和解复用器, 基本逻辑门电路
接口信号表:
1|------------|------|--------------------------------------------|
2| 信号名 | 方向 | 说明 |
3|------------|------|--------------------------------------------|
4| A1[4..0] | I | 寄存器的地址编号, 读取该编号的寄存器的内容 |
5| A2[4..0] | I | 寄存器的地址编号, 读取该编号的寄存器的内容 |
6| A3[4..0] | I | 寄存器的地址编号, 向该编号的寄存器写入内容 |
7| WD[31..0] | I | 如果要写寄存器, WD中存储了要写的数据 |
8| WE | I | 写使能信号, 决定是否写寄存器 |
9| Clk | I | 时钟信号 |
10|------------|------|--------------------------------------------|
11| RD1[31..0] | O | A1地址所对应寄存器的内容 |
12| RD2[31..0] | O | A2地址所对应寄存器的内容 |
13|------------|------|--------------------------------------------|
功能定义:
-
读寄存器: 通过A1和A2两个寄存器地址, 读出寄存器数据并通过RD1和RD2输出
-
写寄存器: 当写寄存器时, 需要WE使能信号置位, 在下一个时钟周期到来时, 将WD中的32位数据写入A3对应的寄存器中
设计说明:
-
在地址的解码中, 我们没有采用32选的MUX, 感觉不够优雅, 且可拓展性可能不好; 这里选用2x4x4的级联方式使用MUX选择器.
-
在A3中, 将5位的地址分为0-1, 2-3, 4共3段地址, 通过最高位来决定要不要允许对4x4的寄存器阵列执行写操作, 具体实现只需要用高位与WE信号做与操作即可. 而两段低2位的地址, 则用来选择寄存器阵列的行和列, 行信号与列信号通过与门来决定到底选哪个寄存器.
-
在A1和A2中, 类似的分别通过MUX选择器选择行、列、寄存器组这3级的数据, 最终选择出目标地址的数据送出即可
Controller
MIPS Controller也是计算机系统中的一个重要组件, 其主要功能是对指令进行解码以产生各种控制信号, 发送相应的控制信号给计算机系统的其他部分, 以使其按照指令的要求进行相应的操作.
控制器中主要包含的就是各个逻辑门, 其共同作用负责指令的解码以及控制信号的产生.
从可读性的角度来看这样的布局更好一些…
接口信号表:
1|--------------|------|----------------------------------------------------|
2| 信号名 | 方向 | 说明 |
3|--------------|------|----------------------------------------------------|
4| OpCode[5..0] | I | MIPS指令的高6位 |
5| Func[5..0] | I | MIPS指令的低6位, 只有当前指令的OpCode为0时才有意义 |
6|--------------|------|----------------------------------------------------|
7| ALUSrc | O | 0: ALU的第二个操作数来自于GRP |
8| | | 1: ALU的第二个操作数来自于指令中的立即数 |
9| IfBeq | O | 判断是否为beq指令 |
10| Mem2Reg | O | 0: ALU的输出作为写回GRP的数据来源 |
11| | | 1: 内存的输出作为写回GPR的数据来源 |
12| RegWrite | O | 是否使能写寄存器 |
13| MemWrite | O | 是否使能写内存 |
14| ExtOp | O | 0: 0拓展, 1:符号拓展 |
15| ALUOp[1..0] | O | 0: addu, 1:subu, 2:ori, 3: lui |
16|--------------|------|----------------------------------------------------|
或逻辑中指令与控制信号对应表:
1|------|--------|---------|----------|----------|-------|-------|-------|
2| | ALUSrc | Mem2Reg | RegWrite | MemWrite | ExtOp | ALUOp | IfBeq |
3|------|--------|---------|----------|----------|-------|-------|-------|
4| addu | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
5| subu | 0 | 0 | 1 | 0 | 0 | 1 | 0 |
6| ori | 1 | 0 | 1 | 0 | 0 | 2 | 0 |
7| lui | 1 | 0 | 1 | 0 | 0 | 3 | 0 |
8| lw | 1 | 1 | 1 | 0 | 1 | 0 | 0 |
9| sw | 1 | 0 | 0 | 1 | 1 | 0 | 0 |
10| beq | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
11|------|--------|---------|----------|----------|-------|-------|-------|
-
ALUSrc: 对于R型指令, ALU的输出正常从寄存器中得来(ALUSrc=0), 而I型指令则要从指令中到来(ALUSrc=1), 而beq尽管是一个I型指令, 但是其实际需要比较的是rs和rt两个寄存器的内容, 因而ALUSrc也为0
-
Mem2Reg: 只有lw指令才会从内存中读数到寄存器
-
RegWrite: 4条ALU类指令以及lw指令会写GRP寄存器堆
-
MemWrite: 只有sw指令才会向内存中写数
-
ExtOp: lw和sw指令中的立即数可正可负, 因为这是一个地址的偏移量, 因而需要使用符号拓展, 而其余指令通常只是位数不够需要补齐32位, 使用0拓展
-
ALUOp: 除去4条ALU指令, lw和sw需要调用加法操作计算地址偏移量, beq需要调用减法判断rs和rt的内容是否相等
功能定义:
-
译码: 通过OpCode和Func的输入, 执行与操作, 翻译出机器码对应的指令
-
产生控制信号: 根据翻译得到的指令, 执行或操作, 产生对应的控制信号
设计说明:
-
ALUOp由于是一个2bit的信号, 因而在实现时使用了优先译码器, 将addu, subu, ori和lui这四条输入引脚翻译为一个2bit的输出信号. 由于多个指令均可能产生addu等ALU指令, 因而优先译码器的输出部分为或逻辑.
-
一些非ALU指令(如lw,sw,beq等)也会用到ALU指令, 因而也需要为他们产生这类ALU控制信号:
-
lw, sw: 计算内存偏移地址, 产生addu信号
-
beq: 比较rs和rt内容是否一致, 产生subu信号
-
ALU
MIPS ALU主要用于执行算术和逻辑运算, 并向外输出一些标志, 在我们的设计中直接使用了一些已有的功能单元, 因为ALU内部拥有:
-
加法器
-
减法器
-
MUX与基本逻辑门
接口信号表:
1|---------|------|------------------|
2| 信号名 | 方向 | 说明 |
3|---------|------|------------------|
4| A | I | 操作数1 |
5| B | I | 操作数2 |
6| F[1..0] | I | 决定ALU操作功能: |
7| | | 0: addu, 1: subu |
8| | | 2: ori, 3: lui |
9|---------|------|------------------|
10| C | O | 输出 |
11| Zero | O | 判断结果是否全0 |
12|---------|------|------------------|
功能定义:
-
addu: 执行加法操作, 输出A+B
-
subu: 执行减法操作, 输出A-B
-
ori: 执行逻辑或操作, 输出A|B
-
lui: 高低位互换, 将B的低16位和高16位互换
- 为了实现lui指令的完整功能还需要ALU外部的拓展器相结合, 当执行lui指令时, 外部的0拓展会将B的高16位填充为0, 因而为了实现lui高位装载的功能, 在ALU内部只需要将高低16位互换即可.
EXT
符号拓展模块用于将16位的有符号数拓展为32位结果, 又具体根据控制信号可以分为0拓展和符号拓展.
模块内部拥有2个拓展器和2路MUX复选器.
接口信号表:
1|--------------|------|-----------------------|
2| 信号名 | 方向 | 说明 |
3|--------------|------|-----------------------|
4| imm16[15..0] | I | 输入16位的立即数 |
5| ExtOp | I | 0: 0拓展, 1: 符号拓展 |
6|--------------|------|-----------------------|
7| ext32[31..0] | O | 输出32位的拓展结果 |
8|--------------|------|-----------------------|
功能定义:
-
0拓展: 不管符号位是多少, 高16位全补0
-
符号拓展: 当ExtOp置位时采用符号拓展
测试用例
mips1
1start:
2# t1 = 3
3ori $t1, $zero, 3
4# t2 = 7
5ori $t2, $t1, 5
6# t3 = a
7addu $t3, $t1, $t2
8# t2 = 4
9subu $t2, $t2, $t1
10
11# jmp to ok if $t1==$t2, 无法jmp
12beq $t1, $t2, ok
13
14# 装载全1至t3高16位
15lui $t3, 0xffff
16# 装载全1至t3低16位, t3=-1
17ori $t3, $t3, 0xffff
18
19# mem[0] = -1
20sw $t3, 0
21
22# t4 = mem[0]
23lw $t4, 0
24
25ok:
26# jmp to start
27beq $zero, $zero, start
通过mars编译为hex格式:
134090003 352a0005 012a5821 01495023 112a0004 3c0bffff 356bffff ac0b0000
28c0c0000 1000fff6
这是一个简单的测试用例, 但是涉及了所有的7条指令, 可以较好地检测CPU是否涉及成功.
当所有指令执行完成后, t1-t4共4个寄存器的内容会被修改; 内存的第0个位置将被写为全1(即-1的补码)
可以看到几个寄存器的内容确实如上述所述, 且内存的0号单元也写入了0xffffffff
mips2
再利用chatgpt给出的一个更复杂的程序, 其将对内存进行更多的操作:
1
2# 加载立即数
3lui $t0, 0x1110 # $t0 = 0x11100000
4# 存储字
5sw $t0, 4($0) # 将$t0存储到地址0x04
6# 加载字
7lw $t1, 4($0) # 将地址0x04处的值加载到$t1
8# 将0x5678加载到t2的高16位
9lui $t2, 0x5678 # $t2 = 0x56780000
10# 按位或立即数
11ori $t3, $t1, 0x9abc # $t3 = $t1 | 0x9abc = 0x11109abc
12# 将t3存在地0x08
13sw $t3, 8($0)
14# 分支相等
15beq $t1, $t3, end # 如果$t1 == $t3,则跳转到label处
16# 加载字
17lw $t4, 4($0) # 将地址0x04处的值加载到$t4
18# 无符号加法
19addu $t5, $t3, $t4 # $t5 = $t3 + $t4 = 0x22209abc
20# 存储字
21sw $t5, 0xc($0) # 将$t5存储到地址0x08
22# 无符号减法
23subu $t6, $t5, $t2 # $t6 = $t5 - $t2 = 0xcba89abc
24# 存储字
25sw $t6, 0x10($0) # 将$t6存储到地址0x10
26# 将0x2220加载到t0的高16位
27lui $t3, 0x2220 # 此时$t3 = 0x22200000
28ori $t3, $t3, 0x9abc # 此时$t3 = 0x22209abc
29beq $t3, $t6, branch01 # 此时应该不会跳转, 因为$t3 != $t6
30beq $t3, $t5, branch02 # 此时应该会跳转, 因为$t3 == $t5
31
32branch01:
33sw $t6, 0x14($0) # 将$t6存储到地址0x14
34beq $t1, $t1, end # 结束
35
36branch02:
37sw $t6, 0x18($0) # 将$t6存储到地址0x18
38beq $t1, $t1, end # 结束
39
40end:
41nop
42
43# 最后的结果 (DM中)
44# 0x4: 0x11100000
45# 0x8: 0x11109abc
46# 0xc: 0x22209abc
47# 0x10: 0xcba89abc
48# 0x14: 0x00000000
49# 0x18: 0xcba89abc
hex代码为:
13c081110 ac080004 8c090004 3c0a5678 352b9abc ac0b0008 112b000d 8c0c0004
2016c6821 ac0d000c 01aa7023 ac0e0010 3c0b2220 356b9abc 116e0001 116d0002
3ac0e0014 11290002 ac0e0018 11290000 00000000
检查一下内存的内容, 与预计结果一致, 因而可以认为指令成功执行!