Mercurial > repos > blastem
comparison gentests.py @ 214:9126c33cc33c
Add test generator, builder and runner
author | Mike Pavone <pavone@retrodev.com> |
---|---|
date | Fri, 19 Apr 2013 09:29:37 -0700 |
parents | |
children | acd29e2664c6 |
comparison
equal
deleted
inserted
replaced
213:4d4559b04c59 | 214:9126c33cc33c |
---|---|
1 #!/usr/bin/env python | |
2 | |
3 def split_fields(line): | |
4 parts = [] | |
5 while line: | |
6 field,_,line = line.partition('\t') | |
7 parts.append(field.strip()) | |
8 while line.startswith('\t'): | |
9 line = line[1:] | |
10 return parts | |
11 | |
12 class Program(object): | |
13 def __init__(self, instruction): | |
14 self.avail_dregs = {0,1,2,3,4,5,6,7} | |
15 self.avail_aregs = {0,1,2,3,4,5,6,7} | |
16 instruction.consume_regs(self) | |
17 self.inst = instruction | |
18 | |
19 def name(self): | |
20 return str(self.inst).replace('.', '_').replace('#', '_').replace(',', '_').replace(' ', '_').replace('(', '[').replace(')', ']') | |
21 | |
22 def write_rom_test(self, outfile): | |
23 outfile.write('\tdc.l $0, start\n') | |
24 for i in xrange(0x8, 0x100, 0x4): | |
25 outfile.write('\tdc.l empty_handler\n') | |
26 outfile.write('\tdc.b "SEGA"\nempty_handler:\n\trte\nstart:\n') | |
27 outfile.write('\tmove #0, CCR\n') | |
28 already = {} | |
29 self.inst.write_init(outfile, already) | |
30 if 'label' in already: | |
31 outfile.write('lbl_' + str(already['label']) + ':\n') | |
32 outfile.write('\t'+str(self.inst)+'\n') | |
33 outfile.write('\t'+self.inst.save_result(self.get_dreg(), True) + '\n') | |
34 save_ccr = self.get_dreg() | |
35 outfile.write('\tmove SR, ' + str(save_ccr) + '\n') | |
36 outfile.write('\tmove #$1F, CCR\n') | |
37 self.inst.invalidate_dest(already) | |
38 self.inst.write_init(outfile, already) | |
39 if 'label' in already: | |
40 outfile.write('lbl_' + str(already['label']) + ':\n') | |
41 outfile.write('\t'+str(self.inst)+'\n') | |
42 outfile.write('\t'+self.inst.save_result(self.get_dreg(), False) + '\n') | |
43 outfile.write('\treset\n') | |
44 | |
45 def consume_dreg(self, num): | |
46 self.avail_dregs.discard(num) | |
47 | |
48 def consume_areg(self, num): | |
49 self.avail_aregs.discard(num) | |
50 | |
51 def get_dreg(self): | |
52 return Register('d', self.avail_dregs.pop()) | |
53 | |
54 class Register(object): | |
55 def __init__(self, kind, num): | |
56 self.kind = kind | |
57 self.num = num | |
58 | |
59 def __str__(self): | |
60 if self.kind == 'd' or self.kind == 'a': | |
61 return self.kind + str(self.num) | |
62 return self.kind | |
63 | |
64 def write_init(self, outfile, size, already): | |
65 if not str(self) in already: | |
66 minv,maxv = get_size_range(size) | |
67 val = randint(minv,maxv) | |
68 already[str(self)] = val | |
69 outfile.write('\tmove.'+size+' #'+str(val)+', ' + str(self) + '\n') | |
70 | |
71 def consume_regs(self, program): | |
72 if self.kind == 'd': | |
73 program.consume_dreg(self.num) | |
74 elif self.kind == 'a': | |
75 program.consume_areg(self.num) | |
76 | |
77 def valid_ram_address(address, size='b'): | |
78 return address >= 0xE00000 and address <= 0xFFFFFFFC and (address & 0xE00000) == 0xE00000 and (size == 'b' or not address & 1) | |
79 | |
80 def random_ram_address(mina=0xE00000, maxa=0xFFFFFFFC): | |
81 return randint(mina, maxa) | 0xE00000 | |
82 | |
83 class Indexed(object): | |
84 def __init__(self, base, index, index_size, disp): | |
85 self.base = base | |
86 self.index = index | |
87 self.index_size = index_size | |
88 self.disp = disp | |
89 | |
90 def write_init(self, outfile, size, already): | |
91 if self.base.kind == 'pc': | |
92 if str(self.index) in already: | |
93 index = already[str(self.index)] | |
94 if self.index_size == 'w': | |
95 index = index & 0xFFFF | |
96 #sign extend index | |
97 if index & 0x8000: | |
98 index -= 65536 | |
99 if index > -1024: | |
100 index = already[str(self.index)] = randint(-32768, -1024) | |
101 outfile.write('\tmove.l #' + str(index) + ', ' + str(self.index) + '\n') | |
102 else: | |
103 index = already[str(self.index)] = randint(-32768, -1024) | |
104 outfile.write('\tmove.l #' + str(index) + ', ' + str(self.index) + '\n') | |
105 num = already.get('label', 0)+1 | |
106 already['label'] = num | |
107 address = 'lbl_' + str(num) + ' + 2 + ' + str(self.disp) + ' + ' + str(index) | |
108 else: | |
109 if str(self.base) in already: | |
110 if not valid_ram_address(already[str(self.base)]): | |
111 del already[str(self.base)] | |
112 self.write_init(outfile, size, already) | |
113 return | |
114 else: | |
115 base = already[str(self.base)] | |
116 else: | |
117 base = already[str(self.base)] = random_ram_address() | |
118 outfile.write('\tmove.l #' + str(base) + ', ' + str(self.base) + '\n') | |
119 if str(self.index) in already: | |
120 index = already[str(self.index)] | |
121 if self.index_size == 'w': | |
122 index = index & 0xFFFF | |
123 #sign extend index | |
124 if index & 0x8000: | |
125 index -= 65536 | |
126 if not valid_ram_address(base + index): | |
127 index = already[str(self.index)] = randint(-64, 63) | |
128 outfile.write('\tmove.l #' + str(index) + ', ' + str(self.index) + '\n') | |
129 else: | |
130 index = already[str(self.index)] = randint(-64, 63) | |
131 outfile.write('\tmove.l #' + str(index) + ', ' + str(self.index) + '\n') | |
132 address = base + index + self.disp | |
133 if (address & 0xFFFFFF) < 0xE00000: | |
134 if (address & 0xFFFFFF) < 128: | |
135 self.disp -= (address & 0xFFFFFF) | |
136 else: | |
137 self.disp += 0xE00000-(address & 0xFFFFFF) | |
138 address = base + index + self.disp | |
139 elif (address & 0xFFFFFF) > 0xFFFFFC: | |
140 self.disp -= (address & 0xFFFFFF) - 0xFFFFFC | |
141 address = base + index + self.disp | |
142 if size != 'b' and address & 1: | |
143 self.disp = self.disp ^ 1 | |
144 address = base + index + self.disp | |
145 minv,maxv = get_size_range(size) | |
146 outfile.write('\tmove.' + size + ' #' + str(randint(minv, maxv)) + ', (' + str(address) + ').l\n') | |
147 | |
148 def __str__(self): | |
149 return '(' + str(self.disp) + ', ' + str(self.base) + ', ' + str(self.index) + '.' + self.index_size + ')' | |
150 | |
151 def consume_regs(self, program): | |
152 self.base.consume_regs(program) | |
153 self.index.consume_regs(program) | |
154 | |
155 class Displacement(object): | |
156 def __init__(self, base, disp): | |
157 self.base = base | |
158 self.disp = disp | |
159 | |
160 def write_init(self, outfile, size, already): | |
161 if self.base.kind == 'pc': | |
162 num = already.get('label', 0)+1 | |
163 already['label'] = num | |
164 address = 'lbl_' + str(num) + ' + 2 + ' + str(self.disp) | |
165 else: | |
166 if str(self.base) in already: | |
167 if not valid_ram_address(already[str(self.base)]): | |
168 del already[str(self.base)] | |
169 self.write_init(outfile, size, already) | |
170 return | |
171 else: | |
172 base = already[str(self.base)] | |
173 else: | |
174 base = already[str(self.base)] = random_ram_address() | |
175 outfile.write('\tmove.l #' + str(base) + ', ' + str(self.base) + '\n') | |
176 address = base + self.disp | |
177 if (address & 0xFFFFFF) < 0xE00000: | |
178 if (address & 0xFFFFFF) < 0x10000: | |
179 self.disp -= (address & 0xFFFFFF) | |
180 else: | |
181 self.disp += 0xE00000-(address & 0xFFFFFF) | |
182 address = base + self.disp | |
183 elif (address & 0xFFFFFF) > 0xFFFFFC: | |
184 self.disp -= (address & 0xFFFFFF) - 0xFFFFFC | |
185 address = base + self.disp | |
186 if size != 'b' and address & 1: | |
187 self.disp = self.disp ^ 1 | |
188 address = base + self.disp | |
189 minv,maxv = get_size_range(size) | |
190 outfile.write('\tmove.' + size + ' #' + str(randint(minv, maxv)) + ', (' + str(address) + ').l\n') | |
191 | |
192 def __str__(self): | |
193 return '(' + str(self.disp) + ', ' + str(self.base) + ')' | |
194 | |
195 def consume_regs(self, program): | |
196 self.base.consume_regs(program) | |
197 | |
198 class Indirect(object): | |
199 def __init__(self, reg): | |
200 self.reg = reg | |
201 | |
202 def __str__(self): | |
203 return '(' + str(self.reg) + ')' | |
204 | |
205 def write_init(self, outfile, size, already): | |
206 if str(self.reg) in already: | |
207 if not valid_ram_address(already[str(self.reg)], size): | |
208 del already[str(self.reg)] | |
209 self.write_init(outfile, size, already) | |
210 return | |
211 else: | |
212 address = already[str(self.reg)] | |
213 else: | |
214 address = random_ram_address() | |
215 if size != 'b': | |
216 address = address & 0xFFFFFFFE | |
217 outfile.write('\tmove.l #' + str(address) + ', ' + str(self.reg) + '\n') | |
218 already[str(self.reg)] = address | |
219 minv,maxv = get_size_range(size) | |
220 outfile.write('\tmove.' + size + ' #' + str(randint(minv, maxv)) + ', (' + str(address) + ').l\n') | |
221 | |
222 def consume_regs(self, program): | |
223 self.reg.consume_regs(program) | |
224 | |
225 class Increment(object): | |
226 def __init__(self, reg): | |
227 self.reg = reg | |
228 | |
229 def __str__(self): | |
230 return '(' + str(self.reg) + ')+' | |
231 | |
232 def write_init(self, outfile, size, already): | |
233 if str(self.reg) in already: | |
234 if not valid_ram_address(already[str(self.reg)], size): | |
235 del already[str(self.reg)] | |
236 self.write_init(outfile, size, already) | |
237 return | |
238 else: | |
239 address = already[str(self.reg)] | |
240 else: | |
241 address = random_ram_address() | |
242 if size != 'b': | |
243 address = address & 0xFFFFFFFE | |
244 outfile.write('\tmove.l #' + str(address) + ', ' + str(self.reg) + '\n') | |
245 already[str(self.reg)] = address | |
246 minv,maxv = get_size_range(size) | |
247 outfile.write('\tmove.' + size + ' #' + str(randint(minv, maxv)) + ', (' + str(address) + ').l\n') | |
248 | |
249 def consume_regs(self, program): | |
250 self.reg.consume_regs(program) | |
251 | |
252 class Decrement(object): | |
253 def __init__(self, reg): | |
254 self.reg = reg | |
255 | |
256 def __str__(self): | |
257 return '-(' + str(self.reg) + ')' | |
258 | |
259 def write_init(self, outfile, size, already): | |
260 if str(self.reg) in already: | |
261 if not valid_ram_address(already[str(self.reg)]- 4 if size == 'l' else 2 if size == 'w' else 1, size): | |
262 del already[str(self.reg)] | |
263 self.write_init(outfile, size, already) | |
264 return | |
265 else: | |
266 address = already[str(self.reg)] | |
267 else: | |
268 address = random_ram_address(mina=0xE00004) | |
269 if size != 'b': | |
270 address = address & 0xFFFFFFFE | |
271 outfile.write('\tmove.l #' + str(address) + ', ' + str(self.reg) + '\n') | |
272 already[str(self.reg)] = address | |
273 minv,maxv = get_size_range(size) | |
274 outfile.write('\tmove.' + size + ' #' + str(randint(minv, maxv)) + ', (' + str(address) + ').l\n') | |
275 | |
276 def consume_regs(self, program): | |
277 self.reg.consume_regs(program) | |
278 | |
279 class Absolute(object): | |
280 def __init__(self, address, size): | |
281 self.address = address | |
282 self.size = size | |
283 | |
284 def __str__(self): | |
285 return '(' + str(self.address) + ').' + self.size | |
286 | |
287 def write_init(self, outfile, size, already): | |
288 minv,maxv = get_size_range(size) | |
289 outfile.write('\tmove.' + size + ' #' + str(randint(minv, maxv)) + ', '+str(self)+'\n') | |
290 | |
291 def consume_regs(self, program): | |
292 pass | |
293 | |
294 class Immediate(object): | |
295 def __init__(self, value): | |
296 self.value = value | |
297 | |
298 def __str__(self): | |
299 return '#' + str(self.value) | |
300 | |
301 def write_init(self, outfile, size, already): | |
302 pass | |
303 | |
304 def consume_regs(self, program): | |
305 pass | |
306 | |
307 all_dregs = [Register('d', i) for i in range(0, 8)] | |
308 all_aregs = [Register('a', i) for i in range(0, 8)] | |
309 all_indirect = [Indirect(reg) for reg in all_aregs] | |
310 all_predec = [Decrement(reg) for reg in all_aregs] | |
311 all_postinc = [Increment(reg) for reg in all_aregs] | |
312 from random import randint | |
313 def all_indexed(): | |
314 return [Indexed(base, index, index_size, randint(-128, 127)) for base in all_aregs for index in all_dregs + all_aregs for index_size in ('w','l')] | |
315 | |
316 def all_disp(): | |
317 return [Displacement(base, randint(-32768, 32767)) for base in all_aregs] | |
318 | |
319 def rand_pc_disp(): | |
320 return [Displacement(Register('pc', 0), randint(-32768, -1024)) for x in xrange(0, 8)] | |
321 | |
322 def all_pc_indexed(): | |
323 return [Indexed(Register('pc', 0), index, index_size, randint(-128, 127)) for index in all_dregs + all_aregs for index_size in ('w','l')] | |
324 | |
325 def rand_abs_short(): | |
326 return [Absolute(0xFFFF8000 + randint(0, 32767), 'w') for x in xrange(0, 8)] | |
327 | |
328 def rand_abs_long(): | |
329 return [Absolute(0xFF0000 + randint(0, 65535), 'l') for x in xrange(0, 8)] | |
330 | |
331 def get_size_range(size): | |
332 if size == 'b': | |
333 return (-128, 127) | |
334 elif size == 'w': | |
335 return (-32768, 32767) | |
336 else: | |
337 return (-2147483648, 2147483647) | |
338 | |
339 def rand_immediate(size): | |
340 minv,maxv = get_size_range(size) | |
341 | |
342 return [Immediate(randint(minv, maxv)) for x in xrange(0,8)] | |
343 | |
344 def get_variations(mode, size): | |
345 mapping = { | |
346 'd':all_dregs, | |
347 'a':all_aregs, | |
348 '(a)':all_indirect, | |
349 '-(a)':all_predec, | |
350 '(a)+':all_postinc, | |
351 '(n,a)':all_disp, | |
352 '(n,a,x)':all_indexed, | |
353 '(n,pc)':rand_pc_disp, | |
354 '(n,pc,x)':all_pc_indexed, | |
355 '(n).w':rand_abs_short, | |
356 '(n).l':rand_abs_long | |
357 } | |
358 if mode in mapping: | |
359 ret = mapping[mode] | |
360 if type(ret) != list: | |
361 ret = ret() | |
362 return ret | |
363 elif mode == '#n': | |
364 return rand_immediate(size) | |
365 elif mode.startswith('#(') and mode.endswith(')'): | |
366 inner = mode[2:-1] | |
367 start,sep,end = inner.partition('-') | |
368 return [Immediate(num) for num in range(int(start), int(end))] | |
369 | |
370 class Inst2Op(object): | |
371 def __init__(self, name, size, src, dst): | |
372 self.name = name | |
373 self.size = size | |
374 self.src = src | |
375 self.dst = dst | |
376 | |
377 def __str__(self): | |
378 return self.name + '.' + self.size + ' ' + str(self.src) + ', ' + str(self.dst) | |
379 | |
380 def write_init(self, outfile, already): | |
381 self.src.write_init(outfile, self.size, already) | |
382 self.dst.write_init(outfile, self.size, already) | |
383 | |
384 def invalidate_dest(self, already): | |
385 if type(self.dst) == Register: | |
386 del already[str(self.dst)] | |
387 | |
388 def save_result(self, reg, always): | |
389 if always or type(self.dst) != Register: | |
390 return 'move.' + self.size + ' ' + str(self.dst) + ', ' + str(reg) | |
391 else: | |
392 return '' | |
393 | |
394 def consume_regs(self, program): | |
395 self.src.consume_regs(program) | |
396 self.dst.consume_regs(program) | |
397 | |
398 class Entry(object): | |
399 def __init__(self, line): | |
400 fields = split_fields(line) | |
401 self.name = fields[0] | |
402 sizes = fields[1] | |
403 sources = fields[2].split(';') | |
404 dests = fields[3].split(';') | |
405 combos = [] | |
406 for size in sizes: | |
407 for source in sources: | |
408 if size != 'b' or source != 'a': | |
409 for dest in dests: | |
410 if size != 'b' or dest != 'a': | |
411 combos.append((size, source, dest)) | |
412 self.cases = combos | |
413 | |
414 def programs(self): | |
415 res = [] | |
416 for (size, src, dst) in self.cases: | |
417 sources = get_variations(src, size) | |
418 dests = get_variations(dst, size) | |
419 for source in sources: | |
420 for dest in dests: | |
421 res.append(Program(Inst2Op(self.name, size, source, dest))) | |
422 return res | |
423 | |
424 def process_entries(f): | |
425 entries = [] | |
426 for line in f: | |
427 if not line.startswith('Name') and not line.startswith('#') and len(line.strip()) > 0: | |
428 entries.append(Entry(line)) | |
429 return entries | |
430 | |
431 | |
432 def main(args): | |
433 entries = process_entries(open('testcases.txt')) | |
434 for entry in entries: | |
435 programs = entry.programs() | |
436 for program in programs: | |
437 f = open('generated_tests/' + program.name() + '.s68', 'w') | |
438 program.write_rom_test(f) | |
439 f.close() | |
440 | |
441 if __name__ == '__main__': | |
442 import sys | |
443 main(sys.argv) | |
444 |