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

写在开头

由于P7是P6的迭代开发,因此本次工作将主要体现在数据生成上,而且由于Mars的局限性,我们将编写两个测试程序(一个对拍Mars,一个对拍CPU),分别用于测试功能/异常和中断

命令行学习

导出0x4180位置处的机器码

1
java -jar Mars.jar mc LargeText a dump 0x00004180-0x00006000 HexText handler.txt nc test.asm

在Mars中,我们是无法导出0x4180位置的机器码的,但我们可以通过命令行进行导出,上面的命令行就是以16进制导出test.asm中的0x4180-0x6000位置处的机器码,导出到handler.txt文件中。

限制Mars运行指令数

1
java -jar Mars.jar mc LargeText nc db lg ex me 65536 test.asm > mar.txt

在P7中,最起码对于笔者来说,是经常会将程序指引向一个死循环的,所以我们需要给Mars设置指令条数上限,这样Mars才能结束运行,否则将会一直运行。指令中65536就是运行上限。

机器码合成

实现思路

在P7中,机器码被分为了两部分,正常程序部分从0x3000开始,异常处理部分从0x4180部分开始,但我们只能给CPU输入一个文件,所以我们就需要进行合成。
但合成仍然有一个问题,我们如何保证异常处理代码位于0X4180处?,毕竟很少有正常程序可以填充满0x3000-0x4180。在这种情况下直接合成则异常程序会低于0x4180。
因此我们需要添加Nop在正常程序后面,直到填充到0x4180位置,然后拼接异常处理机器码。

实现示例

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
#include<stdio.h>
char IM[5000][32];
int main()
{
FILE* in;
in = fopen("machine.txt", "r");
int i = 0;
while ((fscanf(in, "%s", IM[i])) != EOF)
{
i++;
}
fclose(in);
FILE* handler;
handler = fopen("handler.txt", "r");
int j = 1120;
while ((fscanf(handler, "%s", IM[j])) != EOF)
{
j++;
}
fclose(handler);
freopen("code.txt", "w", stdout);
for (int i = 0;i <= 5000;i++)
{
if (IM[i][0] == 0)
{
printf("00000000\n");
}
else
{
printf("%s\n", IM[i]);
}
}
}

Python实现(数据执行器)

异常/功能正确性测试

需要修改,全部修改是更改命令行为上述命令行,修改比较简单,示例就不放了

中断正确性测试

实现思路

需要修改,主要修改是将对拍对象由Mars改成另一个CPU,将Mars生成的机器码给两个CPU吃,然后对比这两个CPU的文件是否相同

实现示例

友情提醒:代码段可以点击右上角收起

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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
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\\P7\\P7exp\\FlowCpu"
d_road = "C:\\Users\\Unicorn\\Desktop\\P7\\P7exp\\P7jbw"
run_time = "100us"
xilinx_path = "G:\\ISE\\14.7\\ISE_DS\\ISE"
dir_name = 'C:\\Users\\Unicorn\\Desktop\\P7\\P7exp\\P7全自动测试_对拍\\测试记录\\mars文件\\'
dir_name_3 = 'C:\\Users\\Unicorn\\Desktop\\P7\\P7exp\\P7全自动测试_对拍\\测试记录\\机器码文件\\'
dir_name_1 = 'C:\\Users\\Unicorn\\Desktop\\P7\\P7exp\\P7全自动测试_对拍\\测试记录\\比较文件\\'
dir_name_2 = 'C:\\Users\\Unicorn\\Desktop\\P7\\P7exp\\P7全自动测试_对拍\\测试记录\\'
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.jar mc LargeText a dump .text HexText machine.txt nc test.asm")
os.system("java -jar Mars.jar mc LargeText a dump 0x00004180-0x00006000 HexText handler.txt nc test.asm")
os.system("机器码合成.exe")
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.jar mc LargeText nc db lg ex me 65536 test.asm > mar.txt")
# 循环结尾 os.system("java -jar Mars_perfect.jar mc CompactDataAtZero nc db me 65536 test.asm > mar.txt")
# 延迟槽 os.system("java -jar Mars_perfect.jar mc CompactDataAtZero nc db test.asm > mar.txt")

def run_ise_p():
# 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_txt > compile_log.txt")
os.system("mips.exe -nolog -tclbatch " + p_road + "\\mips.tcl> raw_out_p.txt")


