Logisim开发单周期MIPS处理器

 计组  Logisim  MIPS 󰈭 4108字

作为计组第一次实验的报告模板进行整理, 内容为使用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.

  • 符号拓展器、几个加法器以及逻辑门电路.

接口信号表:

Text
 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位寄存器

  • 几个多路选择器和解复用器, 基本逻辑门电路

接口信号表:

Text
 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也是计算机系统中的一个重要组件, 其主要功能是对指令进行解码以产生各种控制信号, 发送相应的控制信号给计算机系统的其他部分, 以使其按照指令的要求进行相应的操作.

控制器中主要包含的就是各个逻辑门, 其共同作用负责指令的解码以及控制信号的产生.

从可读性的角度来看这样的布局更好一些…

接口信号表:

Text
 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|--------------|------|----------------------------------------------------|

或逻辑中指令与控制信号对应表:

Text
 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与基本逻辑门

接口信号表:

Text
 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复选器.

接口信号表:

Text
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

asm
 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格式:

Text
134090003 352a0005 012a5821 01495023 112a0004 3c0bffff 356bffff ac0b0000
28c0c0000 1000fff6

这是一个简单的测试用例, 但是涉及了所有的7条指令, 可以较好地检测CPU是否涉及成功.

当所有指令执行完成后, t1-t4共4个寄存器的内容会被修改; 内存的第0个位置将被写为全1(即-1的补码)

可以看到几个寄存器的内容确实如上述所述, 且内存的0号单元也写入了0xffffffff

mips2

再利用chatgpt给出的一个更复杂的程序, 其将对内存进行更多的操作:

asm
 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代码为:

Text
13c081110 ac080004 8c090004 3c0a5678 352b9abc ac0b0008 112b000d 8c0c0004
2016c6821 ac0d000c 01aa7023 ac0e0010 3c0b2220 356b9abc 116e0001 116d0002
3ac0e0014 11290002 ac0e0018 11290000 00000000

检查一下内存的内容, 与预计结果一致, 因而可以认为指令成功执行!

嗨! 这里是 rqdmap 的个人博客, 我正关注 GNU/Linux 桌面系统, Linux 内核 以及一切有趣的计算机技术! 希望我的内容能对你有所帮助~
如果你遇到了任何问题, 包括但不限于: 博客内容说明不清楚或错误; 样式版面混乱; 加密博客访问请求等问题, 请通过邮箱 rqdmap@gmail.com 联系我!
修改日志
  • 2023-05-29 23:05:14 博客结构与操作脚本重构
  • 2023-05-08 21:44:36 博客架构修改升级
  • 2023-04-06 20:35:51 Logisim开发单周期MIPS处理器