changeset 214:9126c33cc33c

Add test generator, builder and runner
author Mike Pavone <pavone@retrodev.com>
date Fri, 19 Apr 2013 09:29:37 -0700
parents 4d4559b04c59
children 2b1c2c28b261
files comparetests.py gentests.py maketests.py
diffstat 3 files changed, 498 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/comparetests.py	Fri Apr 19 09:29:37 2013 -0700
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+from glob import glob
+import subprocess
+from sys import exit
+
+for path in glob('generated_tests/*.bin'):
+	try:
+		b = subprocess.check_output(['./blastem', path])
+		try:
+			m = subprocess.check_output(['musashi/mustrans', path])
+			_,_,b = b.partition('\n')
+			if b != m:
+				print '-----------------------------'
+				print 'Mismatch in ' + path
+				print 'blastem output:'
+				print b
+				print 'musashi output:'
+				print m
+				print '-----------------------------'
+			else:
+				print path, 'passed'
+		except subprocess.CalledProcessError as e:
+			print '-----------------------------'
+			print 'musashi exited with code', e.returncode, 'for test', path
+			print 'blastem output:'
+			print b
+			print '-----------------------------'
+	except subprocess.CalledProcessError as e:
+		print '-----------------------------'
+		print 'blastem exited with code', e.returncode, 'for test', path
+		print '-----------------------------'
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gentests.py	Fri Apr 19 09:29:37 2013 -0700
@@ -0,0 +1,444 @@
+#!/usr/bin/env python
+
+def split_fields(line):
+	parts = []
+	while line:
+		field,_,line = line.partition('\t')
+		parts.append(field.strip())
+		while line.startswith('\t'):
+			line = line[1:]
+	return parts
+
+class Program(object):
+	def __init__(self, instruction):
+		self.avail_dregs = {0,1,2,3,4,5,6,7}
+		self.avail_aregs = {0,1,2,3,4,5,6,7}
+		instruction.consume_regs(self)
+		self.inst = instruction
+	
+	def name(self):
+		return str(self.inst).replace('.', '_').replace('#', '_').replace(',', '_').replace(' ', '_').replace('(', '[').replace(')', ']')
+	
+	def write_rom_test(self, outfile):
+		outfile.write('\tdc.l $0, start\n')
+		for i in xrange(0x8, 0x100, 0x4):
+			outfile.write('\tdc.l empty_handler\n')
+		outfile.write('\tdc.b "SEGA"\nempty_handler:\n\trte\nstart:\n')
+		outfile.write('\tmove #0, CCR\n')
+		already = {}
+		self.inst.write_init(outfile, already)
+		if 'label' in already:
+			outfile.write('lbl_' + str(already['label']) + ':\n')
+		outfile.write('\t'+str(self.inst)+'\n')
+		outfile.write('\t'+self.inst.save_result(self.get_dreg(), True) + '\n')
+		save_ccr = self.get_dreg()
+		outfile.write('\tmove SR, ' + str(save_ccr) + '\n')
+		outfile.write('\tmove #$1F, CCR\n')
+		self.inst.invalidate_dest(already)
+		self.inst.write_init(outfile, already)
+		if 'label' in already:
+			outfile.write('lbl_' + str(already['label']) + ':\n')
+		outfile.write('\t'+str(self.inst)+'\n')
+		outfile.write('\t'+self.inst.save_result(self.get_dreg(), False) + '\n')
+		outfile.write('\treset\n')
+	
+	def consume_dreg(self, num):
+		self.avail_dregs.discard(num)
+	
+	def consume_areg(self, num):
+		self.avail_aregs.discard(num)
+	
+	def get_dreg(self):
+		return Register('d', self.avail_dregs.pop())
+
+class Register(object):
+	def __init__(self, kind, num):
+		self.kind = kind
+		self.num = num
+	
+	def __str__(self):
+		if self.kind == 'd' or self.kind == 'a':
+			return self.kind + str(self.num)
+		return self.kind
+	
+	def write_init(self, outfile, size, already):
+		if not str(self) in already:
+			minv,maxv = get_size_range(size)
+			val = randint(minv,maxv)
+			already[str(self)] = val
+			outfile.write('\tmove.'+size+' #'+str(val)+', ' + str(self) + '\n')
+	
+	def consume_regs(self, program):
+		if self.kind == 'd':
+			program.consume_dreg(self.num)
+		elif self.kind == 'a':
+			program.consume_areg(self.num)
+
+def valid_ram_address(address, size='b'):
+	return address >= 0xE00000 and address <= 0xFFFFFFFC and (address & 0xE00000) == 0xE00000 and (size == 'b' or not address & 1)
+
+def random_ram_address(mina=0xE00000, maxa=0xFFFFFFFC):
+	return randint(mina, maxa) | 0xE00000
+
+class Indexed(object):
+	def __init__(self, base, index, index_size, disp):
+		self.base = base
+		self.index = index
+		self.index_size = index_size
+		self.disp = disp
+	
+	def write_init(self, outfile, size, already):
+		if self.base.kind == 'pc':
+			if str(self.index) in already:
+				index = already[str(self.index)]
+				if self.index_size == 'w':
+					index = index & 0xFFFF
+					#sign extend index
+					if index & 0x8000:
+						index -= 65536
+				if index > -1024:
+					index = already[str(self.index)] = randint(-32768, -1024)
+					outfile.write('\tmove.l #' + str(index) + ', ' + str(self.index) + '\n')
+			else:
+				index = already[str(self.index)] = randint(-32768, -1024)
+				outfile.write('\tmove.l #' + str(index) + ', ' + str(self.index) + '\n')
+			num = already.get('label', 0)+1
+			already['label'] = num
+			address = 'lbl_' + str(num) + ' + 2 + ' + str(self.disp) + ' + ' + str(index)
+		else:
+			if str(self.base) in already:
+				if not valid_ram_address(already[str(self.base)]):
+					del already[str(self.base)]
+					self.write_init(outfile, size, already)
+					return
+				else:
+					base = already[str(self.base)]
+			else:
+				base = already[str(self.base)] = random_ram_address()
+				outfile.write('\tmove.l #' + str(base) + ', ' + str(self.base) + '\n')
+			if str(self.index) in already:
+				index = already[str(self.index)]
+				if self.index_size == 'w':
+					index = index & 0xFFFF
+					#sign extend index
+					if index & 0x8000:
+						index -= 65536
+				if not valid_ram_address(base + index):
+					index = already[str(self.index)] = randint(-64, 63)
+					outfile.write('\tmove.l #' + str(index) + ', ' + str(self.index) + '\n')
+			else:
+				index = already[str(self.index)] = randint(-64, 63)
+				outfile.write('\tmove.l #' + str(index) + ', ' + str(self.index) + '\n')
+			address = base + index + self.disp
+			if (address & 0xFFFFFF) < 0xE00000:
+				if (address & 0xFFFFFF) < 128:
+					self.disp -= (address & 0xFFFFFF)
+				else:
+					self.disp += 0xE00000-(address & 0xFFFFFF)
+				address = base + index + self.disp
+			elif (address & 0xFFFFFF) > 0xFFFFFC:
+				self.disp -= (address & 0xFFFFFF) - 0xFFFFFC
+				address = base + index + self.disp
+			if size != 'b' and address & 1:
+				self.disp = self.disp ^ 1
+				address = base + index + self.disp
+		minv,maxv = get_size_range(size)
+		outfile.write('\tmove.' + size + ' #' + str(randint(minv, maxv)) + ', (' + str(address) + ').l\n')
+	
+	def __str__(self):
+		return '(' + str(self.disp) + ', ' + str(self.base) + ', ' + str(self.index) + '.' + self.index_size + ')'
+	
+	def consume_regs(self, program):
+		self.base.consume_regs(program)
+		self.index.consume_regs(program)
+
+class Displacement(object):
+	def __init__(self, base, disp):
+		self.base = base
+		self.disp = disp
+	
+	def write_init(self, outfile, size, already):
+		if self.base.kind == 'pc':
+			num = already.get('label', 0)+1
+			already['label'] = num
+			address = 'lbl_' + str(num) + ' + 2 + ' + str(self.disp)
+		else:
+			if str(self.base) in already:
+				if not valid_ram_address(already[str(self.base)]):
+					del already[str(self.base)]
+					self.write_init(outfile, size, already)
+					return
+				else:
+					base = already[str(self.base)]
+			else:
+				base = already[str(self.base)] = random_ram_address()
+				outfile.write('\tmove.l #' + str(base) + ', ' + str(self.base) + '\n')
+			address = base + self.disp
+			if (address & 0xFFFFFF) < 0xE00000:
+				if (address & 0xFFFFFF) < 0x10000:
+					self.disp -= (address & 0xFFFFFF)
+				else:
+					self.disp += 0xE00000-(address & 0xFFFFFF)
+				address = base + self.disp
+			elif (address & 0xFFFFFF) > 0xFFFFFC:
+				self.disp -= (address & 0xFFFFFF) - 0xFFFFFC
+				address = base + self.disp
+			if size != 'b' and address & 1:
+				self.disp = self.disp ^ 1
+				address = base + self.disp
+		minv,maxv = get_size_range(size)
+		outfile.write('\tmove.' + size + ' #' + str(randint(minv, maxv)) + ', (' + str(address) + ').l\n')
+	
+	def __str__(self):
+		return '(' + str(self.disp) + ', ' + str(self.base) + ')'
+	
+	def consume_regs(self, program):
+		self.base.consume_regs(program)
+	
+class Indirect(object):
+	def __init__(self, reg):
+		self.reg = reg
+	
+	def __str__(self):
+		return '(' + str(self.reg) + ')'
+	
+	def write_init(self, outfile, size, already):
+		if str(self.reg) in already:
+			if not valid_ram_address(already[str(self.reg)], size):
+				del already[str(self.reg)]
+				self.write_init(outfile, size, already)
+				return
+			else:
+				address = already[str(self.reg)]
+		else:
+			address = random_ram_address()
+			if size != 'b':
+				address = address & 0xFFFFFFFE
+			outfile.write('\tmove.l #' + str(address) + ', ' + str(self.reg) + '\n')
+			already[str(self.reg)] = address
+		minv,maxv = get_size_range(size)
+		outfile.write('\tmove.' + size + ' #' + str(randint(minv, maxv)) + ', (' + str(address) + ').l\n')
+	
+	def consume_regs(self, program):
+		self.reg.consume_regs(program)
+
+class Increment(object):
+	def __init__(self, reg):
+		self.reg = reg
+	
+	def __str__(self):
+		return '(' + str(self.reg) + ')+'
+	
+	def write_init(self, outfile, size, already):
+		if str(self.reg) in already:
+			if not valid_ram_address(already[str(self.reg)], size):
+				del already[str(self.reg)]
+				self.write_init(outfile, size, already)
+				return
+			else:
+				address = already[str(self.reg)]
+		else:
+			address = random_ram_address()
+			if size != 'b':
+				address = address & 0xFFFFFFFE
+			outfile.write('\tmove.l #' + str(address) + ', ' + str(self.reg) + '\n')
+			already[str(self.reg)] = address
+		minv,maxv = get_size_range(size)
+		outfile.write('\tmove.' + size + ' #' + str(randint(minv, maxv)) + ', (' + str(address) + ').l\n')
+	
+	def consume_regs(self, program):
+		self.reg.consume_regs(program)
+
+class Decrement(object):
+	def __init__(self, reg):
+		self.reg = reg
+	
+	def __str__(self):
+		return '-(' + str(self.reg) + ')'
+	
+	def write_init(self, outfile, size, already):
+		if str(self.reg) in already:
+			if not valid_ram_address(already[str(self.reg)]- 4 if size == 'l' else 2 if size == 'w' else 1, size):
+				del already[str(self.reg)]
+				self.write_init(outfile, size, already)
+				return
+			else:
+				address = already[str(self.reg)]
+		else:
+			address = random_ram_address(mina=0xE00004)
+			if size != 'b':
+				address = address & 0xFFFFFFFE
+			outfile.write('\tmove.l #' + str(address) + ', ' + str(self.reg) + '\n')
+			already[str(self.reg)] = address
+		minv,maxv = get_size_range(size)
+		outfile.write('\tmove.' + size + ' #' + str(randint(minv, maxv)) + ', (' + str(address) + ').l\n')
+	
+	def consume_regs(self, program):
+		self.reg.consume_regs(program)
+
+class Absolute(object):
+	def __init__(self, address, size):
+		self.address = address
+		self.size = size
+	
+	def __str__(self):
+		return '(' + str(self.address) + ').' + self.size
+	
+	def write_init(self, outfile, size, already):
+		minv,maxv = get_size_range(size)
+		outfile.write('\tmove.' + size + ' #' + str(randint(minv, maxv)) + ', '+str(self)+'\n')
+	
+	def consume_regs(self, program):
+		pass
+
+class Immediate(object):
+	def __init__(self, value):
+		self.value = value
+	
+	def __str__(self):
+		return '#' + str(self.value)
+	
+	def write_init(self, outfile, size, already):
+		pass
+	
+	def consume_regs(self, program):
+		pass
+		
+all_dregs = [Register('d', i) for i in range(0, 8)]
+all_aregs = [Register('a', i) for i in range(0, 8)]
+all_indirect = [Indirect(reg) for reg in all_aregs]
+all_predec = [Decrement(reg) for reg in all_aregs]
+all_postinc = [Increment(reg) for reg in all_aregs]
+from random import randint
+def all_indexed():
+	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')]
+
+def all_disp():
+	return [Displacement(base, randint(-32768, 32767)) for base in all_aregs]
+
+def rand_pc_disp():
+	return [Displacement(Register('pc', 0), randint(-32768, -1024)) for x in xrange(0, 8)]
+
+def all_pc_indexed():
+	return [Indexed(Register('pc', 0), index, index_size, randint(-128, 127)) for index in all_dregs + all_aregs for index_size in ('w','l')]
+
+def rand_abs_short():
+	return [Absolute(0xFFFF8000 + randint(0, 32767), 'w') for x in xrange(0, 8)]
+
+def rand_abs_long():
+	return [Absolute(0xFF0000 + randint(0, 65535), 'l') for x in xrange(0, 8)]
+
+def get_size_range(size):
+	if size == 'b':
+		return (-128, 127)
+	elif size == 'w':
+		return (-32768, 32767)
+	else:
+		return (-2147483648, 2147483647)
+
+def rand_immediate(size):
+	minv,maxv = get_size_range(size)
+	
+	return [Immediate(randint(minv, maxv)) for x in xrange(0,8)]
+
+def get_variations(mode, size):
+	mapping = {
+		'd':all_dregs,
+		'a':all_aregs,
+		'(a)':all_indirect,
+		'-(a)':all_predec,
+		'(a)+':all_postinc,
+		'(n,a)':all_disp,
+		'(n,a,x)':all_indexed,
+		'(n,pc)':rand_pc_disp,
+		'(n,pc,x)':all_pc_indexed,
+		'(n).w':rand_abs_short,
+		'(n).l':rand_abs_long
+	}
+	if mode in mapping:
+		ret = mapping[mode]
+		if type(ret) != list:
+			ret = ret()
+		return ret
+	elif mode == '#n':
+		return rand_immediate(size)
+	elif mode.startswith('#(') and mode.endswith(')'):
+		inner = mode[2:-1]
+		start,sep,end = inner.partition('-')
+		return [Immediate(num) for num in range(int(start), int(end))]
+		
+class Inst2Op(object):
+	def __init__(self, name, size, src, dst):
+		self.name = name
+		self.size = size
+		self.src = src
+		self.dst = dst
+	
+	def __str__(self):
+		return self.name + '.' + self.size + ' ' + str(self.src) + ', ' + str(self.dst)
+	
+	def write_init(self, outfile, already):
+		self.src.write_init(outfile, self.size, already)
+		self.dst.write_init(outfile, self.size, already)
+	
+	def invalidate_dest(self, already):
+		if type(self.dst) == Register:
+			del already[str(self.dst)]
+	
+	def save_result(self, reg, always):
+		if always or type(self.dst) != Register:
+			return 'move.' + self.size + ' ' + str(self.dst) + ', ' + str(reg)
+		else:
+			return ''
+	
+	def consume_regs(self, program):
+		self.src.consume_regs(program)
+		self.dst.consume_regs(program)
+
+class Entry(object):
+	def __init__(self, line):
+		fields = split_fields(line)
+		self.name = fields[0]
+		sizes = fields[1]
+		sources = fields[2].split(';')
+		dests = fields[3].split(';')
+		combos = []
+		for size in sizes:
+			for source in sources:
+				if size != 'b' or source != 'a':
+					for dest in dests:
+						if size != 'b' or dest != 'a':
+							combos.append((size, source, dest))
+		self.cases = combos
+		
+	def programs(self):
+		res = []
+		for (size, src, dst) in self.cases:
+			sources = get_variations(src, size)
+			dests = get_variations(dst, size)
+			for source in sources:
+				for dest in dests:
+					res.append(Program(Inst2Op(self.name, size, source, dest)))
+		return res
+		
+def process_entries(f):
+	entries = []
+	for line in f:
+		if not line.startswith('Name') and not line.startswith('#') and len(line.strip()) > 0:
+			entries.append(Entry(line))
+	return entries
+
+
+def main(args):
+	entries = process_entries(open('testcases.txt'))
+	for entry in entries:
+		programs = entry.programs()
+		for program in programs:
+			f = open('generated_tests/' + program.name() + '.s68', 'w')
+			program.write_rom_test(f)
+			f.close()
+	
+if __name__ == '__main__':
+	import sys
+	main(sys.argv)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/maketests.py	Fri Apr 19 09:29:37 2013 -0700
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+from glob import glob
+import subprocess
+from sys import exit
+
+sources = set()
+for path in glob('generated_tests/*.s68'):
+	sources.add(path)
+
+bins = set()
+for path in glob('generated_tests/*.bin'):
+	bins.add(path)
+
+for path in sources:
+	binpath = path.replace('.s68', '.bin')
+	if not binpath in bins:
+		print binpath
+		res = subprocess.call(['vasmm68k_mot', '-Fbin', '-m68000', '-no-opt', '-spaces', '-o', binpath, path])
+		if res != 0:
+			print 'vasmm68k_mot returned non-zero status code', res
+			exit(1)
+