简易流水线CPU全自动测试思路(Verilog)
这是一篇针对计组P5的自动化测试学习
写在开头
如果P3,P4大家有好好的做数据生成,那么P5的数据生成的工作将主要体现在对于数据生成器的优化上,对于数据执行器则无需太大的优化。 如果大家是从P5才开始做测试,那么建议看看笔者博客中P3 P4的自动化测试,很多工作在那时就已经完成,P5的自动化将迭代开发,在本篇博客中将不再赘述。命令行学习
1 | java -jar Mars_perfect.jar mc CompactDataAtZero nc db test.asm > mar.txt |
有小问号可能要问了,这和P4没啥区别啊这?其实区别就在于”db”,这是要求Mars执行时按延迟槽执行的指令。
Python实现(数据执行器)
基础功能迭代
迭代在P4的基础,我们只需要更改命令行学习中的那一条指令即可完成P5的数据执行器。
自动存储机器码
在P4中笔者只是将asm文件和比较结果进行了存储,并没有保存机器码,在P5笔者建议保留机器码文件,具体原因见后。1
2
3
4
5
6
7dir_name_3 = 'C:\\Users\\Unicorn\\Desktop\\P5\\P5exp\\P5全自动测试\\测试记录\\机器码文件\\'
f_1 = open('code.txt', "r")
list_temp = f_1.readlines()
f_2 = open(dir_name_3 + 'test'+str(test_order)+'.txt', "w")
f_2.writelines(list_temp)
f_1.close()
f_2.close()
功能:将生成的机器码文件Code.txt的内容复制到test+第几次测试.txt中,并放到dir_name_3的目录下。(此处使用绝对目录)
自动打包
机器码其实本来就是可有可无的东西,毕竟只要有asm,需要时我们就能手动生成,但是,考虑到从P5开始,计组提供了数据构造测试器,可以测试我们构造数据的强度,但数据构造测试器有一个极其阴间的一点是:
我们必须将每一个机器码打包成一个压缩包,再将整体压缩包打包成一个压缩包,对于命名还有很多的要求,比如P5的数据必须以P5开头等等,初期手搓的笔者已经累傻了,所以我们要自动化打包。
笔者打包使用360压缩,建议大家同样使用360压缩进行打包,因为360压缩支持命令行操作,我们因此得以利用Python的OS库调用360压缩自动化打包每一次生成的机器码文件,并命名。1
2
3
4
5zip(i+1)
def zip(test_order):
os.system("\"360压缩安装目录" -ar code.txt 打包好的压缩包存放位置\\"命名.zip")
例如:
os.system("\"C:\\Program Files (x86)\\360\\360zip\\360zip.exe\" -ar code.txt C:\\Users\\Unicorn\\Desktop\\P5\\P5exp\\P5全自动测试\\压缩文件\\P5_Q"+str(test_order)+".zip")
TIPS: - ar前面为360压缩安装目录,后面为打包好的压缩包存放位置。
数据生成器迭代思路
P5指令集:
{add,sub,ori,lw,sw,beq,lui,jal,jr,nop}
迭代思路:
• 整体构造思路
(1)需求制造规则,规则产生需求,不断循环迭代。在原有的数据生成上不断添加规则,增强随机性,直至取得一个良好的效果,说人话就是,一旦针对某一项需求设定了规则,就可能导致随机性或正确性的下降,而为了弥补这个随机性的下降,提高随机性,就产生了新的需求,就必然导致新规则的加入以提高随机性。或者是为了保证正确性,就需要添加新的规则,来修补漏洞。而修补漏洞也可能导致随机性的下降,有需要产生新的规则提高随机性。笔者的数据生成就是在这样的循环开发中不断走向圆满的。 (2)如果为了保持正确性,有需要固定生成的一些随机指令,举一个例子,笔者往往选择如下方式尽可能地提高随机性:1 | 指令类(cal_i,cal_r....) |
这样由于中间无关指令可能产生,可能不产生,实现了不同流水级转发的效果,且采用指令类解决冲突,提高了不同指令对同一指令的转发冲突解决效果。</br>
• 迎合流水线设计,缩减寄存器范围,提高转发和冲突的出现频率,增强测试强度。在这里我们将寄存器范围缩减到0-5。
注意:数据的生成中往往会因为0号寄存器的存在导致一些不可预料的错误,这些都需要我们特别增加生成规则,或许有人会考虑将寄存器范围缩减到1-6,但这样会面临着一些关于0寄存器读出,转发,写入的问题没法检测出来,降低了生成强度。• 加入负数测试,现在lw,sw都会随机生成负数立即数,用来检验EXT模块的正确性。
但,这样的加入仍然是有代价的,我们仍然需要添加规则,使得offset+寄存器的值>=0,否则将出错,笔者这里采用了一旦要这样做,就提前指定一个寄存器,为其赋大于该负数的值,然后再执行lw,sw1 | if (rs == 0) |
在这一段代码中,同时有两个规则在起作用,和我上文提到的两条都有关系,比如对rs=0的处理,如果rs是0,不对其进行处理,那么对rs赋值也是白赋值,rs的值恒为0,导致offset+[rs]小于0的问题的出现。Mars将会报错。
再比如对负数的处理,笔者随机生成两个数,进行比较,然后将小的那一个取反,大的那一个赋值给寄存器,保证了lw和sw寻址大于0.
• 保证延迟槽必无beq,j等跳转指令!维护理论正确性
延迟槽中是不能有跳转指令的,否则将是一个UB行为,而且大概率来讲,大家写的CPU将会直接死循环,而Mars则会正确运行。对于这条的处理相对比较简单,只要在随机延迟槽指令时,判断是否是跳转,如果是则再在指令池当中抽取一条即可!• 保证lw,sw在经过负数和寄存器值相加后字对齐,不会报错!
由于lw,sw是按字读取,所以地址加和后必须是4的倍数,否则Mars将报错,因此我们需要改变随机数的生成规则,这里笔者取巧了一下,将随机数的生成全部生成为4的倍数,且寄存器永远为$0,保证了字对齐,至于前面所提到的负数的情况,由于寄存器的值也是随机数生成的,所以加起来仍然是4的倍数,仍然字对齐。1 |
• 移除无用的nop
笔者认为,nop在流水线中是极其无用的指令,加入Nop将减少转发冲突的产生,因此笔者在生成指令时拒绝了Nop的生成 至于nop的功能正确性,嘛,这不还有计组平台的弱测帮你测试这个功能嘛~• 对于jal转发暂停的特殊解决
在P3,P4我们提到,为了保证程序运行的整体正确性,对于jal和beq的生成我们都是有规则的,且对于jal近乎是以固定于每一代码段的末尾的方式生成的,而这种非随机化的规则必然带来代价——转发冲突覆盖的不全面,首当其冲就是jal和各种指令的转发阻塞覆盖的缺失,且由于$31寄存器承担着正确运行的职能,我们还不能随意更改,必须在真正执行Jal前改回来,因此对于jal来说,我们依旧需要提供大量的规则,增强随机性,覆盖其转发阻塞。1 | -------------------------------------------------------- |
其余的特殊转发都很类似,只需不断地添加规则增强随机性,不断地修正新规则所带来的正确性的问题,最后实现覆盖。
• 其余特殊解决
由于时间过于久远,笔者也记不住自己究竟写了多少条规则限制数据生成,更详细的规则还请大家移步文章最后我的数据生成代码查看,谢谢!大家可能觉得这样会很复杂,但实话说,随机生成就是有着这样的缺陷,其指令的正确性只能通过不断地添加规则来实现,但不得不承认,随机生成法在上学这种没有很多空余时间的期间,是投入与产出性价比最高的测试方式。
效果展示
1 | "grade": { |
Python自动化测试框架代码
1 | import difflib |
数据生成器
太长了所以放链接:https://github.com/ForeverYolo/2022-BUAA-CO/tree/main/P5
如果使用了本数据生成器,笔者亲测可以通过2022年P5课上强测!
P5数据生成器代码行数:445行