main gentests.py
  1#!/usr/bin/env python
  2
  3def 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
 12class 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 dirname(self):
 20		return self.inst.name + '_' + self.inst.size
 21	def name(self):
 22		return str(self.inst).replace('.', '_').replace('#', '_').replace(',', '_').replace(' ', '_').replace('(', '[').replace(')', ']')
 23	
 24	def write_rom_test(self, outfile):
 25		outfile.write('\tdc.l $0, start\n')
 26		needdivzero = self.inst.name.startswith('div')
 27		needchk = self.inst.name.startswith('chk')
 28		for i in xrange(0x8, 0x100, 0x4):
 29			if needdivzero and i == 0x14:
 30				outfile.write('\tdc.l div_zero_handler\n')
 31			elif needchk and i == 0x18:
 32				outfile.write('\tdc.l chk_handler\n')
 33			else:
 34				outfile.write('\tdc.l empty_handler\n')
 35		outfile.write('\tdc.b "SEGA"\nempty_handler:\n\trte\n')
 36		if needdivzero:
 37			outfile.write('div_zero_handler:\n')
 38			div_zero_count = self.get_dreg()
 39			outfile.write('\taddq #1, ' + str(div_zero_count) + '\n')
 40			outfile.write('\trte\n')
 41		if needchk:
 42			outfile.write('chk_handler:\n')
 43			chk_count = self.get_dreg()
 44			outfile.write('\taddq #1, ' + str(chk_count) + '\n')
 45			outfile.write('\trte\n')
 46		outfile.write('start:\n\tmove #0, CCR\n')
 47		if needdivzero:
 48			outfile.write('\tmoveq #0, ' + str(div_zero_count) + '\n')
 49		already = {}
 50		self.inst.write_init(outfile, already)
 51		if 'label' in already:
 52			outfile.write('lbl_' + str(already['label']) + ':\n')
 53		outfile.write('\t'+str(self.inst)+'\n')
 54		outfile.write('\t'+self.inst.save_result(self.get_dreg(), True) + '\n')
 55		save_ccr = self.get_dreg()
 56		outfile.write('\tmove SR, ' + str(save_ccr) + '\n')
 57		outfile.write('\tmove #$1F, CCR\n')
 58		self.inst.invalidate_dest(already)
 59		self.inst.write_init(outfile, already)
 60		if 'label' in already:
 61			outfile.write('lbl_' + str(already['label']) + ':\n')
 62		outfile.write('\t'+str(self.inst)+'\n')
 63		outfile.write('\t'+self.inst.save_result(self.get_dreg(), False) + '\n')
 64		outfile.write('\treset\nforever:\n\tbra.s forever\n')
 65	
 66	def consume_dreg(self, num):
 67		self.avail_dregs.discard(num)
 68	
 69	def consume_areg(self, num):
 70		self.avail_aregs.discard(num)
 71	
 72	def get_dreg(self):
 73		return Register('d', self.avail_dregs.pop())
 74
 75class Dummy(object):
 76	def __str__(self):
 77		return ''
 78	def write_init(self, outfile, size, already):
 79		pass
 80	def consume_regs(self, program):
 81		pass
 82
 83dummy_op = Dummy()
 84
 85class Register(object):
 86	def __init__(self, kind, num):
 87		self.kind = kind
 88		self.num = num
 89	
 90	def __str__(self):
 91		if self.kind == 'd' or self.kind == 'a':
 92			return self.kind + str(self.num)
 93		return self.kind
 94	
 95	def write_init(self, outfile, size, already):
 96		if not str(self) in already:
 97			minv,maxv = get_size_range(size)
 98			val = randint(minv,maxv)
 99			already[str(self)] = val
