这是一篇针对计组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
7
dir_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
5
zip(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
2
3
指令类(cal_i,cal_r....)
(可能随机0-2条无关指令) #无关指令:寄存器不相关
jr(冲突指令)

这样由于中间无关指令可能产生,可能不产生,实现了不同流水级转发的效果,且采用指令类解决冲突,提高了不同指令对同一指令的转发冲突解决效果。</br>

• 迎合流水线设计,缩减寄存器范围,提高转发和冲突的出现频率,增强测试强度。在这里我们将寄存器范围缩减到0-5。

注意:数据的生成中往往会因为0号寄存器的存在导致一些不可预料的错误,这些都需要我们特别增加生成规则,或许有人会考虑将寄存器范围缩减到1-6,但这样会面临着一些关于0寄存器读出,转发,写入的问题没法检测出来,降低了生成强度。

• 加入负数测试,现在lw,sw都会随机生成负数立即数,用来检验EXT模块的正确性。

但,这样的加入仍然是有代价的,我们仍然需要添加规则,使得offset+寄存器的值>=0,否则将出错,笔者这里采用了一旦要这样做,就提前指定一个寄存器,为其赋大于该负数的值,然后再执行lw,sw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (rs == 0)
{
rs++;
}
int imm1 = I;
int imm2 = I;
if (imm1 < imm2)
{
int temp = imm1;
imm1 = imm2;
imm2 = temp;
}
sub(rs, rs, rs);
ori(rs, rs, imm1, 0);
int imm3 = 0 - imm2;
printf("lw $%d,%d($%d)\n", rt, imm3, rs);
grf[rt] = dm[(imm3 + imm1) / 4];

在这一段代码中,同时有两个规则在起作用,和我上文提到的两条都有关系,比如对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
#define I ((rand() + rand()) % 40) * 4

• 移除无用的nop

笔者认为,nop在流水线中是极其无用的指令,加入Nop将减少转发冲突的产生,因此笔者在生成指令时拒绝了Nop的生成 至于nop的功能正确性,嘛,这不还有计组平台的弱测帮你测试这个功能嘛~

• 对于jal转发暂停的特殊解决

在P3,P4我们提到,为了保证程序运行的整体正确性,对于jal和beq的生成我们都是有规则的,且对于jal近乎是以固定于每一代码段的末尾的方式生成的,而这种非随机化的规则必然带来代价——转发冲突覆盖的不全面,首当其冲就是jal和各种指令的转发阻塞覆盖的缺失,且由于$31寄存器承担着正确运行的职能,我们还不能随意更改,必须在真正执行Jal前改回来,因此对于jal来说,我们依旧需要提供大量的规则,增强随机性,覆盖其转发阻塞。
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
--------------------------------------------------------
void sw_jal(int rs, int rt)
{
int imm = -12288;
printf("sw $%d,%d($%d)\n", rt, imm, rs);
}
--------------------------------------------------------
void lw_jal(int rs, int rt)
{
int imm = -12288;
printf("lw $%d,%d($%d)\n", rt, imm, rs);
}
--------------------------------------------------------
if (Reg % 4)
{
printf("jal Test_jal%d\n", i);
Choose(Stall_Jal, 0, -1, -1, -1,-1, 0);
printf("beq $0,$0,Test_beq%d\n", i);
Choose(Stall_Jal, 0, -1, -1, -1,-1, 0);
printf("Test_jal%d: jr $ra\n", i);
printf("Test_beq%d:\n", i);
}
Choose(Normal, 0, -1, -1, -1,-1, 0);
printf("jal Test%d\n", i);
Choose(Delayinstr, 0, -1, -1, -1,-1, 0);
-------------------------------------------------------
case 10:
ori(31, rt, I, 0);
break;
case 11:
add(rs, 31, rd);
break;
case 12:
lw_jal(31, rt);
break;
case 13:
sw_jal(31, rt);
break;
case 14:
int g = Reg % 4;
if (g==0)
{
sub(rs, rs, rs);
add(rs, 31, rd);
sub(31, 31, 31); // rd $31 rs=0
add(rd, 0, 31);
}
else if(g==1)
{
sub(rs, rs, rs);
add(rs, 31, rd);
sub(31, 31, 31); // rd $31 rs=0 $31=0
ori(rd, 31, 0, 0);
}
else if(g==2)
{
int immi = I;
sub(rt, rt, rt);
sw_diy(rt, 31, immi);
sub(31,31,31);
lw_diy(rt, 31, immi);
}
else
{
sub(rs, rs, rs);
add(rs, 31, rd);
add(31, 31, 31);
sub(rd, 31, 31); // rd $31 rs=0
}
break;
}
-------------------------------------------------------
void sw_diy(int base, int rt, int imm)
{
printf("sw $%d,%d($%d)\n", rt, imm, base);
}
-------------------------------------------------------
void lw_diy(int base, int rt, int imm)
{
printf("lw $%d,%d($%d)\n", rt, imm, base);
}
-------------------------------------------------------

其余的特殊转发都很类似,只需不断地添加规则增强随机性,不断地修正新规则所带来的正确性的问题,最后实现覆盖。

• 其余特殊解决

由于时间过于久远,笔者也记不住自己究竟写了多少条规则限制数据生成,更详细的规则还请大家移步文章最后我的数据生成代码查看,谢谢!

