# CPU 设计文档及思考题
# 设计草稿
# 顶层模块草稿
- 整个 CPU 模块大致分为上游和下游两个有限状态机。上游是 Moore 型的取指模块,输出将要执行的下一条指令;下游则是 Mealy 型的译码、执行、访存、回写全过程模块。
- 各个子模块以及粗略连接关系的初版草稿如下图所示。接下来,将要详细讨论各个模块的输入输出和连接关系。
# 顶层模块工程设计
- 每条指令相应数据通路如下表所示。
<table>
<tr>
<td rowspan = "2"> 指令 </td>
<td>IFU</td>
<td colspan = "4">GRF</td>
<td>EXT</td>
<td colspan = "2">ALU</td>
<td colspan = "2">DM</td>
</tr>
<tr>
<td>Beq</td>
<td>A1</td>
<td>A2</td>
<td>A3</td>
<td>WD</td>
<td>Imm</td>
<td>A</td>
<td>B</td>
<td>A</td>
<td>WD</td>
</tr>
<tr>
<td>add</td>
<td></td>
<td>IFU.Instr[25:21]</td>
<td>IFU.Instr[20:16]</td>
<td>IFU.Instr[15:11]</td>
<td>ALU.C</td>
<td></td>
<td>GRF.D1</td>
<td>GRF.D2</td>
<td></td>
<td></td>
</tr>
<tr>
<td>sub</td>
<td></td>
<td>IFU.Instr[25:21]</td>
<td>IFU.Instr[20:16]</td>
<td>IFU.Instr[15:11]</td>
<td>ALU.C</td>
<td></td>
<td>GRF.D1</td>
<td>GRF.D2</td>
<td></td>
<td></td>
</tr>
<tr>
<td>ori</td>
<td></td>
<td>IFU.Instr[25:21]</td>
<td></td>
<td>IFU.Instr[20:16]</td>
<td>ALU.C</td>
<td>IFU.Instr[15:0]</td>
<td>GRF.D1</td>
<td>EXT.ext</td>
<td></td>
<td></td>
</tr>
<tr>
<td>lw</td>
<td></td>
<td>IFU.Instr[25:21]</td>
<td></td>
<td>IFU.Instr[20:16]</td>
<td>DM.Data</td>
<td>IFU.Instr[15:0]</td>
<td>GEF.D1</td>
<td>EXT.ext</td>
<td>ALU.C</td>
<td></td>
</tr>
<tr>
<td>sw</td>
<td></td>
<td>IFU.Instr[25:21]</td>
<td>IFU.Instr[20:16]</td>
<td></td>
<td></td>
<td>IFU.Instr[15:0]</td>
<td>GRF.D1</td>
<td>EXT.ext</td>
<td>ALU.C</td>
<td>GRF.D2</td>
</tr>
<tr>
<td>lui</td>
<td></td>
<td></td>
<td></td>
<td>IFU.Instr[20:16]</td>
<td>ALU.C</td>
<td>IFU.Instr[15:0]</td>
<td></td>
<td>EXT.ext</td>
<td></td>
<td></td>
</tr>
<tr>
<td>beq</td>
<td>ALU.Zero</td>
<td>IFU.Instr[25:21]</td>
<td>IFU.Instr[20:16]</td>
<td></td>
<td></td>
<td></td>
<td>GRF.D1</td>
<td>GRF.D2</td>
<td></td>
<td></td>
</tr>
</table>
# 子模块
取指令单元 IRU
信号名 方向 描述 Clk Input 时钟 Reset Input 异步复位 PC 到 0x00003000 Beq Input 跳转 pc Output PC Instr Output 下一条指令 寄存器堆 GRF
信号名 方向 描述 Clk Input 时钟 Reset Input 异步复位 WE Input 写使能 A1[4:0] Input 操作数 1 地址 A2[4:0] Input 操作数 2 地址 A3[4:0] Input 操作数 3 地址 WD[31:0] Input 写入数据 D1[31:0] Output 读出数据 1 D2[31:0] Output 读出数据 2 控制器 Controller
信号名 方向 描述 Opcode[5:0] Input Funct[5:0] Input BeqEn Output 是否为 beq RegWrite Output GRF 写使能 ALUSrc Output B 为寄存器 / 立即数 ALUOp[3:0] Output ALU 模式 MemoryWrite Output DM 写使能 ExtOp[1:0] Output EXT 使能和模式 Mem2Reg Output ALU/DM 回写 算术逻辑运算单元 ALU
信号名 方向 描述 A[31:0] Input 操作数 1 B[31:0] Input 操作数 2 ALUOp[3:0] Input 模式 C Output 运算结果 Zero Output 结果是否为零 数据存储器 DM
信号名 方向 描述 Clk Input 时钟 En Input 写使能 A Input 读地址 D Input 写数据 Data Output 读出数据 结合上述模块输入输出和连接关系,画出如下顶层草稿:
# 二级子模块和模块实现
IFU
- PC(IFU)
- 寄存器加上异步复位到 0x00003000 功能,采用寄存器复位时复位至 0,因此需要在寄存器前将传入下一状态 - 0x00003000, 寄存器下游再 + 0x00003000。
- NPC(IFU)
信号名 方向 描述 PC Inuput 当前 PC Instr Input 当前指令 NPC Output 下一个 PC PC4 Output PC+4 - IM(IFU)
- 是一个 ROM,为了使得 PC 的输出与 DM 实际地址相匹配,将 pc-0x00003000 再右移两位得到 addr。
- PC(IFU)
Controller
- 参考理论课讲解和教程,由 Contrller_AND 译码,其输入为 Opcode 和 FunctCode,输出为各指令独热码。
- Controller_OR 释放控制信号,输入为指令的独热码,输出为上述各控制信号。
- 参考理论课讲解和教程,由 Contrller_AND 译码,其输入为 Opcode 和 FunctCode,输出为各指令独热码。
ALU
- 根据控制信号分别进行计算,结果由多路选择器选择。
# 测试方案
- 思路:
- 编写 MIPS 代码,在 MARS 上运行并查看寄存器和内存的数值;将 MIPS 代码转换为机器代码后导入 Logisim 的 ROM 中并运行,之后查看 logisim 当中的 GRF 和 DM 数据和 MARS 中是否一致。
- 根据教程,先测试不依赖于其他指令,但其他指令测试需要用到的指令,如 ori,lui; 先测试 sw,lw 指令,再测试 add,sub 等指令。
- 数据构造:
- 进行测试的数据应该覆盖广泛,包括正数与负数,绝对值大的数,和零附近的数字;测试覆盖全部寄存器。
- 测试数据应包含边界条件,如包括 0;包括 0xffff 等首位为 1 的立即数以测试扩展方式的正确性;跳转指令包含跳转到自己的指令。
- 应用大批随机数进行测试。
- 自动化
- 手动编写代码测试并比对的工作过于繁重,因此利用程序生成 MIPS 代码,如使用 C 测试 ori 测试指令。
其他相关代码(产生 add_sub):#include<stdio.h> #include<stdlib.h> #include<time.h> #define BATCH_SIZE 50 FILE *pCode; int main() { srand((unsigned int)time(0)); pCode = fopen("mips_code.txt","w"); char s[120]; int i; for (i = 0; i< 32; i++) { sprintf(s,"ori $%d,$%d,%d\n",i,i + 1,0); fprintf(pCode,s); } fclose(pCode); pCode = fopen("mips_code_ori1.txt","w"); for (i = 0; i< 32; i++) { sprintf(s,"ori $%d,$%d,%d\n",i,i + 1,0xffff); fprintf(pCode,s); } fclose(pCode); pCode = fopen("mips_code_ori2.txt","w"); for (i = 0 ; i < BATCH_SIZE; i++) { sprintf(s,"ori $%d,$%d,%d\n",rand()% 32,rand()%32,rand()%0x10000); fprintf(pCode,s); } fclose(pCode); return 0; }
#include<stdio.h> #include<string.h> int main() { int i,j,k=0; for ( i = 0; i < 4; i++) { for(j=i;j<4;j++,k++) { printf("add $s%d,$t%d,$t%d\n",0,i*2,j*2+1); printf("sw $s0,%d($0)\n",k*4); } } for ( i = 0; i < 4; i++) { for(j=i;j<4;j++,k++) { printf("sub $s%d,$t%d,$t%d\n",0,i*2,j*2+1); printf("sw $s0,%d($0)\n",k*4); } } return 0; }
.data
.text
ori $t0,$0,3
ori $t1,$0,4
lui $t2,0xffff
ori $t2,$t2,0xffff
lui $t3,0xffff
ori $t3,$t3,0xfffd
lui $t4,0x7fff
ori $t4,$t4,0xffff
lui $t5,0x7fee
ori $t5,$t5,0xfeff
lui $t6,0x8000
ori $t6,$t6,0x0000
lui $t7,0x8000
ori $t7,$t7,0x0003
add $s0,$t0,$t1
sw $s0,0($0)
add $s0,$t0,$t3
sw $s0,4($0)
add $s0,$t0,$t5
sw $s0,8($0)
add $s0,$t0,$t7
sw $s0,12($0)
add $s0,$t2,$t3
sw $s0,16($0)
add $s0,$t2,$t5
sw $s0,20($0)
add $s0,$t2,$t7
sw $s0,24($0)
add $s0,$t4,$t5
sw $s0,28($0)
add $s0,$t4,$t7
sw $s0,32($0)
add $s0,$t6,$t7
sw $s0,36($0)
sub $s0,$t0,$t1
sw $s0,40($0)
sub $s0,$t0,$t3
sw $s0,44($0)
sub $s0,$t0,$t5
sw $s0,48($0)
sub $s0,$t0,$t7
sw $s0,52($0)
sub $s0,$t2,$t3
sw $s0,56($0)
sub $s0,$t2,$t5
sw $s0,60($0)
sub $s0,$t2,$t7
sw $s0,64($0)
sub $s0,$t4,$t5
sw $s0,68($0)
sub $s0,$t4,$t7
sw $s0,72($0)
sub $s0,$t6,$t7
sw $s0,76($0)
- MARS 结果与 Logisim 结果的自动对比目前还没有实现。
# 思考题
- 思考题 1:
- 在 CPU 上游模块 Moore 机中,发挥状态存储功能的是程序计数器 PC,发挥状态转移功能的是计算下个 pc 的 NPC。
- 在 CPU 下游模块 Mealy 机中,发挥状态存储功能的是寄存器堆 GRF,CPU 之外的 DM;发挥状态转移功能的是控制器 Controller,运算器 ALU 和扩展单元 EXT 等。
- 思考题 2:
- 在本次实验的要求下,此设计是合理的。
- 本实验当中,IM 中的指令相当于预置完成,在 CPU 执行过程当中,无需进行修改,且指令数量庞大,因此使用只读的存储阵列 ROM 比较合理。
- GRF 存储三十二个三十二位二进制数,且要求可读可写。由于数量较少且对速度要求高,不宜使用大型阵列 RAM。另外由于访问地址是 5 位且 0 号要特殊处理,直接使用寄存器实现比较合适。
- DM 是要求可读可写的大型数据存储器,使用 RAM 比较合适。
- 在真实的处理器当中,IM 要求可写,否则始终只能运行同一程序。因此不能够使用 ROM。
- 在本次实验的要求下,此设计是合理的。
- 思考题 3:
- 尚未设计其他完整的模块,但是对一些模块预留了一些接口,程序具有可扩展性。比如考虑到部分跳转相关指令可能需要将 PC+4 存入 GRF,因此 IFU 添加了 PC4 输出,也增加了新控制信号输入以及 32 位数字输入。扩展了 GRF 的 WD,A3 选择器宽度。ALUOp 预留若干位。
- 思考题 4:
- nop 对应汇编语句为 <kbd>sll $0,$0,0</kbd> , 相当于不进行任何操作,RegWrite,MemWrite 等控制信号均为零。由于控制器的控制信号释放均由或门实现,故释放的控制信号均为 0 的 nop 指令无需加入控制信号真值表。
- 思考题 5:
- 测试点的数据不够强。指令集当中有 sub 和 nop 未进行测试。
- 各个指令的情况:
- ori 指令,lui 指令基本符合测试要求,但可以进行更多数据的测试。
- add 指令覆盖了正负数情况,但缺少 0 的测试,且其测试缺少较大整数(如计算完溢出,不过该情况无法在 MARS 上检查,可能要手动检查)和零附近整数的情况。
- sw,lw 指令测试较强。
- beq 指令覆盖了相等和不等的情况,但是只有向后跳转,没有跳转到自身和跳转到之前的情况。