100			outfile.write('\tmove.'+size+' #'+str(val)+', ' + str(self) + '\n')
101	
102	def consume_regs(self, program):
103		if self.kind == 'd':
104			program.consume_dreg(self.num)
105		elif self.kind == 'a':
106			program.consume_areg(self.num)
107
108def valid_ram_address(address, size='b'):
109	return address >= 0xE00000 and address <= 0xFFFFFFFC and (address & 0xE00000) == 0xE00000 and (size == 'b' or not address & 1)
110
111def random_ram_address(mina=0xE00000, maxa=0xFFFFFFFC):
112	return randint(mina/2, maxa/2)*2 | 0xE00000
113
114class Indexed(object):
115	def __init__(self, base, index, index_size, disp):
116		self.base = base
117		self.index = index
118		self.index_size = index_size
119		self.disp = disp
120	
121	def write_init(self, outfile, size, already):
122		if self.base.kind == 'pc':
123			if str(self.index) in already:
124				index = already[str(self.index)]
125				if self.index_size == 'w':
126					index = index & 0xFFFF
127					#sign extend index
128					if index & 0x8000:
129						index -= 65536
130				if index > -1024:
131					index = already[str(self.index)] = 2 * randint(-16384, -512)
132					outfile.write('\tmove.l #' + str(index) + ', ' + str(self.index) + '\n')
133			else:
134				index = already[str(self.index)] = 2 * randint(-16384, -512)
135				outfile.write('\tmove.l #' + str(index) + ', ' + str(self.index) + '\n')
136			num = already.get('label', 0)+1
137			already['label'] = num
138			if (already[str(self.index)] + self.disp) & 1:
139				self.disp += 1
140			address = 'lbl_' + str(num) + ' + 2 + ' + str(self.disp) + ' + ' + str(index)
141		else:
142			if self.base == self.index:
143				if str(self.base) in already:
144					if not valid_ram_address(already[str(self.base)]*2):
145						del already[str(self.base)]
146						self.write_init(outfile, size, already)
147						return
148					else:
149						base = index = already[str(self.base)]
150				else:
151					base = index = already[str(self.base)] = random_ram_address()/2
152					outfile.write('\tmove.l #' + str(base) + ', ' + str(self.base) + '\n')
153			else:
154				if str(self.base) in already:
155					if not valid_ram_address(already[str(self.base)]):
156						del already[str(self.base)]
157						self.write_init(outfile, size, already)
158						return
159					else:
160						base = already[str(self.base)]
161				else:
162					base = already[str(self.base)] = random_ram_address()
163					outfile.write('\tmove.l #' + str(base) + ', ' + str(self.base) + '\n')
164				if str(self.index) in already:
165					index = already[str(self.index)]
166					if self.index_size == 'w':
167						index = index & 0xFFFF
168						#sign extend index
169						if index & 0x8000:
170							index -= 65536
171					if not valid_ram_address(base + index):
172						index = already[str(self.index)] = randint(-64, 63)
173						outfile.write('\tmove.l #' + str(index) + ', ' + str(self.index) + '\n')
174				else:
175					index = already[str(self.index)] = randint(-64, 63)
176					outfile.write('\tmove.l #' + str(index) + ', ' + str(self.index) + '\n')
177			address = base + index + self.disp
178			if (address & 0xFFFFFF) < 0xE00000:
179				if (address & 0xFFFFFF) < 128:
180					self.disp -= (address & 0xFFFFFF)
181				else:
182					self.disp += 0xE00000-(address & 0xFFFFFF)
183				if self.disp > 127:
184					self.disp = 127
185				elif self.disp < -128:
186					self.disp = -128
187				address = base + index + self.disp
188			elif (address & 0xFFFFFF) > 0xFFFFFC:
189				self.disp -= (address & 0xFFFFFF) - 0xFFFFFC
190				if self.disp > 127:
191					self.disp = 127
192				elif self.disp < -128:
193					self.disp = -128
194				address = base + index + self.disp
195			if size != 'b' and address & 1:
196				self.disp = self.disp ^ 1
197				address = base + index + self.disp
198		minv,maxv = get_size_range(size)
199		outfile.write('\tmove.' + size + ' #' + str(randint(minv, maxv)) + ', (' + str(address) + ').l\n')
200	
201	def __str__(self):
202		return '(' + str(self.disp) + ', ' + str(self.base) + ', ' + str(self.index) + '.' + self.index_size + ')'
203	
204	def consume_regs(self, program):
205		self.base.consume_regs(program)
206		self.index.consume_regs(program)
207
208class Displacement(object):
209	def __init__(self, base, disp):
210		self.base = base
211		if disp & 1:
212			disp += 1
213		self.disp = disp 
214	
215	def write_init(self, outfile, size, already):
216		if self.base.kind == 'pc':
217			num = already.get('label', 0)+1
218			already['label'] = num
219			address = 'lbl_' + str(num) + ' + 2 + ' + str(self.disp)
220		else:
221			if str(self.base) in already:
222				if not valid_ram_address(already[str(self.base)]):
223					del already[str(self.base)]
224					self.write_init(outfile, size, already)
225					return
226				else:
227					base = already[str(self.base)]
228			else:
229				base = already[str(self.base)] = random_ram_address()
230				outfile.write('\tmove.l #' + str(base) + ', ' + str(self.base) + '\n')
231			address = base + self.disp
232			if (address & 0xFFFFFF) < 0xE00000:
233				if (address & 0xFFFFFF) < 0x10000:
234					self.disp -= (address & 0xFFFFFF)
235				else:
236					self.disp += 0xE00000-(address & 0xFFFFFF)
237				address = base + self.disp
238			elif (address & 0xFFFFFF) > 0xFFFFFC:
239				self.disp -= (address & 0xFFFFFF) - 0xFFFFFC
240				address = base + self.disp
241			if size != 'b' and address & 1:
242				self.disp = self.disp ^ 1
243				address = base + self.disp
244		minv,maxv = get_size_range(size)
245		outfile.write('\tmove.' + size + ' #' + str(randint(minv, maxv)) + ', (' + str(address) + ').l\n')
246	
247	def __str__(self):
248		return '(' + str(self.disp) + ', ' + str(self.base) + ')'
249	
250	def consume_regs(self, program):
251		self.base.consume_regs(program)
252	
253class Indirect(object):
254	def __init__(self, reg):
255		self.reg = reg
256	
257	def __str__(self):
258		return '(' + str(self.reg) + ')'
259	
260	def write_init(self, outfile, size, already):
261		if str(self.reg) in already:
262			if not valid_ram_address(already[str(self.reg)], size):
263				del already[str(self.reg)]
264				self.write_init(outfile, size, already)
265				return
266			else:
267				address = already[str(self.reg)]
268		else:
269			address = random_ram_address()
270			if size != 'b':
271				address = address & 0xFFFFFFFE
272			outfile.write('\tmove.l #' + str(address) + ', ' + str(self.reg) + '\n')
273			already[str(self.reg)] = address
274		minv,maxv = get_size_range(size)
275		outfile.write('\tmove.' + size + ' #' + str(randint(minv, maxv)) + ', (' + str(address) + ').l\n')
276	
277	def consume_regs(self, program):
278		self.reg.consume_regs(program)
279
280class Increment(object):
281	def __init__(self, reg):
282		self.reg = reg
283	
284	def __str__(self):
285		return '(' + str(self.reg) + ')+'
286	
287	def write_init(self, outfile, size, already):
288		if str(self.reg) in already:
289			if not valid_ram_address(already[str(self.reg)], size):
290				del already[str(self.reg)]
291				self.write_init(outfile, size, already)
292				return
293			else:
294				address = already[str(self.reg)]
295		else:
296			address = random_ram_address()
297			if size != 'b':
298				address = address & 0xFFFFFFFE
299			outfile.write('\tmove.l #' + str(address) + ', ' + str(self.reg) + '\n')
300			already[str(self.reg)] = address
301		minv,maxv = get_size_range(size)
302		outfile.write('\tmove.' + size + ' #' + str(randint(minv, maxv)) + ', (' + str(address) + ').l\n')
303	
304	def consume_regs(self, program):
305		self.reg.consume_regs(program)
306
307class Decrement(object):
308	def __init__(self, reg):
309		self.reg = reg
310	
311	def __str__(self):
312		return '-(' + str(self.reg) + ')'
313	
314	def write_init(self, outfile, size, already):
315		if str(self.reg) in already:
316			if not valid_ram_address(already[str(self.reg)]- 4 if size == 'l' else 2 if size == 'w' else 1, size):
317				del already[str(self.reg)]
318				self.write_init(outfile, size, already)
319				return
320			else:
321				address = already[str(self.reg)]
322		else:
323			address = random_ram_address(mina=0xE00004)
324			if size != 'b':
325				address = address & 0xFFFFFFFE
326			outfile.write('\tmove.l #' + str(address) + ', ' + str(self.reg) + '\n')
327			already[str(self.reg)] = address
328		minv,maxv = get_size_range(size)
329		outfile.write('\tmove.' + size + ' #' + str(randint(minv, maxv)) + ', (' + str(address) + ').l\n')
330	
331	def consume_regs(self, program):
332		self.reg.consume_regs(program)
333
334class Absolute(object):
335	def __init__(self, address, size):
336		self.address = address
337		self.size = size
338	
339	def __str__(self):
340		return '(' + str(self.address) + ').' + self.size
341	
342	def write_init(self, outfile, size, already):
343		minv,maxv = get_size_range(size)
344		outfile.write('\tmove.' + size + ' #' + str(randint(minv, maxv)) + ', '+str(self)+'\n')
345	
346	def consume_regs(self, program):
347		pass
348
349class Immediate(object):
350	def __init__(self, value):
351		self.value = value
352	
353	def __str__(self):
354		return '#' + str(self.value)
355	
356	def write_init(self, outfile, size, already):
357		pass
358	
359	def consume_regs(self, program):
360		pass
361		
362all_dregs = [Register('d', i) for i in range(0, 8)]
363all_aregs = [Register('a', i) for i in range(0, 8)]
364all_indirect = [Indirect(reg) for reg in all_aregs]
365all_predec = [Decrement(reg) for reg in all_aregs]
366all_postinc = [Increment(reg) for reg in all_aregs]
367from random import randint
368def all_indexed():
369	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')]
370
371def all_disp():
372	return [Displacement(base, randint(-32768, 32767)) for base in all_aregs]
373
374def rand_pc_disp():
375	return [Displacement(Register('pc', 0), randint(-32768, -1024)) for x in xrange(0, 8)]
376
377def all_pc_indexed():
378	return [Indexed(Register('pc', 0), index, index_size, randint(-128, 127)) for index in all_dregs + all_aregs for index_size in ('w','l')]
379
380def rand_abs_short():
381	return [Absolute(random_ram_address(0xFFFF8000), 'w') for x in xrange(0, 8)]
382
383def rand_abs_long():
384	return [Absolute(random_ram_address(), 'l') for x in xrange(0, 8)]
385
386def get_size_range(size):
387	if size == 'b':
388		return (-128, 127)
389	elif size == 'w':
390		return (-32768, 32767)
391	else:
392		return (-2147483648, 2147483647)
393
394def rand_immediate(size):
395	minv,maxv = get_size_range(size)
396	
397	return [Immediate(randint(minv, maxv)) for x in xrange(0,8)]
398
399def get_variations(mode, size):
400	mapping = {
401		'd':all_dregs,
402		'a':all_aregs,
403		'(a)':all_indirect,
404		'-(a)':all_predec,
405		'(a)+':all_postinc,
406		'(n,a)':all_disp,
407		'(n,a,x)':all_indexed,
408		'(n,pc)':rand_pc_disp,
409		'(n,pc,x)':all_pc_indexed,
410		'(n).w':rand_abs_short,
411		'(n).l':rand_abs_long
412	}
413	if mode in mapping:
414		ret = mapping[mode]
415		if type(ret) != list:
416			ret = ret()
417		return ret
418	elif mode == '#n':
419		return rand_immediate(size)
420	elif mode.startswith('#(') and mode.endswith(')'):
421		inner = mode[2:-1]
422		start,sep,end = inner.rpartition('-')
423		start,end = int(start),int(end)
424		if end-start > 16:
425			return [Immediate(randint(start, end)) for x in range(0,8)]
426		else:
427			return [Immediate(num) for num in range(start, end+1)]
428	else:
429		print "Don't know what to do with source type", mode
430		return None
431		
432class Inst2Op(object):
433	def __init__(self, name, size, src, dst):
434		self.name = name
435		self.size = size
436		self.src = src
437		self.dst = dst
438	
439	def __str__(self):
440		return self.name + '.' + self.size + ' ' + str(self.src) + ', ' + str(self.dst)
441	
442	def write_init(self, outfile, already):
443		self.src.write_init(outfile, self.size, already)
444		self.dst.write_init(outfile, self.size, already)
445	
446	def invalidate_dest(self, already):
447		if type(self.dst) == Register:
448			del already[str(self.dst)]
449	
450	def save_result(self, reg, always):
451		if always or type(self.dst) != Register:
452			if type(self.dst) == Decrement:
453				src = Increment(self.dst.reg)
454			elif type(self.dst) == Increment:
455				src = Decrement(self.dst.reg)
456			else:
457				src = self.dst
458			return 'move.' + self.size + ' ' + str(src) + ', ' + str(reg)
459		else:
460			return ''
461	
462	def consume_regs(self, program):
463		self.src.consume_regs(program)
464		self.dst.consume_regs(program)
465
466class Inst1Op(Inst2Op):
467	def __init__(self, name, size, dst):
468		super(Inst1Op, self).__init__(name, size, dummy_op, dst)
469	
470	def __str__(self):
471		return self.name + '.' + self.size + ' ' + str(self.dst)
472
473class Entry(object):
474	def __init__(self, line):
475		fields = split_fields(line)
476		self.name = fields[0]
477		sizes = fields[1]
478		sources = fields[2].split(';')
479		if len(fields) > 3:
480			dests = fields[3].split(';')
481		else:
482			dests = None
483		combos = []
484		for size in sizes:
485			for source in sources:
486				if size != 'b' or source != 'a':
487					if dests:
488						for dest in dests:
489							if size != 'b' or dest != 'a':
490								combos.append((size, source, dest))
491					else:
492						combos.append((size, None, source))
493		self.cases = combos
494		
495	def programs(self):
496		res = []
497		for (size, src, dst) in self.cases:
498			dests = get_variations(dst, size)
499			if src:
500				sources = get_variations(src, size)
501				for source in sources:
502					for dest in dests:
503						res.append(Program(Inst2Op(self.name, size, source, dest)))
504			else:
505				for dest in dests:
506					res.append(Program(Inst1Op(self.name, size, dest)))
507		return res
508		
509def process_entries(f):
510	entries = []
511	for line in f:
512		if not line.startswith('Name') and not line.startswith('#') and len(line.strip()) > 0:
513			entries.append(Entry(line))
514	return entries
515
516from os import path, mkdir
517def main(args):
518	entries = process_entries(open('testcases.txt'))
519	for entry in entries:
520		programs = entry.programs()
521		for program in programs:
522			dname = program.dirname()
523			if not path.exists('generated_tests/' + dname):
524				mkdir('generated_tests/' + dname)
525			f = open('generated_tests/' + dname + '/' + program.name() + '.s68', 'w')
526			program.write_rom_test(f)
527			f.close()
528	
529if __name__ == '__main__':
530	import sys
531	main(sys.argv)
532