1. 分析指令集
- 经过分类,指令共包含R型指令和I型指令
- 列出其对应的32位机器码,寻找共性差异
R型指令:
Type |
Op |
Rs |
Rt |
Rd |
Shamt |
Func |
解释 |
add |
000000 |
(5) |
(5) |
(5) |
00000 |
100000 |
相加(rs+rt->rd) |
sub |
000000 |
(5) |
(5) |
(5) |
00000 |
100010 |
相减(rs-rt->rd) |
|
|
|
|
|
|
|
|
I型指令:
Type |
Op |
Rs |
Rt |
Immediate |
解释 |
ori |
001101 |
(5) |
(5) |
(16) |
或运算(rs|immediate->rt) |
lui |
001111 |
00000 |
(5) |
(16) |
立即数加载至高16位({immediate||{16{1’b0}}}->rt) |
Type |
Op |
Rs |
Rt |
Offset |
解释 |
lw |
100011 |
(5) |
(5) |
(16) |
加载字(rs+offset在memory中data->rt) |
sw |
101011 |
(5) |
(5) |
(16) |
保存字(rt->rs+offset在memory中的data) |
beq |
000100 |
(5) |
(5) |
(16) |
rs与rt相等则PC偏移offset*4 |
- 可以分析得到,通过Op能够区分R型指令和I型指令
- R型指令中通过Func再具体区别运算类型
2. 分析Controller
端口 |
作用(0) |
作用(1) |
|
RegDst |
GRF写入端地址选择Rt |
GRF写入端地址选择Rd |
|
ALUSrc |
ALU输入端B选择R[rt] |
ALU输入端B选择Signext |
|
MemtoReg |
GRF写入端数据来自ALU输出 |
GRF写入端数据来自DM输出 |
|
RegWrite |
无 |
把数据写入GRF中对应寄存器 |
|
MemWrite |
无 |
数据存储器DM写输入 |
|
nPC_Sel |
PC=PC+4 |
PC=PC+4+移位 |
|
ExtOp |
|
|
选择扩展类型 |
ALU_Op |
|
|
选择计算类型 |
3.设计PC状态转移模块
- 经过分析,PC只会转移到两个状态:1. PC+4 2. PC+4+移位
- 1.状态是除beq以外其他指令的转移
- 2.状态时beq成立时的转移
- PC是保存的状态
- Moore状态机,输出是PC所对应的ROM中的地址
- reset异步复位
type |
name |
位宽 |
input |
PCop |
1 |
input |
PC |
[15:0] |
input |
beq |
[15:0] |
output |
instruction |
[31:0] |
因此可设计如下PC状态机:

其中PCshift模块为:

需要注意,因为PC实际是从0x00003000开始的,而ROM是从0x00000000开始的,因此二者有插值。在获取PC值的时候,需要加上初始地址0x00003000
4. 设计splitter
- 根据不同指令从对应位数拆分出相关信息,用于后续操作。

5. 设计ALU
ALU端口设计:
type |
name |
位宽 |
input |
num1 |
[31:0] |
input |
num2 |
[31:0] |
input |
ALUop |
[3:0] |
output |
ans |
[31:0] |
ALUop解释:
ALUop |
解释 |
0000 |
加法 |
0001 |
减法 |
0010 |
或运算 |
0011 |
相等 |
0100 |
小于 |
0101 |
大于 |
ALU实现电路:

6. CU
端口设计:
type |
name |
位宽 |
解释 |
input |
op |
[5:0] |
机器码高6位 |
input |
func |
[5:0] |
机器码低6位 |
output |
RegDst |
1 |
GRF被写入寄存器是rt还是rd。0是rt,1是rd |
output |
ALUSrc |
1 |
ALU第二个运算值是rt还是立即数。0是rt,1是立即数 |
output |
mentoReg |
1 |
GRF写入值是ALUans还是DMans。0是ALUans,1是DMans |
output |
Regwrite |
1 |
是否写入GRF |
output |
Menwrite |
1 |
是否写入RAM |
output |
PCsel |
1 |
当前指令是否是beq |
output |
Extop |
[1:0] |
扩展操作类型类型,详见Extender |
output |
ALUop |
[3:0] |
ALU运算类型,详见ALU |
- 内部电路采用最小项表达式判断法,先判断Op,再判断Func

- 之后根据每种指令对应的输出来用或逻辑接线。

- 对于ALUop的判断,再次采用最小项表达式的方法。

7. Extender
端口类型:
type |
name |
位宽 |
input |
num |
[15:0] |
input |
Extop |
[1:0] |
output |
ans |
[31:0] |
Extop类型解释:
Extop |
解释 |
00 |
zero扩展 |
01 |
sign扩展 |
10 |
低16位移至高16位,补0 |
11 |
sign扩展,左移2位,补0 |
实现过程: |
|
 |
|
8. 控制信号操作电路
- 凭借splitter提供的寄存器信息、立即数信息,和CU提供的控制信号,我们可以组合连接GRF与RAM,以达到完成指令操作的效果。

- 通过CU的控制信号来决定MUX的输出是什么,从而特性化处理不同指令的要求。
9. 输出

通过tunnel获取相应输出的结果,并从端口输出。(PC要加上初始地址)
10. 测试方案
- 理论检验。一个个指令单独检测,从CU产生的控制信号,到这些控制信号实际所控制的内容,分别进行检查核对,确保指令正确执行。
- 数据检验。利用教程平台提供的机器码,导入ROM中运行CPU,通过翻译机器码获取MIPS指令,并利用MARS计算出正确结果,与CPU中的GRF和RAM核对信息。
11. 思考题
- CPU中有两个FSM。一个是由PC和PCop组成的FSM,是一个Moore状态机,输出是当前PC所对应的指令机器码;另一个是GRF和RAM与Splitter和CU组成的FSM,是一个Mealy状态机,输出是众多信号。PC、GRF、RAM起到状态存储功能,PCshifter、ALU、Extender实现状态转移功能(即计算出下一状态)。
- 我认为比较合理。ROM是只读寄存器,不可修改,其存储的是指令的机器码,而程序指令一经写好就是固定的,因此可以应用。GRF是寄存器堆,用来对应32个寄存器,能起到数据存储的作用,我认为也比较合理。RAM是随机访存寄存器,且读出和写入操作分离,满足内存中访问和存储的操作,因此也合理。但也有一些问题,比如 $sp寄存器是栈指针,其初始值并不是0,而是一个地址。进行入栈操作时会先 $sp自减4字节,之后再在内存中 $sp的位置存储数据。而寄存器默认初始值是0,因此需要注意特殊寄存器赋初值的问题。
- 除了教程中提及的模块,我还设计了PCshifter模块,其作用是根据PC和PCop来计算出下一周期的PC。利用PCop当MUX的sel信号,根据PCop的不同,决定不同的PC输出

- nop的作用是让当前电路不发生任何改变。当CU中不设计nop指令时,出现nop指令后CU所有控制信号都是0,包括Regwrite和Menwrite,此时不会对GRF和RAM进行任何改变,满足nop指令的作用,因此不需要设计。
- 该测试样例的覆盖率和强度都较高,但是对于特定样例、边界情况的测试还有些不足。
ori指令:应该增加立即数的符号位是1的测试样例,以判断是否是无符号扩展。
lui指令:测试较为全面
add指令:应增加溢出情况测试
sw指令:应增加对某一地址重复赋值的测试,应变换寄存器中的值测试偏移量
lw指令:应变换寄存器中的值测试偏移量
beq指令:应增加label是beq指令的下一条指令的跳转,测试跳转情况下PC是否+4再偏移。