# 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

    信号名方向描述
    ClkInput时钟
    ResetInput异步复位 PC 到 0x00003000
    BeqInput跳转
    pcOutputPC
    InstrOutput下一条指令
  • 寄存器堆 GRF

    信号名方向描述
    ClkInput时钟
    ResetInput异步复位
    WEInput写使能
    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
    BeqEnOutput是否为 beq
    RegWriteOutputGRF 写使能
    ALUSrcOutputB 为寄存器 / 立即数
    ALUOp[3:0]OutputALU 模式
    MemoryWriteOutputDM 写使能
    ExtOp[1:0]OutputEXT 使能和模式
    Mem2RegOutputALU/DM 回写
  • 算术逻辑运算单元 ALU

    信号名方向描述
    A[31:0]Input操作数 1
    B[31:0]Input操作数 2
    ALUOp[3:0]Input模式
    COutput运算结果
    ZeroOutput结果是否为零
  • 数据存储器 DM

    信号名方向描述
    ClkInput时钟
    EnInput写使能
    AInput读地址
    DInput写数据
    DataOutput读出数据
  • 结合上述模块输入输出和连接关系,画出如下顶层草稿:图 0

# 二级子模块和模块实现

  • IFU
    图 4

    • PC(IFU)
      • 寄存器加上异步复位到 0x00003000 功能,采用寄存器复位时复位至 0,因此需要在寄存器前将传入下一状态 - 0x00003000, 寄存器下游再 + 0x00003000。
    • NPC(IFU)
      信号名方向描述
      PCInuput当前 PC
      InstrInput当前指令
      NPCOutput下一个 PC
      PC4OutputPC+4
    • IM(IFU)
      • 是一个 ROM,为了使得 PC 的输出与 DM 实际地址相匹配,将 pc-0x00003000 再右移两位得到 addr。
  • Controller

    • 参考理论课讲解和教程,由 Contrller_AND 译码,其输入为 Opcode 和 FunctCode,输出为各指令独热码。图 1
    • Controller_OR 释放控制信号,输入为指令的独热码,输出为上述各控制信号。图 2
  • ALU

    • 根据控制信号分别进行计算,结果由多路选择器选择。

# 测试方案

  • 思路:
    • 编写 MIPS 代码,在 MARS 上运行并查看寄存器和内存的数值;将 MIPS 代码转换为机器代码后导入 Logisim 的 ROM 中并运行,之后查看 logisim 当中的 GRF 和 DM 数据和 MARS 中是否一致。
    • 根据教程,先测试不依赖于其他指令,但其他指令测试需要用到的指令,如 ori,lui; 先测试 sw,lw 指令,再测试 add,sub 等指令。
  • 数据构造:
    • 进行测试的数据应该覆盖广泛,包括正数与负数,绝对值大的数,和零附近的数字;测试覆盖全部寄存器。
    • 测试数据应包含边界条件,如包括 0;包括 0xffff 等首位为 1 的立即数以测试扩展方式的正确性;跳转指令包含跳转到自己的指令。
    • 应用大批随机数进行测试。
  • 自动化
    • 手动编写代码测试并比对的工作过于繁重,因此利用程序生成 MIPS 代码,如使用 C 测试 ori 测试指令。
    #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;
    }
    
    其他相关代码(产生 add_sub):
    #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 指令覆盖了相等和不等的情况,但是只有向后跳转,没有跳转到自身和跳转到之前的情况。
更新于

请我喝[茶]~( ̄▽ ̄)~*

Zeng Huaxu 微信支付

微信支付

Zeng Huaxu 支付宝

支付宝