仅供参考,严禁抄袭!
注意,本文供具有一定计算机组成原理理论和数字电路基础的同学参考学习,并不是从零开始的教程,如果前导知识还未掌握,请参考其他文章。
顶层设计示意图
一、设计草稿:
1.IFU(取指令单元)
包括PC(32位寄存器)、IM(32位*32字指令存储器)、reset(1位复位信号)、clk(1位时钟信号)。其中IM使用ROM进行实现,以字存址,因此每进行一次指令后PC加一。
因为规定IM的地址位为5,所以取PC[4:0]作为地址读取Instr即可。
具体端口如下表所示。
端口 | 方向 | 描述 |
reset | I | 异步复位信号。为1时指令地址保持0。 |
clk | I | 时钟信号 |
PC’ | I | 经过计算得到的下周期指令的存放地址 |
PC | O | 该周期指令的存放地址 |
Instr | O | 读取到的指令信号 |
2.GRF(寄存器文件)
该寄存器文件模块可实现以下功能:
- 复位。当reset信号有效时,所有寄存器存储的数值全部清零。
- 读取。读出A1、A2地址对应的寄存器中的数据到RD1、R2端口。
- 写入。当WE信号有效时,根据A3中输入的目标寄存器地址,将WD写入对应的目标寄存器。
具体端口及其功能如下表所示。
端口 | 方向 | 描述 |
reset | I | 异步复位信号。为1时指令地址保持0。 |
clk | I | 时钟信号 |
WE | I | 写使能信号。1:可向GRF中写入数据 0:不能向GRF中写入数据 |
A1 | I | 5位地址输入信号,指定32个寄存器中的一个,将其中存储的数据读出到RD1 |
A2 | I | 5位地址输入信号,指定32个寄存器中的一个,将其中存储的数据读出到RD2 |
A3 | I | 5位地址输入信号,指定32个寄存器中的一个作为写入的目标寄存器 |
WD | I | 32位数据输入信号 |
RD1 | O | 输出A1指定的寄存器中的32位数据 |
RD2 | O | 输出A2指定的寄存器中的32位数据 |
3.EXT(扩展单元)
该模块主要用于判断对指令中的立即数的扩展方式。目前仅有零扩展和符号扩展两种方式,可在该基础上进行指令添加。
设计此模块的目的在于满足ALU对于32位操作数的强制规定,根据指令需求便捷地实现扩展。
具体端口及其功能如下表所示。
端口 | 方向 | 描述 |
Instr[15:0] | I | 指令信号中的16位待扩展立即数信号 |
EXTop | I | 扩展操作信号。为0时进行零扩展,为1时进行符号扩展 |
ExtImm | O | 完成扩展后的立即数信号 |
4.ALU(算术逻辑单元)
该ALU目前仅包括了P3课下要求的相关计算逻辑,但具有较好的扩展性,根据ALUnum的数据进行相关运算。ALUnum与指令操作的对应关系如下表所示。
ALUnum | 指令 | 描述 |
000 | add | result = Op1 + Op2 |
001 | sub | result = Op1 – Op2 |
010 | ori | result = Op1 & Op2 |
011 | lui | result = Op2 << 0x10 |
具体端口及其功能如下表所示。
端口 | 方向 | 描述 |
Op1 | I | 32位操作数1 |
Op2 | I | 32位操作数2 |
ALUnum | I | 3位(可扩展)ALU操作选择数 |
result | O | 32位ALU运算结果 |
equal | O | 判断两个操作数是否相等。 1:相等 0:不相等 |
5.Controller(控制器)
Controller模块是整个单周期CPU的设计核心,其设计逻辑在于将指令码中的Opcode和Funct两段代码作为识别指令内容的关键依据。本Controller仅基于P3要求的指令进行搭建,但具有良好的扩展性。
例如,ori指令的Opcode段为001101,因此可以用真值表达式将ori的输出表示为:
ori = ~opcode[5]&&~opcode[4]&&opcode[3]&&opcode[2]&&~opcode[1]&&opcode[0]
以此类推,最终写出所有指令的真值表达式,再进行对应接线。
值得注意的是,R类型指令的判断还需要结合Funct段代码进行识别。
具体端口及其功能如下表所示。
端口 | 方向 | 描述 |
Opcode | I | 6位数据段,其值为Instr[31:25] |
Funct | I | 6位数据段,其值为Instr[5:0] |
RegWrite | O | 寄存器写使能信号。有效时可向寄存器文件写入数据 |
MemWrite | O | 数据存储器写使能信号。有效时可向DM写入数据 |
EXTop | O | 数据扩展操作信号。1:符号扩展 0:零扩展 |
ALUop_0 | O | ALU控制信号的0位数据 |
ALUop_1 | O | ALU控制信号的1位数据 |
ALUop_2 | O | ALU控制信号的2位数据 |
ALUSrc | O | ALU中Op2的选择信号。1:立即数 0:寄存器 |
MemtoReg | O | 内存读取信号。有效时将数据存储器中的数据读入寄存器文件中 |
Branch | O | 跳转信号。有效时将PC跳转至立即数中存储的地址 |
ResDst | O | 寄存器写地址选择信号。有效时选择Instr[20:16],否则选择Instr[15:11] |
6.DM(数据存储器)
使用Logism内置的RAM实现,采用Separate load and store ports属性。
需要注意的是,ALU中计算得出的地址为PC的真实值,而RAM使用按字存储的存储逻辑,因此需要将ALU的计算结果右移两位再传入地址信号,选用result[6:2]即可。
str端口接入MemWrite信号控制是否写入数据;ld端口接入MemtoReg信号判断是否向寄存器文件中传入DM中的保存数值。
二、测试方案:
1. ALU功能测试
MIPS代码 操作码
ori $t0, $t0, 1 35080001
ori $t1, $t1, 2 35290002
addu $t2, $t0, $t1 01095021
subu $t3, $t1, $t0 01285823
lui $t4, 1 3c0c0001
2.DM功能测试
MIPS代码 操作码
ori $t0, $t0, 1 35080001
ori $t1, $t1, 2 35290002
sw $t1, ($t0) ad090000
lw $t2, ($t0) 8d0a0000
3.跳转功能测试
MIPS代码 操作码
beq $t0, $t1, jump 11090001
ori $t0, $t0, 1 35080001
jump
ori $t1, $t1, 2 35290002
三、思考题:
1.上面我们介绍了通过 FSM 理解单周期 CPU 的基本方法。请大家指出单周期 CPU 所用到的模块中,哪些发挥状态存储功能,哪些发挥状态转移功能。
答:状态存储功能模块:GRF、DM
状态转移功能模块:IFU、EXT、ALU、Controller
2.现在我们的模块中 IM 使用 ROM, DM 使用 RAM, GRF 使用 Register,这种做法合理吗? 请给出分析,若有改进意见也请一并给出。
答:合理。ROM的优点在于数据不能被改变,下次打开文件时指令仍然存在。而且,指令在程序运行期间不能篡改,而ROM正符合这一特点。RAM可读可写可复位,可以存放、修改数据,支持sw和lw指令,并且其速度快于ROM,可以方便快捷地对其存储值进行修改。GRF是临时存储单元,是CPU的核心部件,Logisim提供的寄存器功能与实际的CPU中相同,一切运算都必须通过寄存器完成,同时是实现寄存器模块效率最高的元件。而对于缺点方面,由于RAM采用按字存储的方式,因此不支持跨地址读写数据;并且一次只能读写一个地址(4字节),因此无法直接实现lb,sb等指令。
3.在上述提示的模块之外,你是否在实际实现时设计了其他的模块?如果是的话,请给出介绍和设计的思路。
答:暂时没有设计其他的模块,但在扩展指令的过程中将会加入ALUController的模块,对ALU的行为进行进一步选择。
4.事实上,实现 nop 空指令,我们并不需要将它加入控制信号真值表,为什么?
答:nop在CPU运行周期内不做任何行为,因此将所有控制信号置低电平即可确保CPU的各个部件不受任何影响,相当于执行了一个没有行为的周期,也即执行了nop空指令。
5.上文提到,MARS 不能导出 PC 与 DM 起始地址均为 0 的机器码。实际上,可以避免手工修改的麻烦。请查阅相关资料进行了解,并阐释为了解决这个问题,你最终采用的方法。
答:可以将PC的信号传入一个自动修改模块,来判断PC是否到达DM的地址区域。我们假设DM的起始地址为0x3000,那么我们可以构建一个判断模块:当PC小于0x3000时不做任何修改;PC大于0x3000时减去0x3000,以保证二者的地址没有重合部分。
6.阅读 Pre 的 “MIPS 指令集及汇编语言” 一节中给出的测试样例,评价其强度(可从各个指令的覆盖情况,单一指令各种行为的覆盖情况等方面分析),并指出具体的不足之处。
答:该测试样例的强度较高。先由最基本的可独立判断正误的指令进行验证,之后再在其基础上对更高层指令的正误进行验证,且各个指令的覆盖情况较为完整,对程序的验证能够起到较为准确的反馈,但目前我还没有找到该样例的不足之处。