From 27191aa76983816008d816fcb9d39e2abbcaff06 Mon Sep 17 00:00:00 2001 From: Richard Dymond Date: Sat, 29 Jun 2024 17:51:39 -0300 Subject: [PATCH] Add tools/z80-timing-tests.py --- .github/workflows/z80-timing-tests.yml | 39 ++++++++ tools/z80-timing-tests.py | 127 +++++++++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 .github/workflows/z80-timing-tests.yml create mode 100755 tools/z80-timing-tests.py diff --git a/.github/workflows/z80-timing-tests.yml b/.github/workflows/z80-timing-tests.yml new file mode 100644 index 00000000..e0c40502 --- /dev/null +++ b/.github/workflows/z80-timing-tests.yml @@ -0,0 +1,39 @@ +name: z80-timing-tests + +on: + push: + paths: + - 'skoolkit/simulator.py' + - 'skoolkit/cmiosimulator.py' + - 'c/csimulator.c' + workflow_dispatch: + +jobs: + z80-timing-tests: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + sim: [--cmio, --ccmio] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Download TAP file + run: | + wget -O tests.tap "https://zxe.io/depot/software/ZX Spectrum/ZX Spectrum Timing Tests - 48K v1.0 (2010-04-14)(Butler, Richard; Butler, Tim)[!].tap" + + - if: matrix.sim == '--ccmio' + name: Build csimulator + run: python setup.py build_ext -b . + + - name: Run tests + run: | + export SKOOLKIT_HOME=$(pwd) + tools/z80-timing-tests.py ${{ matrix.sim }} tests.tap diff --git a/tools/z80-timing-tests.py b/tools/z80-timing-tests.py new file mode 100755 index 00000000..d6fcffb4 --- /dev/null +++ b/tools/z80-timing-tests.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +import argparse +import os +import sys +import time + +SKOOLKIT_HOME = os.environ.get('SKOOLKIT_HOME') +if not SKOOLKIT_HOME: + sys.stderr.write('SKOOLKIT_HOME is not set; aborting\n') + sys.exit(1) +if not os.path.isdir(SKOOLKIT_HOME): + sys.stderr.write('SKOOLKIT_HOME={}; directory not found\n'.format(SKOOLKIT_HOME)) + sys.exit(1) +sys.path.insert(0, SKOOLKIT_HOME) + +from skoolkit import ROM48, tap2sna, CCMIOSimulator, read_bin_file +from skoolkit.cmiosimulator import CMIOSimulator +from skoolkit.simutils import PC, T + +class Tracer: + def __init__(self): + self.msg = '' + self.press_enter = 60 + self.passed = 0 + self.failed = 0 + + def run(self, simulator): + registers = simulator.registers + if hasattr(simulator, 'opcodes'): + opcodes = simulator.opcodes + memory = simulator.memory + pc = registers[PC] + while True: + opcodes[memory[pc]]() + if registers[26] and registers[25] % 69888 < 32: + simulator.accept_interrupt(registers, memory, pc) + pc = registers[24] + if pc == 0x15F2: + if self.print_cb(registers[0]): + break + else: + while True: + simulator.run(interrupts=True) + if registers[24] == 0x15F2: + if self.print_cb(registers[0]): + break + + def read_port(self, registers, port): + if self.press_enter > 0 and port == 0xBFFE: + self.press_enter -= 1 + return 0xFE # Press ENTER + return 0xFF + + def print_cb(self, a): + if a >= 32: + self.msg += chr(a) + elif a == 13: + if self.msg.strip(): + if self.msg == 'ZXSPECTRUMZXSPECTRUMZXSPECTRUMZX': + # Tests 36+ test floating bus emulation, so quit now + return True + print(self.msg) + if self.msg.endswith('Pass'): + self.passed += 1 + elif self.msg.endswith('Fail'): + self.failed += 1 + else: + print() + self.msg = '' + return False + +def write_snapshot(fname, ram, registers, state): + global snapshot, reg + snapshot = [0] * 65536 + rom = read_bin_file(ROM48) + snapshot[:len(rom)] = rom + snapshot[0x4000:] = ram + reg = {} + for spec in registers: + s = spec.split('=', 1) + reg[s[0]] = int(s[1]) + +def load_tap(tapfile): + global snapshot, reg + tap2sna.write_snapshot = write_snapshot + tap2sna.main(('--start', '7997', tapfile)) + return snapshot, reg + +def run(tapfile, options): + if options.cmio: + simulator_cls = CMIOSimulator + else: + if CCMIOSimulator is None: + sys.stderr.write('ERROR: CCMIOSimulator is not available\n') + sys.exit(1) + simulator_cls = CCMIOSimulator + snapshot, registers = load_tap(tapfile) + simulator = simulator_cls(snapshot, registers, {'iff': 1}) + tracer = Tracer() + simulator.set_tracer(tracer) + begin = time.time() + tracer.run(simulator) + rt = time.time() - begin + z80t = simulator.registers[T] / 3500000 + speed = z80t / rt + print(f'\n{tracer.passed} tests passed\n{tracer.failed} tests failed\n') + print(f'Z80 execution time: {simulator.registers[T]} T-states ({z80t:.03f}s)') + print(f'Simulation time: {rt:.03f}s (x{speed:.02f})') + return 1 if tracer.failed else 0 + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + usage='{} [options] FILE'.format(os.path.basename(sys.argv[0])), + description="Run 'ZX Spectrum Timing Tests 48K' on a SkoolKit CMIOSimulator instance. " + "FILE must be a TAP file that loads the tests.", + add_help=False + ) + parser.add_argument('tapfile', help=argparse.SUPPRESS, nargs='?') + group = parser.add_argument_group('Options') + group.add_argument('--ccmio', action='store_true', + help="Run tests with CCMIOSimulator (this is the default).") + group.add_argument('--cmio', action='store_true', + help="Run tests with CMIOSimulator.") + namespace, unknown_args = parser.parse_known_args() + if unknown_args or namespace.tapfile is None: + parser.exit(2, parser.format_help()) + sys.exit(run(namespace.tapfile, namespace))