Logisim开发单周期MIPS处理器

 计组  Logisim  MIPS 󰈭 4078字

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

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

嗨! 这里是 rqdmap 的个人博客, 我正关注 GNU/Linux 桌面系统, Linux 内核, 后端开发, Python, Rust 以及一切有趣的计算机技术! 希望我的内容能对你有所帮助~
如果你遇到了任何问题, 包括但不限于: 博客内容说明不清楚或错误; 样式版面混乱等问题, 请通过邮箱 rqdmap@gmail.com 联系我!
修改记录:
  • 2023-05-29 23:05:14大幅重构了python脚本的目录结构,实现了若干操作博客内容、sqlite的助手函数;修改原本的文本数 据库(ok)为sqlite数据库,通过嵌入front-matter的page_id将源文件与网页文件相关联
  • 2023-05-08 21:44:36博客架构修改升级
  • 2023-04-06 20:35:51Logisim开发单周期MIPS处理器
Logisim开发单周期MIPS处理器