大家可能觉得这样会很复杂,但实话说,随机生成就是有着这样的缺陷,其指令的正确性只能通过不断地添加规则来实现,但不得不承认,随机生成法在上学这种没有很多空余时间的期间,是投入与产出性价比最高的测试方式。

效果展示

1
2
3
4
5
"grade": {
"forward": {
"average": 93.8735632183908,
"stall": {
"average": 100.0,

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import difflib
import os
import re
import sys
import time

import filestools
from filediff.diff import file_diff_compare
p_road = "C:\\Users\\Unicorn\\Desktop\\P5\\P5exp\\FlowCpu"
run_time = "100us"
xilinx_path = "G:\\ISE\\14.7\\ISE_DS\\ISE"
dir_name = 'C:\\Users\\Unicorn\\Desktop\\P5\\P5exp\\P5全自动测试\\测试记录\\mars文件\\'
dir_name_3 = 'C:\\Users\\Unicorn\\Desktop\\P5\\P5exp\\P5全自动测试\\测试记录\\机器码文件\\'
dir_name_1 = 'C:\\Users\\Unicorn\\Desktop\\P5\\P5exp\\P5全自动测试\\测试记录\\比较文件\\'
dir_name_2 = 'C:\\Users\\Unicorn\\Desktop\\P5\\P5exp\\P5全自动测试\\测试记录\\'
error = []


def generate(test_order):
# print("生成指令中" + '\n')
command = "C语言.exe"
os.system(command)
f_1 = open('test.asm', "r")
list_temp = f_1.readlines()
f_2 = open(dir_name + 'test'+str(test_order)+'.asm', "w")
f_2.writelines(list_temp)
f_1.close()
f_2.close()


def run_mar(test_order):
# print("生成机器码中" + '\n')
os.system("java -jar Mars_perfect.jar mc CompactDataAtZero a dump .text HexText code.txt nc test.asm")
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()
# print("生成标准答案中" + '\n')
os.system("java -jar Mars_perfect.jar mc CompactDataAtZero nc db test.asm > mar.txt")

# 延迟槽 os.system("java -jar Mars_perfect.jar mc CompactDataAtZero nc db test.asm > mar.txt")

def run_ise():
# print("运行ise中" + '\n')
file_list = []
for i, j, k in os.walk(p_road):
for file in k:
if file.endswith(".v"):
file_list.append(file)
with open(p_road + "\\mips.prj", "w") as prj:
for i in range(len(file_list)):
prj.write("Verilog work \"" + p_road + "\\" + file_list[i] + "\"\n")
with open(p_road + "\mips.tcl", "w") as tcl:
tcl.write("run " + run_time +";\nexit")
prj.close()
tcl.close()
os.environ["XILINX"] = xilinx_path
os.system(xilinx_path + "\\bin\\nt64\\fuse -nodebug -prj " + p_road + "\\mips.prj -o mips.exe mips_tb > compile_log.txt")
os.system("mips.exe -nolog -tclbatch " + p_road + "\\mips.tcl> raw_out.txt")


def process():
# print("转换ise答案中" + '\n')
myfriendmem = open("raw_out.txt", encoding="utf-8").read()
j = 0
while myfriendmem[j] != '@':
j = j + 1
mymem = ''
for i in range(j, len(myfriendmem)):
mymem = mymem + myfriendmem[i];
with open("verilog.txt", "w", encoding="utf-8") as file:
file.write(mymem)


def copy_file(name, target_road):
f_1 = open(name, "r")
list_temp = f_1.readlines()
f_2 = open(target_road + "\\" + name, "w")
f_2.writelines(list_temp)
f_1.close()
f_2.close()


def file_cmp(test_order):
co = open(dir_name_2 + 'result.txt', mode='a', encoding='utf-8')
co.write("第"+str(test_order)+"次比较结果:"+'\n')
print("第"+str(test_order)+"次比较结果:")
with open("mar.txt", "r") as out_1:
out_std = out_1.readlines()
out_std.remove('\n')
with open("verilog.txt", "r+") as out_2:
out_test = out_2.readlines()
with open(".\\log.txt".format(test_order), "w") as log:
flag = 0
if len(out_std) > len(out_test):
flag = 1;
else:
for i in range(len(out_std)):
if out_std[i] != out_test[i]:
flag = 1;
if flag:
print("Wrong Answer!")
co.write("Wrong Answer!"+'\n')
else:
print("Accepted!")
co.write("Accepted!"+'\n')
co.close()

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)
fb = open(dir_name_1 + out_file, mode='w', encoding='utf-8')
fb.write(result)

def zip(test_order):
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")


print("输入测试次数:")
test_times =int(input())
co = open(dir_name_2 + 'result.txt', mode='w', encoding='utf-8')
co.write("测试次数:"+str(test_times)+'\n')
co.close()
for i in range(test_times):
generate(i+1)
run_mar(i+1)
run_ise()
process()
zip(i+1)
# print("比较答案中" + '\n')
compare_file(r'verilog.txt', r'mar.txt', 'result'+str(i+1)+'.html')
file_cmp(i+1)
time.sleep(1)
print("Done!" + '\n')

数据生成器

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

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

P5数据生成器代码行数:445行