本页目录
一. 设计草稿
在正式开始动手搭建自己的单周期cpu之前,我首先翻阅了教学提供的ppt和许多往年学长的精品博客,以及结合了数字设计和计算机体系结构这本书,得出了一个设计cpu的大致思路,即模块化设计,将cpu拆分成一个个具有各自功能的模块,如下:
-
IFU
功能: 在时钟的上升沿根据PC所指向的地址取出一条指令,并根据指令变化PC
说明: 使用了一个32位寄存器储存PC, 使用了一个ROM储存要执行的指令,ROM规格为4096 * 32bit 因为PC是4的倍数,故使用2-13位作地址线即可
端口名称 端口格式 说明 clk input 时钟 rst input 异步复位 branch input 指令是否要branch jump input 指令是否要jump imm1 input[15:0] 16位立即数,用于计算branch后的地址 imm2 input[25:0] 26位立即数,用于计算jump后的地址 Instr output[31:0] 取出的指令 PC output[31:0] PC的值 实现如图:

-
Control
功能: 根据指令的内容确定要进行的操作
说明: 使用的是P3教程中推荐的与或逻辑
因为涉及到了指令的操作,不妨我们对要实现的
add,sub,ori,sw,lw,beq,lui,nop等指令进行一个简单的分析 (注:简化了译码过程,只给出结论)指令名称 数据写入 写入的地址 写入数据来自 ALU操作 ALU第二操作数 add 寄存器 Instr[15:11] ALU结果 加法 从GRF读取的RD2 sub 寄存器 Instr[15:11] ALU结果 减法 从GRF读取的RD2 ori 寄存器 Instr[16:20] ALU结果 或运算 扩展后的imm sw 内存 ALUresult 从GRF读取的RD2 加法 扩展后的imm lw 寄存器 Instr[16:20] Memory 加法 扩展后的imm beq —– —– —– 比较 从GRF读取的RD2 lui 寄存器 Instr[16:20] ALU结果 输出B 扩展后的imm 由此我们可以设计出Control 所需要的端口
端口名称 端口格式 说明 OP input[5:0] Instr[31:26] Funct input[5:0] Instr[5:0] ALUop output[2:0] ALU的操作 RegDst output 选择写入寄存器的地址 RegWrite output 是否对GRF进行写入操作 MemWrite output 是否对Mem进行写入操作 MemRead output 写入的数据是否来自Mem beq? output 是否是beq操作 ALUsrc output ALU第二操作数是否是imm SignExtend output 是否是符号扩展 LeftShift output 是否是左移操作 实现如图:

-
ALU
功能: 根据输入的操作类型对两个操作数进行运算,输出运算结果
说明: 目前支持的运算类型有
A,B,add,sub,or,cmp默认输出第一个运算数A 初版设计时我把比较器的一位输出扩展为32位与其它运算类型的输出使用了同一个多路选择器,而后觉得过于繁琐,在室友的建议下改为了多加了一个单独的一位输出端口名称 端口格式 说明 A input[31:0] 操作数A B input[31:0] 操作数B ALUop input[2:0] 操作类型 ALUresult output[31:0] 输出 equal? output A与B是否相等 实现如图:

-
GRF
功能: 根据输入的地址对寄存器堆进行读写操作
说明: 之前的P0课下已经搭建好了GRF,直接拿来使用即可 注意
$0寄存器不可被写入实现如图:

-
DM
功能: 根据输入的地址对内存进行读写操作
说明: 因其功能过少,所以没有设计单独的模块,直接使用一个4096*32bit的RAM实现,与CPU集成在一起 在真实的内存地址中,最小单位是8bit,所以我们在计算得到地址时,应该使用MemAddr[13:2]作为地址线访问内存
实现如图:

-
EXT
功能: 对立即数imm进行扩展
说明: 所以没有设计单独的模块,使用两个Extender和两个多路选择器实现,与CPU集成在一起 默认输出
0扩展,还有将imm作为高位扩展的功能实现如图:

经过以上的分析,我们对CPU的工作模式和通路连接就更清楚了,接下来的连线就更加简单了
如图:

