这是一篇针对计组P3的自动化测试学习

写在开头

通过阅读本文,你可以了解到如何蒟蒻合理生成不会陷入死循环不会报错的指令,并了解到对logisim进行自动化测试的相关方法。

全自动测试的开端:设施准备

要完成logisim的自动测试,需要使用到Logisim的命令行操作对输出进行重定向,所以我们需要.jar版的logisim来实现,该文件上网找找就有,我也已经上传至百度网盘,提取码:ozsw,需要的uu可以自取。

命令行学习

logisim的命令行官方文档是有介绍的,感兴趣的可以去看看:
Logisim官方介绍
这里我直接贴出有用的命令行,并对其做出解释,方便速通。

1
java -jar logisim-generic-2.7.1.jar CPU名称.circ -tty table > 输出.txt

这句话的意思是运行该.circ文件并将顶层模块的输出全部进行输出,注意是顶层模块子模块不会输出任何结果!
意味着若你想得到你需要的数据,你就需要在主模块将其作为输出进行输出。
对于一个xxxx.asm程序,我们需要通过命令行运行mars使其输出机器码文件:
1
java -jar Mars4_5.jar xxxx.asm nc mc CompactTextAtZero a dump .text HexText 机器码.txt

对于cpu的额外设置

有了上述指令,程序就可以运行了,并且我们可以在程序的同一目录下得到输出文件,但此时会出现新的问题,该cpu会一直运转,不会停止,这时候就需要对其提供halt指令(源自官方文档),当halt信号为1时,结束程序,可以如下实现:
为在顶层模块采用计数器设置计数最大值(你需要运行的周期),设置计数器为达到最大值保持,这时候进位输出端便会输出1,将其作为halt信号即可结束程序
P3图片1

官方介绍:

P3图片2
好像这就是评测机的评测机制的一部分(思考)

不用担心这里的halt信号也会作为输出不断输出,logisim会忽略halt信号的输出。

机器码读入ROM

Logisim官方文档也提供了读入的方法,但其实不是很直观,观察.circ文件中rom的表示方式,我们可以使用正则表达式对其匹配并强制替换:

1
addr/data: 10 32([\s\S]*?)</a>  #正则表达式

注意非贪婪匹配,贪婪匹配会导致该正则表达式匹配到多余的内容,替换后使得文件损坏。
P3图片3

替换前

P3图片4

替换后

python实现思路

python提供了os模块代替手动执行命令行,示例如下:

1
os.system("java -jar Mars4_5.jar test.asm nc mc CompactTextAtZero a dump .text HexText command.txt")

python提供了re模块用于正则表达式匹配替换,示例如下:
1
mymem = re.sub(r'addr/data: 10 32([\s\S]*?)</a>', "addr/data: 10 32\n" + content + "</a>", mymem)

执行完毕后得到的结果文件可以与同学对拍。
而python也提供了filediff.diff模块用于文本差异比对,生成html文件,若有区别,则会标红,这里是由于文本一致所以无色。
P3图片7
(左右各为两个CPU输出结果) 有了这些我们就可以写出.py程序直接得到最终的比较结果

自动化数据生成思路

测试讲完了,但测试数据怎么来?这就需要我们自动化生成测试数据。
可以选择c,c++,java,python生成,因为它们都支持命令行的重定向操作,最终用python指挥它们干活即可。

具体思路:

•开局先使用ori或lui给31个寄存器用随机数乱赋初值,确保后面的指令运行时不会拿0加来加去,导致没有测试意义。
•构造一个字符串数组,存指令名称,用随机数生成的数字确定本次随机的指令,然后分别实现各个指令的生成,对于寄存器和立即数选择随机生成。
•对于一般指令这样做完全够用,但beq和jal和jr则需要仔细考虑,不然会导致程序死循环或寻址到不合适的地方而报错。

在此提供我的一些思路:
•以段生成代码,将jal固定出现在入口末尾,jr固定出现在出口末尾,确保jal和jr的跳转不会导致死循环
•对于beq的生成,其实有一个最简单粗暴的方式是beq往后跳转,这样不满足条件,指令也会往下执行,满足,也会往下执行,不会出现问题。当然如果一定要实现往前跳转,可以固定一段代码段,将其作为计数模块,当其执行超过一定次数时直接跳出此段代码。

P3图片8
P3图片9
P3图片10
这部分做好后同样利用Python即可将其与上述测试程序链接自动生成代码自己对拍出结果,实现自动化测试

Python代码放送

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import difflib
import os
import re
import sys
import filestools
from filediff.diff import file_diff_compare
# 0.自动化生成数据
print("生成指令中"+'\n')
command = "C语言.exe"
os.system(command)

# 1.先运行Mars生成机器码
print("生成机器码中"+'\n')
os.system("java -jar Mars4_5.jar test.asm nc mc CompactTextAtZero a dump .text HexText command.txt")

# 2.运行写好的circ文件将里面的ROM值更改为上述输出的机器码
print("添加指令至单周期cpu中"+'\n')
content = open("command.txt").read()
mymem = open("单周期cpu.circ", encoding="utf-8").read()
mymem = re.sub(r'addr/data: 10 32([\s\S]*?)</a>', "addr/data: 10 32\n" + content + "</a>", mymem)
with open("单周期cpu镜像.circ", "w", encoding="utf-8") as file:
file.write(mymem)
print("添加指令至另一个单周期cpu中"+'\n')
myfriendmem = open("另一个单周期cpu.circ", encoding="utf-8").read()
myfriendmem = re.sub(r'addr/data: 10 32([\s\S]*?)</a>', "addr/data: 10 32\n" + content + "</a>", myfriendmem)
with open("另一个单周期cpu镜像.circ", "w", encoding="utf-8") as file:
file.write(myfriendmem)

# 3.运行logisim输出结果
print("运行单周期cpu中"+'\n')
command = "java -jar logisim-generic-2.7.1.jar 单周期cpu镜像.circ -tty table > 单周期cpu结果.txt"
os.system(command)
print("运行另一个单周期cpu中"+'\n')
command = "java -jar logisim-generic-2.7.1.jar 另一个单周期cpu镜像.circ -tty table > 另一个单周期cpu结果.txt"
os.system(command)


# 4.找人对拍
print("正在比较结果,时间可能较长,请耐心等待"+'\n')
def read_file(filename):
try:
with open(filename, 'r') as f:
return f.readlines()
except IOError:
print("ERROR: 没有找到文件:%s或读取文件失败!" % filename)
sys.exit(1)


def compare_file(file1, file2, out_file):
file1_content = read_file(file1)
file2_content = read_file(file2)
d = difflib.HtmlDiff()
result = d.make_file(file1_content, file2_content)
with open(out_file, 'w') as f:
f.writelines(result)


compare_file(r'单周期cpu结果.txt', r'另一个单周期cpu结果.txt', r'result.html')
file_diff_compare("单周期cpu结果.txt","另一个单周期cpu结果.txt")
print("比较结束,测试完毕,请查看结果"+'\n')
#5.到文件中查看结果

数据生成器

太长了所以放链接:https://github.com/ForeverYolo/2022-BUAA-CO/blob/main/P3

结语

这篇文章是写于作者学习P3之时,回过头来还是觉得当时的自己略显稚嫩,所以笔者在这里起一个抛砖引玉的作用,希望对大家测试logisim有所启发!

如果使用了本数据生成器,笔者亲测可以通过2022年P3课上强测!

P3数据生成器代码行数:304行