mirror of
https://github.com/mnaberez/py65.git
synced 2024-05-31 12:41:31 +00:00
More cleanup to functional tests. Automatically skip the Klaus
Dormann ones if not present.
This commit is contained in:
parent
7d5a29cbaa
commit
5b28c007d9
56
py65/tests/devices/functional_tests.py
Normal file
56
py65/tests/devices/functional_tests.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
"""Helpers for functional tests based on executing 65x02 object code."""
|
||||
|
||||
|
||||
class FunctionalTestExecutor(object):
|
||||
def __init__(self, mpu_class, filename, load_addr):
|
||||
self.mpu_class = mpu_class
|
||||
self.mpu = self._make_mpu()
|
||||
|
||||
object_code = bytearray(open(filename, "rb").read())
|
||||
self.write_memory(load_addr, object_code)
|
||||
|
||||
def _make_mpu(self, *args, **kargs):
|
||||
mpu = self.mpu_class(*args, **kargs)
|
||||
if 'memory' not in kargs:
|
||||
mpu.memory = 0x10000 * [0xAA]
|
||||
return mpu
|
||||
|
||||
def write_memory(self, start_address, bytes):
|
||||
self.mpu.memory[start_address:start_address + len(bytes)] = bytes
|
||||
|
||||
@staticmethod
|
||||
def never_trace_predicate(pc):
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def always_trace_predicate(pc):
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def address_completion_predicate(addrs):
|
||||
"""Terminate test when PC loops to itself or enters addrs set"""
|
||||
def completion_predicate(mpu, old_pc):
|
||||
return mpu.pc == old_pc or mpu.pc in addrs
|
||||
|
||||
return completion_predicate
|
||||
|
||||
def execute(
|
||||
self, pc, completion_predicate,
|
||||
trace_predicate=never_trace_predicate
|
||||
):
|
||||
self.mpu.pc = pc
|
||||
|
||||
while True:
|
||||
old_pc = self.mpu.pc
|
||||
self.mpu.step(trace=trace_predicate(self.mpu.pc))
|
||||
if completion_predicate(self.mpu, old_pc):
|
||||
break
|
||||
|
||||
|
||||
def trace_on_assertion(executor, *args, **kwargs):
|
||||
try:
|
||||
return executor(*args, **kwargs)
|
||||
except AssertionError:
|
||||
# Rerun with tracing
|
||||
return executor(*args, trace=True, **kwargs)
|
||||
|
76
py65/tests/devices/test_bcd_functional.py
Normal file
76
py65/tests/devices/test_bcd_functional.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
"""65(c)02-based test suite for BCD implementation correctness.
|
||||
|
||||
See source code in bcd/*.c65 for more details.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import py65.devices.mpu6502
|
||||
import py65.devices.mpu65c02
|
||||
from py65.tests.devices import functional_tests
|
||||
|
||||
|
||||
def run_bcd_test_case(mpu_class, filename, trace=False):
|
||||
executor = functional_tests.FunctionalTestExecutor(
|
||||
mpu_class, filename, load_addr=0x200)
|
||||
|
||||
# $1000: JSR $0200
|
||||
executor.write_memory(0x1000, [0x20, 0x00, 0x02])
|
||||
|
||||
# Set up BRK vector pointing to $2000 so we can trap PC
|
||||
executor.write_memory(0xfffe, [0x00, 0x20])
|
||||
|
||||
if trace:
|
||||
tracer = executor.always_trace_predicate
|
||||
else:
|
||||
tracer = executor.never_trace_predicate
|
||||
|
||||
executor.execute(
|
||||
0x1000,
|
||||
# If we are looping at the same PC, or we return
|
||||
# from the JSR, or we hit the BRK vector, then we are done.
|
||||
executor.address_completion_predicate({0x1003, 0x2000}),
|
||||
tracer
|
||||
)
|
||||
mpu = executor.mpu
|
||||
|
||||
if mpu.memory[0x0b] != 0: # Tests did not complete successfully
|
||||
# Display internal test state; read the .c65 source to understand
|
||||
# what these mean about the particular test case that failed.
|
||||
assert False, (
|
||||
"N1={:02x} N2={:02x} HA={:02x} HNVZC={:08b} DA={:02x} "
|
||||
"DNVZC={:08b} AR={:02x} NF={:08b} VF={:08b} ZF={:08b} "
|
||||
"CF={:08b}".format(
|
||||
mpu.memory[0x00], mpu.memory[0x01], mpu.memory[0x02],
|
||||
mpu.memory[0x03], mpu.memory[0x04], mpu.memory[0x05],
|
||||
mpu.memory[0x06], mpu.memory[0x07], mpu.memory[0x08],
|
||||
mpu.memory[0x09], mpu.memory[0x0a]
|
||||
))
|
||||
|
||||
|
||||
class BCDFunctionalTests(unittest.TestCase):
|
||||
|
||||
@staticmethod
|
||||
def test6502DecimalTest():
|
||||
functional_tests.trace_on_assertion(
|
||||
run_bcd_test_case,
|
||||
py65.devices.mpu6502.MPU,
|
||||
"devices/bcd/6502_decimal_test.bin"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def test65c02DecimalTest():
|
||||
functional_tests.trace_on_assertion(
|
||||
run_bcd_test_case,
|
||||
py65.devices.mpu65c02.MPU,
|
||||
"devices/bcd/65C02_decimal_test.bin"
|
||||
)
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.findTestCases(sys.modules[__name__])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='test_suite')
|
|
@ -1,111 +0,0 @@
|
|||
import unittest
|
||||
import sys
|
||||
import py65.devices.mpu6502
|
||||
import py65.devices.mpu65c02
|
||||
|
||||
|
||||
class BinaryObjectTests(unittest.TestCase):
|
||||
"""Test cases based on executing 65x02 object code."""
|
||||
|
||||
# If test fails, rerun with execution tracing enabled
|
||||
TRACE_TEST_FAILURE = False
|
||||
|
||||
def binaryObjectTestCase(self, filename, load_addr, pc, success_addr, should_trace=None):
|
||||
mpu = self._make_mpu()
|
||||
mpu.pc = pc
|
||||
|
||||
object_code = bytearray(open(filename, "r").read())
|
||||
self._write(mpu.memory, load_addr, object_code)
|
||||
|
||||
if not should_trace:
|
||||
should_trace = lambda pc: False
|
||||
|
||||
while True:
|
||||
old_pc = mpu.pc
|
||||
mpu.step(trace=should_trace(mpu.pc))
|
||||
if mpu.pc == old_pc:
|
||||
break
|
||||
|
||||
assert mpu.pc == success_addr, "%s 0xb=%02x 0xc=%02x 0xd=%02x 0xf=%02x" % (
|
||||
mpu, mpu.memory[0xb], mpu.memory[0xc], mpu.memory[0xd], mpu.memory[0xf])
|
||||
|
||||
# Test Helpers
|
||||
|
||||
def _write(self, memory, start_address, bytes):
|
||||
memory[start_address:start_address + len(bytes)] = bytes
|
||||
|
||||
def _make_mpu(self, *args, **kargs):
|
||||
mpu = self.MPU(*args, **kargs)
|
||||
if 'memory' not in kargs:
|
||||
mpu.memory = 0x10000 * [0xAA]
|
||||
return mpu
|
||||
|
||||
def runBinaryTest(self, executor, filename):
|
||||
try:
|
||||
return executor(filename)
|
||||
except AssertionError:
|
||||
# Rerun with tracing
|
||||
if self.TRACE_TEST_FAILURE:
|
||||
return executor(filename, trace=True)
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
class FunctionalBCDTests(BinaryObjectTests):
|
||||
def bcd_test_case(self, filename, trace=False):
|
||||
mpu = self._make_mpu()
|
||||
mpu.pc = 0x1000
|
||||
|
||||
object_code = bytearray(open(filename, "r").read())
|
||||
self._write(mpu.memory, 0x200, object_code)
|
||||
|
||||
# $1000: JSR $0200
|
||||
self._write(mpu.memory, 0x1000, [0x20, 0x00, 0x02])
|
||||
|
||||
# Set up BRK vector pointing to $2000 so we can trap PC
|
||||
self._write(mpu.memory, 0xfffe, [0x00, 0x20])
|
||||
|
||||
def should_trace(pc):
|
||||
return trace
|
||||
|
||||
while True:
|
||||
old_pc = mpu.pc
|
||||
mpu.step(trace=should_trace(mpu.pc))
|
||||
# If we are looping at the same PC, or we return
|
||||
# from the JSR, or we hit the BRK vector, then we are done.
|
||||
if mpu.pc == old_pc or mpu.pc == 0x1003 or mpu.pc == 0x2000:
|
||||
break
|
||||
|
||||
if mpu.memory[0x0b] != 0: # Tests did not complete successfully
|
||||
assert False, ("N1={:02x} N2={:02x} HA={:02x} HNVZC={:08b} DA={"
|
||||
":02x} DNVZC={:08b} AR={:02x} NF={:08b} VF={:08b} "
|
||||
"ZF={:08b} CF={:08b}".format(
|
||||
mpu.memory[0x00], mpu.memory[0x01], mpu.memory[0x02],
|
||||
mpu.memory[0x03], mpu.memory[0x04], mpu.memory[0x05],
|
||||
mpu.memory[0x06], mpu.memory[0x07], mpu.memory[0x08],
|
||||
mpu.memory[0x09], mpu.memory[0x0a]
|
||||
))
|
||||
|
||||
|
||||
class Functional6502Tests(FunctionalBCDTests):
|
||||
MPU = py65.devices.mpu6502.MPU
|
||||
|
||||
def test6502DecimalTest(self):
|
||||
self.runBinaryTest(
|
||||
self.bcd_test_case, "devices/bcd/6502_decimal_test.bin")
|
||||
|
||||
|
||||
class Functional65C02Tests(FunctionalBCDTests):
|
||||
MPU = py65.devices.mpu65c02.MPU
|
||||
|
||||
def test65C02DecimalTest(self):
|
||||
self.runBinaryTest(
|
||||
self.bcd_test_case, "devices/bcd/65C02_decimal_test.bin")
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.findTestCases(sys.modules[__name__])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='test_suite')
|
|
@ -1,87 +0,0 @@
|
|||
import unittest
|
||||
import sys
|
||||
import py65.devices.mpu65c02
|
||||
|
||||
|
||||
class KlausDormannTests(unittest.TestCase):
|
||||
"""Runs Klaus Dormann's 6502-based test suites"""
|
||||
|
||||
MPU = py65.devices.mpu65c02.MPU
|
||||
|
||||
def klausTestCase(self, filename, load_addr, pc, completion_criteria,
|
||||
should_trace=None):
|
||||
mpu = self._make_mpu()
|
||||
mpu.pc = pc
|
||||
|
||||
object_code = bytearray(open(filename, "r").read())
|
||||
self._write(mpu.memory, load_addr, object_code)
|
||||
|
||||
if not should_trace:
|
||||
should_trace = lambda pc: False
|
||||
|
||||
while True:
|
||||
old_pc = mpu.pc
|
||||
mpu.step(trace=should_trace(mpu.pc))
|
||||
if completion_criteria(mpu, old_pc):
|
||||
break
|
||||
|
||||
return mpu
|
||||
|
||||
def make_completion_criteria(self, success_addr):
|
||||
def completion_criteria(mpu, old_pc):
|
||||
return mpu.pc == old_pc or mpu.pc == success_addr
|
||||
|
||||
return completion_criteria
|
||||
|
||||
def test6502FunctionalTest(self):
|
||||
success_addr = 0x3399
|
||||
completion_criteria = self.make_completion_criteria(success_addr)
|
||||
|
||||
mpu = self.klausTestCase(
|
||||
"devices/6502_functional_test.bin", 0x0, 0x400,
|
||||
completion_criteria
|
||||
)
|
||||
|
||||
assert mpu.pc == success_addr, (
|
||||
"%s 0xb=%02x 0xc=%02x 0xd=%02x 0xf=%02x" % (
|
||||
mpu, mpu.memory[0xb], mpu.memory[0xc], mpu.memory[0xd],
|
||||
mpu.memory[0xf])
|
||||
)
|
||||
|
||||
def test65C02ExtendedOpcodesTest(self):
|
||||
success_addr = 0x1570
|
||||
completion_criteria = self.make_completion_criteria(success_addr)
|
||||
|
||||
# Modified version of 65C02_extended_opcodes_test that defines
|
||||
# rkwl_wdc_op = 0 (don't test BBR/BBS instructions, which we do not
|
||||
# implement) and many of the NOP tests for undefined opcodes which are
|
||||
# not yet implemented here.
|
||||
mpu = self.klausTestCase(
|
||||
"devices/65C02_extended_opcodes_test_modified.bin", 0xa, 0x400,
|
||||
completion_criteria
|
||||
)
|
||||
|
||||
assert mpu.pc == success_addr, (
|
||||
"%s 0xb=%02x 0xc=%02x 0xd=%02x 0xf=%02x" % (
|
||||
mpu, mpu.memory[0xb], mpu.memory[0xc], mpu.memory[0xd],
|
||||
mpu.memory[0xf])
|
||||
)
|
||||
|
||||
# Test Helpers
|
||||
|
||||
def _write(self, memory, start_address, bytes):
|
||||
memory[start_address:start_address + len(bytes)] = bytes
|
||||
|
||||
def _make_mpu(self, *args, **kargs):
|
||||
mpu = self.MPU(*args, **kargs)
|
||||
if 'memory' not in kargs:
|
||||
mpu.memory = 0x10000 * [0xAA]
|
||||
return mpu
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.findTestCases(sys.modules[__name__])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='test_suite')
|
82
py65/tests/devices/test_klaus_dormann_functional.py
Normal file
82
py65/tests/devices/test_klaus_dormann_functional.py
Normal file
|
@ -0,0 +1,82 @@
|
|||
"""Harness for running Klaus Dormann's 65(c)02 functional test suite
|
||||
|
||||
These are quite comprehensive test suites for 65(c)02 implementation
|
||||
correctness, but they're licensed under the GPL so we cannot include
|
||||
the binary object code here (this is just the test harness for executing
|
||||
them in py65).
|
||||
|
||||
Obtain the object files from
|
||||
https://github.com/Klaus2m5/6502_65C02_functional_tests instead.
|
||||
"""
|
||||
|
||||
import os.path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import py65.devices.mpu6502
|
||||
import py65.devices.mpu65c02
|
||||
from py65.tests.devices import functional_tests
|
||||
|
||||
|
||||
def run_klaus_dormann_test(mpu_class, filename, load_addr, success_addr,
|
||||
trace=False):
|
||||
executor = functional_tests.FunctionalTestExecutor(
|
||||
mpu_class, filename, load_addr)
|
||||
|
||||
if trace:
|
||||
tracer = executor.always_trace_predicate
|
||||
else:
|
||||
tracer = executor.never_trace_predicate
|
||||
|
||||
executor.execute(
|
||||
0x400, executor.address_completion_predicate({success_addr}), tracer)
|
||||
|
||||
mpu = executor.mpu
|
||||
assert mpu.pc == success_addr, (
|
||||
"%s 0xb=%02x 0xc=%02x 0xd=%02x 0xf=%02x" % (
|
||||
mpu, mpu.memory[0xb], mpu.memory[0xc], mpu.memory[0xd],
|
||||
mpu.memory[0xf])
|
||||
)
|
||||
|
||||
|
||||
class KlausDormannTests(unittest.TestCase):
|
||||
"""Runs Klaus Dormann's 6502-based test suites"""
|
||||
|
||||
def test6502FunctionalTest(self):
|
||||
filename = "devices/6502_functional_test.bin"
|
||||
if not os.path.exists(filename):
|
||||
self.skipTest("%s not available, skipping")
|
||||
|
||||
functional_tests.trace_on_assertion(
|
||||
run_klaus_dormann_test,
|
||||
py65.devices.mpu6502.MPU,
|
||||
filename,
|
||||
load_addr=0x0,
|
||||
success_addr=0x3399
|
||||
)
|
||||
|
||||
def test65c02ExtendedOpcodeTest(self):
|
||||
# Modified version of 65C02_extended_opcodes_test that defines
|
||||
# rkwl_wdc_op = 0 (don't test BBR/BBS instructions, which we do not
|
||||
# implement) and many of the NOP tests for undefined opcodes which
|
||||
# are not yet implemented here.
|
||||
filename = "devices/65C02_extended_opcodes_test_modified.bin"
|
||||
|
||||
if not os.path.exists(filename):
|
||||
self.skipTest("%s not available, skipping")
|
||||
|
||||
functional_tests.trace_on_assertion(
|
||||
run_klaus_dormann_test,
|
||||
py65.devices.mpu65c02.MPU,
|
||||
filename,
|
||||
load_addr=0xa,
|
||||
success_addr=0x1570
|
||||
)
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.findTestCases(sys.modules[__name__])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='test_suite')
|
Loading…
Reference in New Issue
Block a user