二.测试方案
初步调试
针对每一条指令设计一个简单的测试样例,来观察指令本身的执行是否顺畅无误 如:
ori $t0, $0, 10000
sw $t0, 0($0)
这个简单的样例可以帮助测试两条指令的正确性,在测试完全部的单条指令后,我们再去构建规模更大的指令
综合调试
构建一个初具规模的样例,判断我们的CPU的连续执行指令的能力 以教程给的样例为例:
16进制码如下
341c0000
341d0000
34013456
00210820
8c010004
ac010004
3c027878
00411822
3c051234
34040005
00000000
ac85ffff
8c83ffff
10650001
1000000d
34670404
10e3000b
00000000
3c087777
3508ffff
00080022
34001100
00e65020
34080000
34090001
340a0001
010a4020
1109fffe
1000ffff
转译成对应的MIPS指令后
ori $28, $0, 0
ori $29, $0, 0
ori $1, $0, 0x3456
add $1, $1, $2
lw $1, 4($0)
sw $1, 4($0)
lui $2, 0x7878
sub $3, $2, $1
lui $5, 0x1234
ori $4, $0, 5
nop
sw $5, -1($4)
lw $3, -1($4)
beq $3, $5, 1
beq $0, $0, 13
ori $7, $3, 0x404
beq $7, $3, 11
nop
lui $8, 0x7777
ori $8, $8, 0xFFFF
sub $1, $0, $8
ori $0, $0, 0x1100
add $10, $7, $6
ori $8, $0, 0
ori $9, $0, 1
ori $10, $0, 1
add $8, $8, $10
beq $8, $9, -2
beq $0, $0, -1
我们可以使用Logisim和MARS对拍的方式来检验自己的结果是否正确
提交测试
测试样例没问题后,可以试着向课程提交自己的工程,毕竟这也算是一种很好的测试 结果:

嗯?怎么WA了这么多点!
冷静分析一下,能通过自己的测试样例的话应该不至于有如此多的错误,并且6386,6388,6391的错误大概率是同一个
那么我们就从这sw指令入手进行测试看看出了什么bug
这里我绕了一个弯路,因为每次要存入的数据都是0x00000000,我还以为是GRF的输出出了问题
最后发现要读取的MemData是要输入的数据而不是Mem输出的,真是可恶!
而另一个bug在简单排查beq后也很快的得到了解决,至此P3课下提交”愉快“通过
三.思考题
-
上面我们介绍了通过 FSM 理解单周期 CPU 的基本方法。请大家指出单周期 CPU 所用到的模块中,哪些发挥状态存储功能,哪些发挥状态转移功能。 与我们之前所学的FSM相比,单周期CPU显然更加复杂 以我的理解,CPU的状态包括PC的指向,GRF中各个寄存器储存的值,以及内存空间中储存的数据 对状态的定义有了了解后,我们便可以区分状态存储和状态转移的模块了 GRF,DM发挥的是状态储存功能 Control,ALU发挥的是状态转移 IFU比较特殊,它即负责储存PC的值,也负责计算下一次的PC,所以我认为它即有状态储存,也有状态转移
-
现在我们的模块中 IM 使用 ROM, DM 使用 RAM, GRF 使用 Register,这种做法合理吗? 请给出分析,若有改进意见也请一并给出。 合理 IM储存的是我们的指令部分,这一部分不能修改,所以适合使用ROM DM是我们的内存部分,我们要经常对内存进行读取和写入操作,适合用RAM GRF是我们的寄存器堆,我们同样也要经常对它进行读取和写入操作,并且Register的读取速度优于RAM,所以我们使用32个32位Register构成GRF
-
在上述提示的模块之外,你是否在实际实现时设计了其他的模块?如果是的话,请给出介绍和设计的思路。 无,以后可能会加入ALUcontrol等
-
事实上,实现
nop空指令,我们并不需要将它加入控制信号真值表,为什么?nop是指在这一周期内无操作,也就是CPU的状态不变(PC除外) 因此我们只要保证GRF和DM不被改写即可,也就是RegWrite和MemWrite置为0
-
阅读 Pre 的 “MIPS 指令集及汇编语言” 一节中给出的测试样例,评价其强度(可从各个指令的覆盖情况,单一指令各种行为的覆盖情况等方面分析),并指出具体的不足之处。 该测试样例的强度很高,且各个指令的覆盖情况较为完整,对程序的验证能够起到准确的反馈 目前没有找到该样例的不足之处
DISCUSSION
留言