def run_ise_d():
# print("运行ise中" + '\n')
file_list = []
for i, j, k in os.walk(d_road):
for file in k:
if file.endswith(".v"):
file_list.append(file)
with open(d_road + "\\mips.prj", "w") as prj:
for i in range(len(file_list)):
prj.write("Verilog work \"" + d_road + "\\" + file_list[i] + "\"\n")
with open(d_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 " + d_road + "\\mips.prj -o mips.exe mips_txt > compile_log.txt")
os.system("mips.exe -nolog -tclbatch " + d_road + "\\mips.tcl> raw_out_d.txt")


def process_p():
# print("转换ise答案中" + '\n')
myfriendmem = open("raw_out_p.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_p.txt", "w", encoding="utf-8") as file:
file.write(mymem)



def process_d():
# print("转换ise答案中" + '\n')
myfriendmem = open("raw_out_d.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_d.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("verilog_p.txt", "r") as out_1:
out_std = out_1.readlines()
# out_std.remove('\n')
with open("verilog_d.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;
log.write("error in line {}\n expected output is {}\nyour outout is {}\n\n".format(i, out_std[i], out_test[i]))
if flag:
print("Wrong Answer!")
co.write("Wrong Answer!"+'\n')
# os.makedirs(".\\test_log_file\\log_{}\\".format(i))
# copy_file("log.txt", ".\\test_log_file\\log_{}".format(i))
# copy_file("test.asm", ".\\test_log_file\\log_{}".format(i))
# copy_file("mar.txt", ".\\test_log_file\\log_{}".format(i))
# copy_file("verilog.txt", ".\\test_log_file\\log_{}".format(i))
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)
# with open(dir_name + out_file, 'w') as f:
# f.writelines(result)

def zip(test_order):
os.system("\"C:\\Program Files (x86)\\360\\360zip\\360zip.exe\" -ar code.txt C:\\Users\\Unicorn\\Desktop\\P7\\P7exp\\P7全自动测试_对拍\\压缩文件\\P7_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_p()
run_ise_d()
process_p()
process_d()
# zip(i+1)
# print("比较答案中" + '\n')
compare_file(r'verilog_p.txt', r'verilog_d.txt', 'result'+str(i+1)+'.html')
file_cmp(i+1)
time.sleep(3)
print("Done!" + '\n')

数据生成器迭代思路

P7指令集

{add,sub,and,or,slt,sltu,addi,andi,ori,lb,lh,lw,sb,sh,sw,mult,multu,div,divu,mfhi,mflo,mthi,mtlo,beq,bne,lui,jal,jr,nop,mtc0,mfc0,syscall}

迭代思路

整体思路

CPU功能正确性测试

根据P6的介绍,mtc0,mfc0,syscall均为安全指令,所以可以加入全局生成中。

CPU异常正确性测试

根据P7课程网站,我们会发现,异常的可能性是可以穷举的,而且考虑到一个很难受的事情:P6的数据生成器是绝对正确的(我们已经de过了万千Bug,是绝对跑不出问题的),所以我们直接在数据生成中将所有可能的异常加入,然后随机产生。

CPU中断正确性测试

课程组给的Mars是稍微有点问题的,首先它无法测试外部中断,而且计时中断测试,也会由于阻塞的问题导致和CPU行为不一致。因此,Mars在中断测试中无用,我们只能选择对拍。
我们只需修改数据生成,让其开头固定生成一段打开计时器中断的代码即可。

通用Handler

实现思路:

通用的Handler需要我们实现:
(1)检测是什么异常
(2)根据检测到的异常进入不同的异常处理模块
(3)处理异常
(4)恢复
笔者的通用Handler思路实现比较简单,主要是区分一下是否是延迟槽异常,区分一下是否是PC异常,如果是延迟槽异常,就PC+8(分为两次PC+4),如果是PC异常,就跳到开头的死循环位置。其余直接PC+4跳过这条异常指令。

实现示例:

笔者这里实现了一个通用的Handler模型,可以给大家使用。
当笔者实现后,CRT助教哥哥告诉笔者Github好像有现成的,笔者哭死T-T

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
.ktext 0x4180
mfc0 $k1,$13
andi $k1,$k1,0x80000000
beq $k1,$0,nodelay //判断是否是延迟槽里的异常
nop
mfc0 $k0, $14
andi $k0,$k0,0xfffffffc
addu $k0, $k0, 4
mtc0 $k0, $14
nodelay:
mfc0 $k0, $14
andi $k0,$k0,0xfffffffc
addu $k0, $k0, 4
mtc0 $k0, $14
subu $k1,$k1,$k1
ori $k1,$0,0x3008
subu $8,$8,$8
ori $8,$0,0x6ffc
slt $9,$8,$k0
beq $9,$0,nopc_0 //判断是否是PC异常
nop
mtc0 $k1,$14
addiu $k0,$k1,0
nopc_0:
subu $10,$10,$10
ori $10,$0,0x3000
slt $9,$k0,$10
beq $9,$0,nopc_1
nop
mtc0 $k1,$14
addiu $k0,$k1,0
nopc_1:

TIPS:使用笔者的通用Handler需要在代码开头实现一个beq的死循环,这是因为在笔者的通用Handler中,一旦出现PC错误,将直接跳到开头进行死循环刷到指令执行上限结束程序。

功能正确

写3个生成函数:

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
mfc0:
void mfc0(int rs)
{
int random = rand() % 3;
if (random == 0)
{
printf("mfc0 $%d $12\n", rs);
}
else if (random == 1)
{
printf("mfc0 $%d $13\n", rs);
}
else
{
printf("mfc0 $%d $14\n", rs);
}
}
mtc0:
void mtc0(int rs)
{
printf("mtc0 $%d $14\n", rs);
}
syscall:
void syscall() #这个其实可有可无,毕竟就一句话,在我们枚举异常所有情况时写在那里就行
{
printf("syscall\n");
}

加入全局生成即可完成功能正确性测试。

异常正确

枚举所有异常情况,随机生成:

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
case 32: //异常特加
printf("#Exc:\n");
radom = rand() % 6;
if (radom == 0) //ADEL
{
//PC异常
int Imm = 0;
if (Reg % 2 == 0)
{
Imm = 28668 + rand();
}
else
{
while (((Imm % 4) == 0) && (Imm >= 16768))
{
Imm = rand() % 28669;
}
}
sub(rs, rs, rs,1);
ori(rs, rs, Imm, 0);
printf("jr $%d\n", rs);
printf("nop\n");
}
else if (radom == 1) //ADEL
{
int random = rand() % 3;
if (random == 0) //取指不对齐
{
int Imm = rand() % 32548;
if ((Reg % 3) == 0) printf("lw $%d,%d($0)\n", rt, Imm);
else if ((Reg % 3) == 1) printf("lh $%d,%d($0)\n", rt, Imm);
else printf("lb $%d,%d($0)\n", rt, Imm);
}
else if (random == 1)
{
int a = rand() % 12; //取到错误地方
if (a == 0) printf("lh $%d,32512($0)\n", rt);
else if (a == 1) printf("lh $%d,32516($0)\n", rt);
else if (a == 2) printf("lh $%d,32520($0)\n", rt);
else if (a == 3) printf("lb $%d,32512($0)\n", rt);
else if (a == 4) printf("lb $%d,32516($0)\n", rt);
else if (a == 5) printf("lb $%d,32520($0)\n", rt);
else if (a == 6) printf("lh $%d,32528($0)\n", rt);
else if (a == 7) printf("lh $%d,32532($0)\n", rt);
else if (a == 8) printf("lh $%d,32536($0)\n", rt);
else if (a == 9) printf("lb $%d,32528($0)\n", rt);
else if (a == 10) printf("lb $%d,32532($0)\n", rt);
else if (a == 11) printf("lb $%d,32536($0)\n", rt);
}
else if (random == 2)
{
sub(rt, rt, rt,1); //取指超范围
ori(rt, rt, 2147480000 + I, 0);
int a = rand() % 3;
if (a == 0) printf("lw $%d,%d($%d)\n", rs, 2147480000 + I, rt);
else if (a == 1) printf("lh $%d,%d($%d)\n", rs, 2147480000 + I, rt);
else if (a == 2) printf("lb $%d,%d($%d)\n", rs, 2147480000 + I, rt);
}
}
else if (radom == 2) //ADES
{
int random = rand() % 3;
if (random == 0)
{
int Imm = rand() % 32548; //取指不对齐
if ((Reg % 3) == 0) printf("sw $%d,%d($0)\n", rt, Imm);
else if ((Reg % 3) == 1) printf("sh $%d,%d($0)\n", rt, Imm);
else printf("sb $%d,%d($0)\n", rt, Imm);
}
else if (random == 1)
{
int a = rand() % 13; //取到错误地方
if (a == 0) printf("sh $%d,32512($0)\n", rt);
else if (a == 1) printf("sh $%d,32516($0)\n", rt);
else if (a == 2) printf("sh $%d,32520($0)\n", rt);
else if (a == 3) printf("sb $%d,32512($0)\n", rt);
else if (a == 4) printf("sb $%d,32516($0)\n", rt);
else if (a == 5) printf("sb $%d,32520($0)\n", rt);
else if (a == 6) printf("sh $%d,32528($0)\n", rt);
else if (a == 7) printf("sh $%d,32532($0)\n", rt);
else if (a == 8) printf("sh $%d,32536($0)\n", rt);
else if (a == 9) printf("sb $%d,32528($0)\n", rt);
else if (a == 10) printf("sb $%d,32532($0)\n", rt);
else if (a == 11) printf("sb $%d,32536($0)\n", rt);
else if (a == 11) printf("sw $%d,32520($0)\n", rt);
else if (a == 12) printf("sw $%d,32536($0)\n", rt);
}
else if (random == 2) //取指超范围
{
sub(rt, rt, rt,1);
ori(rt, rt, 2147480000 + I, 0);
int a = rand() % 3;
if (a == 0) printf("sw $%d,%d($%d)\n", rs, 2147480000 + I, rt);
else if (a == 1) printf("sh $%d,%d($%d)\n", rs, 2147480000 + I, rt);
else if (a == 2) printf("sb $%d,%d($%d)\n", rs, 2147480000 + I, rt);
}
}
else if (radom == 3) //syscall异常
{
printf("syscall\n");
}
else if (radom == 4) //未知指令
{
printf("ftrap\n");
}
else if (radom == 5) //加减溢出
{
while (rs == 1 || rs == 0)
{
rs = Reg;
}
while (rt == 1 || rt == 0 || rt == rs)
{
rt = Reg;
}
while (rd == 1 || rd == 0 || rd == rs || rd == rt)
{
rd = Reg;
}
int a = rand() % 3;
sub(rs, rs, rs,0);
ori(rs, rs, 2147480000 + I, 0);
sub(rt, rt, rt,0);
ori(rt, rt, 2147480000 + I, 0);
if (a == 0)
{
add(rd, rs, rt,0);
}
else if (a == 1)
{
addi(rs, rd, 2147480000 + I,0);
}
else //测减法溢出的同时测连续异常的处理能力
{
sub(rs, rt, rt,0);
sub(rs, rt, rt,0);
sub(rs, rt, rt,0);
sub(rs, rt, rt,0);
sub(rs, rt, rt,0);
}
}
printf("#ExcEnd:\n");
}
}

以上包含了所有异常的可能,我们在生成时在其中抽取异常生成即可,我将它称为异常池。

注意减法溢出,笔者在这里写了很多个sub是为了检测CPU连续处理异常的能力

中断正确

因为我们是全自动测试,所以我们无需仔细考虑中断和异常同时发生的各种情况,因为我们的样本数据够多,随机生成总会碰到的,所以我们直接采用计时器生成,每隔5个指令进行一次中断,因此我们需要配置计时器中断,固定生成以下代码在开头:

1
2
3
4
printf("ori $t1,11\n");
printf("ori $t2,20\n");
printf("ori $t3,0xFC01\n");
printf("mtc0 $t3,$12\n");

之后正常对拍即可

结语

P7对于自动化测试的压力还是比较大的,毕竟课上就是对P7的强测,只要强测全部过了都可以直接交卷了,所以课下做的好,课上5分钟。
课上分为三个强测:功能,异常,中断强测,因此根据Mars发挥的作用笔者也做了两个测试程序:功能/异常测试,中断测试。

笔者的自动化测试甚至测出了强测没考的点(x
可惜,P7没有P5,P6那种覆盖率测试程序QAQ,一人血书求出P7覆盖率测试程序。

数据生成器

两个都太长了所以放链接:https://github.com/ForeverYolo/2022-BUAA-CO/tree/main/P7
如果使用了本数据生成器,笔者亲测可以通过2022年P7课上强测!
P7功能\异常数据生成器代码行数:1178行
P7中断数据生成器代码行数:1229行