单周期mips架构CPU的logisim实现
通过抽象的方式我们从两个方面来构建单周期CPU,也就是数据路径(DataPath)和控制器(Controller)
数据路径
对于一个数据路径,包括取指令(IF),译码(ID),执行(EX),访存(MEM),回写(WB)这几个方面,相应的有IFU,NPC,GRF,ALU,IM,DM这几个基本单元,单元之间通过Splitter和MUX等进行元件之间的数据交换和处理,这里需要在构建是需要留下几个控制信号的接口,以便于最后CU(控制器单元)单元的构建。
IFU取指令单元
该模块由PC(Programming Counter)模块和IM(Instruction Memory)模块组成。其中PC模块负责对每次新的指令状态进行转移,IM模块则从ROM中得到相应的指令。
- 端口定义
信号名 | 方向 | 位宽 | 描述 |
---|---|---|---|
CLK | I | 1 | 时钟信号 |
Reset | I | 1 | 异步复位信号 |
Stop | I | 1 | 停止信号 |
PC’ | I | 32 | 通过计算得到的下一条指令的地址 |
PC | O | 32 | 状态转移后的地址,输出当前正在执行的地址 |
Instr | O | 32 | 输出当前正在执行的指令 |
- 功能定义
序号 | 功能名称 | 功能描述 |
---|---|---|
1 | 复位 | 当Reset信号有效时,将PC寄存器中的值置为0x00003000 |
2 | 停止 | 当Stop信号有效时,PC寄存器忽略时钟输入,PC当前值保持不变 |
3 | 写 PC 寄存器 | 当 Stop 信号失效且时钟上升沿来临时,将下一条指令的地址(next PC)写入 PC 寄存器 |
4 | 取指令 | 根据当前 PC 的值从 IM(指令存储器)中读出对应的指令到 Instr 端口 |
PC(程序计数器)
- 端口定义
信号名 | 方向 | 位宽 | 描述 |
---|---|---|---|
CLK | I | 1 | 时钟信号 |
Reset | I | 1 | 异步复位信号 |
Stop | I | 1 | 停止信号 |
PC’ | I | 32 | 通过计算得到的下一条指令的地址 |
PC | O | 32 | 状态转移后的地址,输出当前正在执行的地址 |
- 功能定义
序号 | 功能名称 | 功能描述 |
---|---|---|
1 | 复位 | 当Reset信号有效时,将PC寄存器中的值置为0x00003000 |
2 | 停止 | 当Stop信号有效时,PC寄存器忽略时钟输入,PC当前值保持不变 |
3 | 写 PC 寄存器 | 当 Stop 信号失效且时钟上升沿来临时,将下一条指令的地址(next PC)写入 PC 寄存器 |
IM(指令存储器)
- 端口定义
信号名 | 方向 | 位宽 | 描述 |
---|---|---|---|
PC | I | 32 | 当前正在执行的地址 |
Instr | O | 32 | 输出当前正在执行的指令 |
- 功能定义
序号 | 功能名称 | 功能描述 |
---|---|---|
1 | 取指令 | 根据当前PC的值从IM中读出对应的指令 |
NPC(下一指令计算单元)
计算下一个指令,有三种方式,包括直接计算下一条指令,b型跳转指令,j型跳转指令。其中j型跳转指令包括跳转到寄存器的值和直接跳转两种。
- 端口定义
信号名 | 方向 | 位宽 | 描述 |
---|---|---|---|
PC | I | 32 | 当前指令地址 |
Branch | I | 1 | 是否为Branch型指令,控制信号 |
Equals | I | 1 | 是否满足Branch跳转判断 |
Offset | I | 32 | 进行符号拓展后的立即数信号 Branch & Equals==1:b型跳转 |
JUMP | I | 1 | 是否为j指令或者jal指令,控制信号 1:J型跳转 |
Imm_26 | I | 26 | 26位立即数信号,拓展后即为跳转地址 |
JR | I | 1 | 是否为jr或者jalr指令,控制信号 1:JR型跳转 |
Ra | I | 32 | 寄存器中存储的地址数据 |
- 功能定义
序号 | 功能名称 | 功能描述 |
---|---|---|
- 三种跳转指令
b型跳转指令
均为判断后跳转到label(即Offset)
JR型跳转指令(jr,jalr)
跳转到寄存器中的存储的地址
J型跳转指令(j,jal)
跳转到target这个立即数对应的地址
其实也可以分为:
间接寻址(通过PC+4和Offset寻址)
直接寻址(直接跳转到立即数对应地址,或者寄存器中存储的地址)
GRF(通用寄存器组)
- 端口定义
信号名 | 方向 | 位宽 | 描述 |
---|---|---|---|
CLK | I | 1 | 时钟信号 |
Reset | I | 1 | 异步复位信号 1:复位信号有效 0:复位信号无效 |
WE | I | 1 | 写使能信号 1:写入有效 0:写入无效 |
A1 | I | 5 | 地址输入信号,指定 32 个寄存器中的一个,将其中的数据读出到 RD1 |
A2 | I | 5 | 地址输入信号,指定 32 个寄存器中的一个,将其中的数据读出到 RD2 |
A3 | I | 5 | 地址输入信号,指定 32 个寄存器中的一个,将其作为写入目标 |
WD | I | 32 | 数据输入信号 |
RD1 | O | 32 | 输出A1指定的寄存器中的 32 位数据 |
RD2 | O | 32 | 输出A2指定的寄存器中的 32 位数据 |
- 功能定义
序号 | 功能名称 | 功能描述 |
---|---|---|
1 | 复位 | Reset 信号有效时,所有寄存器中储存的值均被清零 |
2 | 读数据 | 读出 A1,A2 地址对应的寄存器中储存的数据,将其加载到 RD1 和 RD2 |
3 | 写数据 | 当 WE 信号有效且时钟上升沿来临时,将 WD 中的数据写入到 A3 地址对应的寄存器 |
EXT(拓展单元)
将16位立即数符号拓展为32位。这里为了提高可拓展性,添加了UnsignedExt
接口
- 端口定义
信号名 | 方向 | 位宽 | 描述 |
---|---|---|---|
Imm_16 | I | 16 | 16位立即数输入信号 |
UnsignedExt | I | 1 | 无符号拓展信号 1:无符号拓展(0拓展) 0:符号拓展 |
Imm_32 | O | 32 | 32位立即数输出信号 |
- 功能定义
序号 | 功能名称 | 功能描述 |
---|---|---|
1 | 符号拓展 | 将16位立即数进行符号拓展 |
ALU(逻辑运算单元)
ALUOp | 指令 | Opcode | Op |
---|---|---|---|
加法 | add | 0000 | ALURes = SrcA+SrcB |
减法 | sub | 0001 | ALURes = SrcA-SrcB |
乘法(low) | mul | 0010 | ALURes = SrcA*SrcB |
除法(商) | div | 0011 | ALURes = SrcA / SrcB |
与运算 | and | 0100 | ALURes = SrcA & SrcB |
或运算 | or | 0101 | ALURes = SrcA | SrcB |
异或运算 | xor | 0110 | ALURes = SrcA $\oplus$ SrcB |
或非运算 | nor | 0111 | ALURes = ~(SrcA | SrcB) |
逻辑左移 | sll | 1000 | ALURes = SrcA << Shift |
逻辑右移 | srl | 1001 | ALURes = SrcA >> Shift |
算数右移 | sra | 1010 | ALURes = SrcA >> Shift |
相等 | equal | 1011 | ALURes = (SrcA == SrcB) ? 1 : 0 |
有符号小于 | signed_less | 1100 | ALURes = (SrcA < SrcB) ? 1 : 0 |
无符号小于 | unsigned_less | 1101 | ALURes = (u_SrcA < u_SrcB) ? 1 : 0 |
有符号大于 | signed_bigger | 1110 | ALURes = (SrcA > SrcB) ? 1 : 0 |
无符号大于 | unsigned_bigger | 1111 | ALURes = (u_SrcA > u_SrcB) ? 1 : 0 |
DM(数据存储器)
- 端口定义
信号名 | 方向 | 位宽 | 描述 |
---|---|---|---|
CLK | I | 1 | 时钟信号 |
Addr | I | 32 | 内存中的地址信号 |
WE | I | 1 | 写使能信号 1:写入有效 0:写入无效 |
WD | I | 32 | 在写入信号有效时,写入内存地址的数据 |
RD | O | 32 | 输出内存中对应地址的数据 |
- 功能定义
序号 | 功能名称 | 功能描述 |
---|---|---|
控制
CU
指令 | Opcode[31:26] | [25:21] | [20:16] | [15:11] | [10:6] | [5:0 |
---|---|---|---|---|---|---|
add | 000000 | rs | rt | rd | 00000 | 100000 |
sub | 000000 | rs | rt | rd | 00000 | 100010 |
ori | 001101 | rs | rt | immediate | ~ | ~ |
lw | 100011 | base | rt | offset | ~ | ~ |
sw | 101011 | base | rt | offset | ~ | ~ |
beq | 000100 | rs | rt | offset | ~ | ~ |
lui | 001111 | 00000 | rt | immediate | ~ | ~ |
nop | 000000 | 0 | 0 | 0 | 0 | 0 |
由于我是先搓完的数据路径部分,在写到CU的时候对于大多数的接口已经不记得了。这里可以学习黑书中的模式,通过一条指令来构建起其中的一些指令的控制,然后加指令来增加前面可能缺少的接口,同时补全接口的定义等。
首先我是将一些和初始化、终止等相关的接口拿出来,这些基本上是自定义的,用于提高CPU的可拓展性。比如在IFU中的Stop,EXT中的UnsignedExt信号等。
其次从CU的角度开始处理指令。
第一步,输入信号。
这里包括两个,Instr[31:26]
也就是Opcode,Instr[5:0]
也就是在R类型指令中的Func。
对这两个进行输入解析。利用And Logical
,判断得到对应什么指令。
然后利用Or Logical
,来激活相应的接口。
第二步,对于lw指令。
这里首先调整了几个选择信号,同时检查发现加上了WriteReg控制信号。
第三步,对于sw信号。
发现sw和lw基本一模一样。做完这两个之后对于整体指令已经熟悉了,然后开始实现剩余的指令控制信号。
最后,反过来从接口的角度思考有哪些指令需要用到该接口或者对该接口有什么操作,然后对CU进行补全以及检查。
自动化测试
思考题
-
上面我们介绍了通过 FSM 理解单周期 CPU 的基本方法。请大家指出单周期 CPU 所用到的模块中,哪些发挥状态存储功能,哪些发挥状态转移功能。
状态存储交给寄存器来搞定,包括IFU里面的指令寄存器和GRF里面的32个寄存器。其他的期间几乎都是在完成状态转移功能。
-
现在我们的模块中 IM 使用 ROM,DM 使用 RAM,GRF 使用 Register,这种做法合理吗? 请给出分析,若有改进意见也请一并给出。
合理的,正确的,中肯定,毋庸置疑的
-
在上述提示的模块之外,你是否在实际实现时设计了其他的模块?如果是的话,请给出介绍和设计的思路。
如果非要说的,或许可以实现一个分线器Splitter?将各部分信号传出来的一个元件,当然这几个就可以实现一个基本的mips单周期cpu
-
事实上,实现
nop
空指令,我们并不需要将它加入控制信号真值表,为什么?等价于sll 0位,或者认为是不进行任何操作处理。
-
阅读 Pre 的 “MIPS 指令集及汇编语言” 一节中给出的测试样例,评价其强度(可从各个指令的覆盖情况,单一指令各种行为的覆盖情况等方面分析),并指出具体的不足之处。
强度不行。指令并有覆盖到所有可能的情况,包括使用到的寄存器,是否考虑溢出情况等。