两种可能的测试数据生成方式

unitTest

分类

  • 如同java中的单元测试,我们将常规指令(不包含什么lwsocwp…)分为五个类别
    1. 寄存器赋值类型指令(set)
      • 例如lui,能直接通过立即数对寄存器赋值(这里我还加入了ori配对成li),将来可能addisubi也是这里的一部分
    2. 寄存器运算类型指令(arth)
      • 例如add,通过两个寄存器相互运算得到值的指令,更通俗就是R型指令
    3. 存取类型指令(store)
      • 例如lw,内存参与的指令
    4. 分支类型指令(branch)
      • 例如beq,有条件跳转(有条件跳转并链接)指令
    5. 跳转类型指令(jump)
      • 例如jal,无条件跳转(无条件跳转并链接)指令
  • 接下来将我们已有的指令集分类加入,并按照set -> arth/store -> branch -> jump的顺序依次进行测试,已测试的指令将出现在后面的指令测试中测试后续指令,所以测试顺序也是非常重要的

测试思路

  • set
    • 先测试set类指令,不仅是因为它们不需要依托其他指令的正确性,而且后续所有边界数据的测试都需要提前赋值,测试思路即多次对$0 ~ $31寄存器赋随机值
  • arth
    • 对于参与运算的数据,分为inf(最大、最小值左右)、zero(0左右)、random(其他随机数),我们对任意的两个寄存器选择上面某种方式赋值,再从此次数据集中随机挑选一条指令参与
  • store
    • 同理,选择基地址寄存器,循环随机生成寄存器值和偏移量,满足下面条件才结束循环
      • 若选出了$0,要求生成0 < offset < 3072
      • 若不是$0,要求0 < grf[base] + offset < 3072
    • 最后乘4,如果是sbsh指令的指令考虑再给offset加上一个小的随机数
  • branch
    • 选择测试的branch指令,并随机决定是否跳转
    • 若不跳转
      • 随机给两个寄存器赋不满足条件的值,然后结束
    • 若跳转
      • 向前跳,借鉴for循环的思路,但只向前跳一小步,主要测试能否向前跳
        li $reg1 num1
        li $reg2 num1 - num2
        li $reg3 num2
        Brach:
        add $reg2, $reg2, $reg3
        beq $reg1, $reg2, Branch
      • 向后跳更加随意
    • 关于死循环
      • 我的数据生成器是可以生成死循环指令,目前只实现了beq死循环,规定死循环只在测试内容的最后5%才有可能出现,并且出现死循环即跳出生成数据返回,死循环永远在最后一个指令
      • 所以我的测评机在把代码丢给mars前会先检测最后一条指令(mars死机不会给出十六进制数据。。。),如果是死循环指令将会切除,并在提供给我们CPU的十六进制数据中加入死循环指令和一条set指令,测试CPU是否真的死循环了
      • 然后我发现死循环某种程度也是向前跳,其实不用单独测试,又把这个功能去掉了
  • jump
    • 对于一个无条件跳转指令,首先他要能跳
    • 对于借助标签的跳转指令
      1. 采用for循环的方式测试能否正常跳转
        li $reg1, num1
        li $reg2, num2
        li $reg3, 1
        Jumpstart:
        beq $reg1, $reg2, Jumpend
        add/sub $reg1, $reg1, $reg3
        jump Jumpstart
      2. 采取连续跳跃,提前生成能够跳出的label组合
        Jump0_0:
        jal Jump0_3
        Jump0_1:
        jal Jump0_4
        Jump0_2:
        jal Jump0_5
        Jump0_3:
        jal Jump0_2
        Jump0_4:
        jal Jump0_1
        Jump0_5:
    • 对于借助寄存器的跳转指令,将与借助标签的跳转指令联动测试,采取类似函数调用的格式测试
      beq $0, $0, BranchforJ1_0
      Jump1_0:
      add $24, $31, $0
      jr $24
      BranchforJ1_0:
      jal Jump1_0

单周期与流水线

  • 对于单周期,单元测试已经可以完备测试,主要是因为单周期指令的数据通路在同一时钟周期仅有一条指令占有,各个指令之间的数据通路不互相影响
  • 对于流水线,我们当然也可以采用单元测试的方式,不过首先指令的格式需要进行细微的调整
    lui $reg1, num1
    nop
    nop
    nop
    nop
    ori $reg1, $reg1, num2
    nop
    ...
    • 通过插入nop消除指令之间的互相影响,达到测试数据通路以及部分控制单元的目的,但也仅仅只能测试这两个
    • 对于转发电路以及冲突单元的测试还需依靠更加随机测试

randomTest

单周期

  • 单周期的随机测试看起来没什么用途,单元测试已经可以完备测试CPU了(虽然我还是实现了随机测试

流水线

  • 首先说明,笔者还未将这部分内容加入测评机,仅仅是一个蓝图()

  • 单元测试有一个最大的缺点即,对于各类指令的测试往往需要预先赋值,这大大影响了指令序列的随机性,但是过于随机的指令序列将严重影响storebranchjump类指令的测试效果

    • 这里引入一个寄存器数组、内存数组,在我们生成数据时跟踪,寄存器、内存的变化
    • 再引入一张表(有多少个条件跳转指令则引入多少张表),记录某两个寄存器是否已经满足跳转关系
    • 听起来有点像自己写mars,我现在的函数是下面这个样子
  • 我们可以将一次随机测试分为数段,每段的开头进行预先赋值,保证一些条件(如至少8个寄存器处于极端数据等等),然后遇到终止条件结束这段测试,对不满足条件的部分重新赋值,终止条件可以如下:

    1. 少于3个寄存器处于极端数据
    2. 少于3个寄存器处于-65535 ~ 65535 + 3072
    3. 少于3个寄存器满足跳转关系
    • 由此,我们跟踪了一部分数据,可以摆脱先赋值再操作这种单元测试,对于lwsw之类的可以从条件2的寄存器中直接使用
    • 向后跳转部分可以从满足条件关系中直接使用
    • 同时保证了寄存器组数据的随机性
  • 如果单元测试足够充分的话,完全可以在随机测试中只测试指令之间的影响,不在乎branch类指令向前还是向后,不在乎arth类指令的数据是否包含极端,这些都是原本数据通路的问题,不必在randomTest中反复测试啦


结语

  • 看起来流水线的数据生成还有很长一段路要走啊,希望能实现蓝图吧
  • 数据生成与处理必然有诸多不当之处,希望大家指正。同时,如果在流水线数据方面有思路的同学,请和我交流(orz)
    单周期测评机