diff --git a/skoolkit/pagingtracer.py b/skoolkit/pagingtracer.py index b459618b..820d8dba 100644 --- a/skoolkit/pagingtracer.py +++ b/skoolkit/pagingtracer.py @@ -16,9 +16,9 @@ from skoolkit.snapshot import BANKS_128K -class PagingTracer: # pragma: no cover +class PagingTracer: def write_port(self, registers, port, value): - if port % 2 == 0: + if port % 2 == 0: # pragma: no cover self.border = value % 8 elif port & 0x8002 == 0: mem = self.simulator.memory diff --git a/skoolkit/snapinfo.py b/skoolkit/snapinfo.py index 50141f3b..dce80194 100644 --- a/skoolkit/snapinfo.py +++ b/skoolkit/snapinfo.py @@ -145,6 +145,7 @@ def _parse_z80(z80file): reg.pc = get_word(header, 6) else: reg.pc = get_word(header, 32) + reg.out7ffd = header[35] reg.sp = get_word(header, 8) reg.i = header[10] reg.r = 128 * (header[12] & 1) + (header[11] & 127) @@ -202,7 +203,7 @@ def _analyse_z80(z80file, header, reg, ram_blocks): print(f'T-states: {reg.tstates}') print('Border: {}'.format((header[12] // 2) & 7)) if bank is not None: - print('Port $7FFD: {} - bank {} (block {}) paged into 49152-65535 C000-FFFF'.format(header[35], bank, bank + 3)) + print('Port $7FFD: {} - bank {} (block {}) paged into 49152-65535 C000-FFFF'.format(reg.out7ffd, bank, bank + 3)) # Print register contents print('Registers:') @@ -288,50 +289,48 @@ def _parse_szx(szxfile): reg.tstates = get_dword(block, 29) elif block_id == 'SPCR': reg.border = block[0] + reg.out7ffd = block[1] return header, reg, blocks def _get_block_id(data, index): return ''.join([chr(b) for b in data[index:index+ 4]]) -def _print_keyb(block, variables): +def _print_keyb(block, reg): issue2 = get_dword(block, 0) & 1 return ['Issue 2 emulation: {}abled'.format('en' if issue2 else 'dis')] -def _print_ramp(block, variables): - lines = [] +def _print_ramp(block, reg): flags = get_word(block, 0) compressed = 'compressed' if flags & 1 else 'uncompressed' page = block[2] ram = block[3:] - lines.append("Page: {}".format(page)) - machine_id = variables['chMachineId'] addresses = '' if page == 5: addresses = '16384-32767 4000-7FFF' elif page == 2: addresses = '32768-49151 8000-BFFF' - elif machine_id > 1 and page == variables['ch7ffd'] & 7: + elif reg.machine_id > 1 and page == reg.out7ffd & 7: addresses = '49152-65535 C000-FFFF' if addresses: addresses += ': ' - lines.append("RAM: {}{} bytes, {}".format(addresses, len(ram), compressed)) - return lines - -def _print_spcr(block, variables): - lines = [] - lines.append('Border: {}'.format(block[0])) - ch7ffd = block[1] - variables['ch7ffd'] = ch7ffd - bank = ch7ffd & 7 - lines.append('Port $7FFD: {} (bank {} paged into 49152-65535 C000-FFFF)'.format(ch7ffd, bank)) - return lines - -def _print_z80r(reg): - lines = [] - lines.append('Interrupts: {}abled'.format('en' if reg.iff2 else 'dis')) - lines.append('Interrupt mode: {}'.format(reg.im)) - lines.append(f'T-states: {reg.tstates}') + return ( + f'Page: {page}', + f'RAM: {addresses}{len(ram)} bytes, {compressed}' + ) + +def _print_spcr(block, reg): + return ( + f'Border: {reg.border}', + f'Port $7FFD: {reg.out7ffd} (bank {reg.out7ffd & 7} paged into 49152-65535 C000-FFFF)' + ) + +def _print_z80r(block, reg): + lines = [ + 'Interrupts: {}abled'.format('en' if reg.iff2 else 'dis'), + f'Interrupt mode: {reg.im}', + f'T-states: {reg.tstates}' + ] return lines + reg.get_lines() SZX_BLOCK_PRINTERS = { @@ -343,19 +342,14 @@ def _print_z80r(reg): def _analyse_szx(header, reg, blocks): print('Version: {}.{}'.format(header[4], header[5])) - machine_id = header[6] - print('Machine: {}'.format(SZX_MACHINES.get(machine_id, 'Unknown'))) - variables = {'chMachineId': machine_id} + reg.machine_id = header[6] + print('Machine: {}'.format(SZX_MACHINES.get(reg.machine_id, 'Unknown'))) for block_id, block in blocks: print('{}: {} bytes'.format(block_id, len(block))) printer = SZX_BLOCK_PRINTERS.get(block_id) if printer: - if block_id == 'Z80R': - lines = printer(reg) - else: - lines = printer(block, variables) - for line in lines: + for line in printer(block, reg): print(" " + line) ############################################################################### @@ -385,8 +379,10 @@ def _parse_sna(snafile): reg.sp = get_word(sna, 23) if len(sna) > 49179: reg.pc = get_word(sna, 49179) + reg.out7ffd = sna[49181] else: reg.pc = get_word(sna, reg.sp - 16357) + reg.out7ffd = 0 reg.border = sna[26] reg.iff2 = (sna[19] & 4) // 4 reg.im = sna[25] diff --git a/skoolkit/trace.py b/skoolkit/trace.py index b45a1773..aca3fabc 100644 --- a/skoolkit/trace.py +++ b/skoolkit/trace.py @@ -17,8 +17,9 @@ import argparse import time -from skoolkit import ROM48, VERSION, SkoolKitError, get_int_param, integer, read_bin_file -from skoolkit.snapshot import make_snapshot, poke, print_reg_help, write_z80v3 +from skoolkit import ROM48, ROM128, VERSION, SkoolKitError, get_int_param, integer, read_bin_file +from skoolkit.pagingtracer import PagingTracer +from skoolkit.snapshot import BANKS_128K, make_snapshot, poke, print_reg_help, write_z80v3 from skoolkit.simulator import (Simulator, A, F, B, C, D, E, H, L, IXh, IXl, IYh, IYl, SP, I, R, xA, xF, xB, xC, xD, xE, xH, xL, PC, T) from skoolkit.snapinfo import parse_snapshot @@ -35,14 +36,17 @@ A'={^A:<3} F'={^F:08b} BC'={BC':<5} DE'={DE':<5} HL'={HL':<5} SP={SP:<5} """.strip() -class Tracer: - def __init__(self, border): - self.operations = 0 +class Tracer(PagingTracer): + def __init__(self, simulator, border, out7ffd): + self.simulator = simulator self.border = border + self.out7ffd = out7ffd + self.operations = 0 self.spkr = None self.out_times = [] - def run(self, simulator, start, stop, verbose, max_operations, max_tstates, decimal, interrupts): + def run(self, start, stop, verbose, max_operations, max_tstates, decimal, interrupts): + simulator = self.simulator opcodes = simulator.opcodes memory = simulator.memory registers = simulator.registers @@ -130,6 +134,8 @@ def write_port(self, registers, port, value): if self.spkr != value & 0x10: self.spkr = value & 0x10 self.out_times.append(registers[T]) + elif port & 0x8002 == 0: + super().write_port(registers, port, value) def get_registers(sna_reg, specs): if sna_reg: @@ -197,15 +203,26 @@ def simplify(delays, depth): return ', '.join(s0) def run(snafile, options): - if snafile == '.': - memory = [0] * 65536 + if snafile in ('48', '128'): + if snafile == '48': + memory = [0] * 0x10000 + else: + memory = [0] * 0x28000 reg = None org = 0 else: - memory, org = make_snapshot(snafile, options.org)[0:2] + memory, org = make_snapshot(snafile, options.org, page=-1)[:2] reg = parse_snapshot(snafile)[1] if snafile.lower()[-4:] == '.sna': reg.sp = (reg.sp + 2) % 65536 + if reg: + state = {'im': reg.im, 'iff': reg.iff2, 'tstates': reg.tstates} + border = reg.border + out7ffd = reg.out7ffd + else: + state = {'im': 1, 'iff': 1, 'tstates': 0} + border = 7 + out7ffd = 0 start = options.start if start is None: if reg: @@ -216,34 +233,30 @@ def run(snafile, options): start = org if options.rom: rom = read_bin_file(options.rom) + memory[:len(rom)] = rom + elif len(memory) == 65536: + memory[:0x4000] = read_bin_file(ROM48) else: - rom = read_bin_file(ROM48) - memory[:len(rom)] = rom + rom = (out7ffd & 16) // 16 + memory[:0x4000] = read_bin_file(ROM128[rom]) + memory[0x24000:] = read_bin_file(ROM128[1 - rom]) + page = out7ffd & 7 + if page: + n = BANKS_128K[page] + memory[n:n + 0x4000], memory[0xC000:0x10000] = memory[0xC000:0x10000], memory[n:n + 0x4000] for spec in options.pokes: poke(memory, spec) - if reg: - im = reg.im - iff = reg.iff2 - border = reg.border - tstates = reg.tstates - else: - im = 1 - iff = 1 - border = 7 - tstates = 0 - state = {'im': im, 'iff': iff, 'tstates': tstates} fast = options.verbose == 0 and not options.interrupts config = {'fast_djnz': fast, 'fast_ldir': fast} simulator = Simulator(memory, get_registers(reg, options.reg), state, config) - tracer = Tracer(border) + tracer = Tracer(simulator, border, out7ffd) simulator.set_tracer(tracer) begin = time.time() - tracer.run(simulator, start, options.stop, options.verbose, - options.max_operations, options.max_tstates, options.decimal, - options.interrupts) + tracer.run(start, options.stop, options.verbose, options.max_operations, + options.max_tstates, options.decimal, options.interrupts) rt = time.time() - begin if options.stats: - z80t = simulator.registers[T] - tstates + z80t = simulator.registers[T] - state['tstates'] z80s = z80t / 3500000 speed = z80s / (rt or 0.001) # Avoid division by zero print(f'Z80 execution time: {z80t} T-states ({z80s:.03f}s)') @@ -257,7 +270,7 @@ def run(snafile, options): print('Sound duration: {} T-states ({:.03f}s)'.format(duration, duration / 3500000)) print('Delays: {}'.format(simplify(delays, options.depth))) if options.dump: - ram = simulator.memory[16384:] + ram = simulator.memory[0x4000:0x24000] r = simulator.registers registers = ( f'a={r[A]}', @@ -279,6 +292,7 @@ def run(snafile, options): ) state = ( f'border={tracer.border}', + f'7ffd={tracer.out7ffd}', f'iff={simulator.iff}', f'im={simulator.imode}', f'tstates={r[T]}' @@ -290,7 +304,7 @@ def main(args): parser = argparse.ArgumentParser( usage='trace.py [options] FILE [file.z80]', description="Trace Z80 machine code execution. " - "FILE may be a binary (raw memory) file, a SNA, SZX or Z80 snapshot, or '.' for no snapshot. " + "FILE may be a binary (raw memory) file, a SNA, SZX or Z80 snapshot, or '48' or '128' for no snapshot. " "If 'file.z80' is given, a Z80 snapshot is written after execution has completed.", add_help=False ) @@ -319,8 +333,7 @@ def main(args): help="Set the value of a register. Do '--reg help' for more information. " "This option may be used multiple times.") group.add_argument('--rom', metavar='FILE', - help='Patch in a ROM at address 0 from this file. ' - 'By default the 48K ZX Spectrum ROM is used.') + help='Patch in a ROM at address 0 from this file.') group.add_argument('-s', '--start', metavar='ADDR', type=integer, help='Start execution at this address.') group.add_argument('-S', '--stop', metavar='ADDR', type=integer, diff --git a/sphinx/source/changelog.rst b/sphinx/source/changelog.rst index 827e72a4..88a9e8c6 100644 --- a/sphinx/source/changelog.rst +++ b/sphinx/source/changelog.rst @@ -12,6 +12,7 @@ Changelog * Added the ``load`` simulated LOAD configuration parameter to :ref:`tap2sna.py ` (to specify an alternative command line to use to load the tape) +* Added support to :ref:`trace.py` for executing machine code in 128K snapshots * Added support to :ref:`control directive loops ` for avoiding repetition of an ``N`` directive at the start of a loop diff --git a/sphinx/source/commands.rst b/sphinx/source/commands.rst index 6fc26247..16d0a895 100644 --- a/sphinx/source/commands.rst +++ b/sphinx/source/commands.rst @@ -1699,8 +1699,8 @@ To list the options supported by `tapinfo.py`, run it with no arguments:: trace.py -------- -`trace.py` simulates the execution of machine code in a 48K memory snapshot. -For example:: +`trace.py` simulates the execution of machine code in a 48K or 128K memory +snapshot. For example:: $ trace.py --start 32768 --stop 49152 game.z80 @@ -1709,8 +1709,8 @@ To list the options supported by `trace.py`, run it with no arguments:: usage: trace.py [options] FILE [file.z80] Trace Z80 machine code execution. FILE may be a binary (raw memory) file, a - SNA, SZX or Z80 snapshot, or '.' for no snapshot. If 'file.z80' is given, a - Z80 snapshot is written after execution has completed. + SNA, SZX or Z80 snapshot, or '48' or '128' for no snapshot. If 'file.z80' is + given, a Z80 snapshot is written after execution has completed. Options: --audio Show audio delays. @@ -1729,8 +1729,7 @@ To list the options supported by `trace.py`, run it with no arguments:: -r name=value, --reg name=value Set the value of a register. Do '--reg help' for more information. This option may be used multiple times. - --rom FILE Patch in a ROM at address 0 from this file. By default - the 48K ZX Spectrum ROM is used. + --rom FILE Patch in a ROM at address 0 from this file. -s ADDR, --start ADDR Start execution at this address. -S ADDR, --stop ADDR Stop execution at this address. @@ -1756,6 +1755,8 @@ running on a real ZX Spectrum. +---------+-------------------------------------------------------------------+ | Version | Changes | +=========+===================================================================+ +| 9.0 | Added support for 128K snapshots | ++---------+-------------------------------------------------------------------+ | 8.9 | Added the ``--interrupts`` option; reads and writes the T-states | | | counter in Z80 snapshots and reads the T-states counter in SZX | | | snapshots | diff --git a/sphinx/source/conf.py b/sphinx/source/conf.py index 2a3dfdad..74f7809b 100644 --- a/sphinx/source/conf.py +++ b/sphinx/source/conf.py @@ -248,7 +248,7 @@ ('man/tapinfo.py', 'tapinfo.py', 'show the blocks in a TAP or TZX file', _authors, 1), ('man/trace.py', 'trace.py', - 'simulate code execution in a 48K memory snapshot', _authors, 1) + 'simulate code execution in a 48K or 128K memory snapshot', _authors, 1) ] # If true, show URL addresses after external links. diff --git a/sphinx/source/man/trace.py.rst b/sphinx/source/man/trace.py.rst index b580cade..9c91d721 100644 --- a/sphinx/source/man/trace.py.rst +++ b/sphinx/source/man/trace.py.rst @@ -10,10 +10,10 @@ SYNOPSIS DESCRIPTION =========== -``trace.py`` simulates the execution of machine code in a 48K binary (raw -memory) file or a SNA, SZX or Z80 snapshot. If FILE is '.', no snapshot is -loaded, and the RAM is left blank (all zeroes). If 'file.z80' is given, a Z80 -snapshot is written after execution has completed. +``trace.py`` simulates the execution of machine code in a 48K or 128K binary +(raw memory) file or a SNA, SZX or Z80 snapshot. If FILE is '48' or '128', no +snapshot is loaded, and the RAM is left blank (all zeroes). If 'file.z80' is +given, a Z80 snapshot is written after execution has completed. OPTIONS ======= @@ -58,8 +58,7 @@ OPTIONS be used multiple times. --rom `FILE` - Patch in a ROM at address 0 from this file. By default the 48K ZX Spectrum - ROM is used. + Patch in a ROM at address 0 from this file. -s, --start `ADDR` Start execution at this address. `ADDR` must be a decimal number, or a diff --git a/sphinx/source/migration.rst b/sphinx/source/migration.rst index b2115d5b..ad8a1c11 100644 --- a/sphinx/source/migration.rst +++ b/sphinx/source/migration.rst @@ -49,3 +49,7 @@ Z80 snapshot file. In SkoolKit 9, this option has been removed; instead the output filename may be specified after the input filename. For example:: $ trace.py --stop 32768 in.z80 out.z80 + +In SkoolKit 8, if the input filename was '.', a blank 48K snapshot was +substituted. In SkoolKit 9, this no longer works; instead, use '48' for a +blank 48K snapshot, or '128' for a blank 128K snapshot. diff --git a/tests/skoolkittest.py b/tests/skoolkittest.py index 5e2577ec..e90ffdf8 100644 --- a/tests/skoolkittest.py +++ b/tests/skoolkittest.py @@ -287,8 +287,10 @@ def _set_z80_registers(self, header, version, registers): header[32] = pc % 256 header[33] = pc // 256 if version == 3: - t = 69887 - (registers.get('tstates', 0) % 69888) - t1, t2 = t % 17472, t // 17472 + frame_duration = 69888 if header[34] < 4 else 70908 + qframe_duration = frame_duration // 4 + t = frame_duration - 1 - (registers.get('tstates', 0) % frame_duration) + t1, t2 = t % qframe_duration, t // qframe_duration header[55:58] = (t1 % 256, t1 // 256, (2 - t2) % 4) sp = registers.get('SP', 0) header[8] = sp % 256 diff --git a/tests/test_trace.py b/tests/test_trace.py index 02110551..6e8f3fd0 100644 --- a/tests/test_trace.py +++ b/tests/test_trace.py @@ -1,3 +1,4 @@ +import hashlib import os from textwrap import dedent from unittest.mock import patch @@ -5,6 +6,9 @@ from skoolkittest import SkoolKitTestCase from skoolkit import simulator, trace, SkoolKitError, VERSION +ROM0_MD5 = 'b4d2692115a9f2924df92a3cbfb358fb' +ROM1_MD5 = '6e09e5d3c4aef166601669feaaadc01c' + def mock_run(*args): global run_args run_args = args @@ -54,7 +58,7 @@ def test_no_arguments(self): self.assertTrue(error.startswith('usage:')) @patch.object(trace, 'Simulator', TestSimulator) - def test_sna(self): + def test_sna_48k(self): header = [ 1, # I 2, 3, # HL' @@ -86,7 +90,50 @@ def test_sna(self): self.assertEqual(simulator.imode, 2) @patch.object(trace, 'Simulator', TestSimulator) - def test_z80(self): + def test_sna_128k(self): + sna = [0] * 131103 + sna[:27] = ( + 1, # I + 2, 3, # HL' + 4, 5, # DE' + 6, 7, # BC' + 8, 9, # AF' + 10, 11, # HL + 12, 13, # DE + 253, 127, # BC + 16, 17, # IY + 18, 19, # IX + 4, # iff2 (bit 2) + 20, # R + 22, # F + 17, # A (page in bank 1 and ROM 1) + 0, 64, # SP=16384 + 2, # im + 5 # Border + ) + sna[8219:8221] = ( + 0xED, 0x79 # $6000 OUT (C),A + ) + sna[49179:49183] = ( + 0, 96, # PC ($6000) + 0, # Port 0x7ffd + 0 # TR-DOS rom not paged + ) + sna[49183:65567] = [1] * 16384 # Bank 1 + snafile = self.write_bin_file(sna, suffix='.sna') + exp_output = """ + $6000 ED79 OUT (C),A A=11 F=00010110 BC=7FFD DE=0D0C HL=0B0A IX=1312 IY=1110 IR=0116 + A'=09 F'=00001000 BC'=0706 DE'=0504 HL'=0302 SP=4002 + Stopped at $6002 + """ + self._test_trace(f'-vv -S 24578 {snafile}', exp_output) + self.assertEqual(simulator.iff, 1) + self.assertEqual(simulator.imode, 2) + self.assertTrue(all(b == 1 for b in simulator.memory[0xC000:0x10000])) + self.assertEqual(hashlib.md5(bytes(simulator.memory[:0x4000])).hexdigest(), ROM1_MD5) + + @patch.object(trace, 'Simulator', TestSimulator) + def test_z80_48k(self): registers = { 'A': 1, 'F': 2, @@ -130,7 +177,55 @@ def test_z80(self): self.assertEqual(simulator.registers[25], 20004) @patch.object(trace, 'Simulator', TestSimulator) - def test_szx(self): + def test_z80_128k(self): + ram = [0] * 49152 + ram[8192:8194] = (0xED, 0x79) # $6000 OUT (C),A + pages = {p: [p] * 16384 for p in (1, 3, 4, 6, 7)} + registers = { + 'A': 1, + 'F': 2, + 'B': 127, + 'C': 253, + 'D': 5, + 'E': 6, + 'H': 7, + 'L': 8, + 'IXh': 9, + 'IXl': 10, + 'IYh': 11, + 'IYl': 12, + 'SP': 65535, + 'I': 13, + 'R': 14, + '^A': 15, + '^F': 16, + '^B': 17, + '^C': 18, + '^D': 19, + '^E': 20, + '^H': 21, + '^L': 22, + 'PC': 24576, + 'iff1': 1, + 'iff2': 1, + 'im': 2, + 'tstates': 20000 + } + z80file = self.write_z80(ram, machine_id=4, out_7ffd=18, pages=pages, registers=registers)[1] + exp_output = """ + $6000 ED79 OUT (C),A A=01 F=00000010 BC=7FFD DE=0506 HL=0708 IX=0B0C IY=090A IR=0D10 + A'=0F F'=00010000 BC'=1112 DE'=1314 HL'=1516 SP=FFFF + Stopped at $6002 + """ + self._test_trace(f'-vv -S 24578 {z80file}', exp_output) + self.assertEqual(simulator.iff, 1) + self.assertEqual(simulator.imode, 2) + self.assertEqual(simulator.registers[25], 20012) + self.assertTrue(all(b == 1 for b in simulator.memory[0xC000:0x10000])) + self.assertEqual(hashlib.md5(bytes(simulator.memory[:0x4000])).hexdigest(), ROM0_MD5) + + @patch.object(trace, 'Simulator', TestSimulator) + def test_szx_48k(self): registers = ( 1, 2, # AF 3, 4, # BC @@ -152,24 +247,72 @@ def test_szx(self): ) ram = [0] * 49152 szxfile = self.write_szx(ram, registers=registers) - output, error = self.run_trace(f'-vv -S 49153 {szxfile}') - self.assertEqual(error, '') exp_output = """ $C000 00 NOP A=02 F=00000001 BC=0403 DE=0605 HL=0807 IX=1211 IY=1413 IR=1517 A'=0A F'=00001001 BC'=0C0B DE'=0E0D HL'=100F SP=8000 Stopped at $C001 """ - self.assertEqual(dedent(exp_output).strip(), output.rstrip()) + self._test_trace(f'-vv -S 49153 {szxfile}', exp_output) self.assertEqual(simulator.iff, 1) self.assertEqual(simulator.imode, 0) self.assertEqual(simulator.registers[25], 261) - def test_no_snapshot(self): - output, error = self.run_trace(f'-v -S 1 .') + @patch.object(trace, 'Simulator', TestSimulator) + def test_szx_128k(self): + ram = [0] * 49152 + ram[8192:8194] = ( + 0xED, 0x79 # $6000 OUT (C),A + ) + pages = {p: [p] * 16384 for p in (1, 3, 4, 6, 7)} + registers = ( + 1, 19, # AF + 253, 127, # BC + 5, 6, # DE + 7, 8, # HL + 9, 10, # AF' + 11, 12, # BC' + 13, 14, # DE' + 15, 16, # HL' + 17, 18, # IX + 19, 20, # IY + 0, 128, # SP=32768 + 0, 96, # PC=24576 + 21, # I + 22, # R + 1, 1, # iff1, iff2 + 0, # im + 1, 1, 0, 0, # dwCyclesStart=257 + ) + szxfile = self.write_szx(ram, machine_id=2, ch7ffd=4, pages=pages, registers=registers) + exp_output = """ + $6000 ED79 OUT (C),A A=13 F=00000001 BC=7FFD DE=0605 HL=0807 IX=1211 IY=1413 IR=1518 + A'=0A F'=00001001 BC'=0C0B DE'=0E0D HL'=100F SP=8000 + Stopped at $6002 + """ + self._test_trace(f'-vv -S 24578 {szxfile}', exp_output) + self.assertEqual(simulator.iff, 1) + self.assertEqual(simulator.imode, 0) + self.assertEqual(simulator.registers[25], 269) + self.assertTrue(all(b == 3 for b in simulator.memory[0xC000:0x10000])) + self.assertEqual(hashlib.md5(bytes(simulator.memory[:0x4000])).hexdigest(), ROM1_MD5) + + def test_no_snapshot_48k(self): + output, error = self.run_trace(f'-v -S 2 48') self.assertEqual(error, '') exp_output = """ $0000 F3 DI - Stopped at $0001 + $0001 AF XOR A + Stopped at $0002 + """ + self.assertEqual(dedent(exp_output).strip(), output.rstrip()) + + def test_no_snapshot_128k(self): + output, error = self.run_trace(f'-v -S 4 128') + self.assertEqual(error, '') + exp_output = """ + $0000 F3 DI + $0001 012B69 LD BC,$692B + Stopped at $0004 """ self.assertEqual(dedent(exp_output).strip(), output.rstrip()) @@ -811,7 +954,7 @@ def test_option_stats_with_non_zero_start_time(self): def test_option_stop(self): for option in ('-S 57', '--stop 0x0039'): - output, error = self.run_trace(f'-v -s 56 {option} .') + output, error = self.run_trace(f'-v -s 56 {option} 48') self.assertEqual(error, '') exp_output = """ $0038 F5 PUSH AF @@ -945,7 +1088,7 @@ def test_invalid_register_value(self): self.assertEqual(cm.exception.args[0], 'Cannot parse register value: A=x') @patch.object(trace, 'write_z80v3', mock_write_z80v3) - def test_write_z80(self): + def test_write_z80_48k(self): data = [ 0x37, # $8000 SCF 0x9F, # $8001 SBC A,A @@ -963,7 +1106,7 @@ def test_write_z80(self): 0xD9, # $8018 EXX 0x01, 0x27, 0xEF, # $8019 LD BC,$EF27 0x11, 0xF8, 0x13, # $801C LD DE,$13F8 - 0x01, 0x77, 0x7D, # $801F LD BC,$7D77 + 0x21, 0x77, 0x7D, # $801F LD HL,$7D77 0x31, 0xE9, 0xBE, # $8022 LD SP,$BEE9 0xDD, 0x21, 0x72, 0x0D, # $8025 LD IX,$0D72 0xFD, 0x21, 0x2E, 0x27, # $8029 LD IY,$272E @@ -980,9 +1123,9 @@ def test_write_z80(self): exp_reg = ( 'a=1', 'f=16', - 'bc=32119', + 'bc=61223', 'de=5112', - 'hl=0', + 'hl=32119', 'ix=3442', 'iy=10030', 'sp=48873', @@ -995,9 +1138,73 @@ def test_write_z80(self): '^hl=25431', f'pc={stop}' ) - exp_state = ('border=1', 'iff=0', 'im=2', 'tstates=166') + exp_state = ('border=1', '7ffd=0', 'iff=0', 'im=2', 'tstates=166') self.assertEqual(dedent(exp_output).strip(), output.rstrip()) self.assertEqual(z80fname, outfile) self.assertEqual(data, snapshot[start:stop]) self.assertEqual(exp_reg, z80reg) self.assertEqual(exp_state, z80state) + + @patch.object(trace, 'write_z80v3', mock_write_z80v3) + def test_write_z80_128k(self): + sna = [0] * 131103 + start = 32768 + sna[49179:49181] = (start % 256, start // 256) # PC + code = [ + 0x37, # $8000 SCF + 0x9F, # $8001 SBC A,A + 0xF3, # $8002 DI + 0xED, 0x5E, # $8003 IM 2 + 0xED, 0x47, # $8005 LD I,A + 0xED, 0x4F, # $8007 LD R,A + 0x08, # $8009 EX AF,AF' + 0x3E, 0x01, # $800A LD A,$01 + 0xA7, # $800C AND A + 0xD3, 0xFE, # $800D OUT ($FE),A + 0x01, 0xFD, 0x7F, # $800F LD BC,$7FFD + 0xED, 0x79, # $8012 OUT (C),A + 0x11, 0xB8, 0x53, # $8014 LD DE,$53B8 + 0x21, 0x57, 0x63, # $8017 LD HL,$6357 + 0xD9, # $801A EXX + 0x01, 0x27, 0xEF, # $801C LD BC,$EF27 + 0x11, 0xF8, 0x13, # $801E LD DE,$13F8 + 0x21, 0x77, 0x7D, # $8021 LD HL,$7D77 + 0x31, 0xE9, 0xBE, # $8024 LD SP,$BEE9 + 0xDD, 0x21, 0x72, 0x0D, # $8027 LD IX,$0D72 + 0xFD, 0x21, 0x2E, 0x27, # $802B LD IY,$272E + ] + sna[start - 16357:start - 16357 + len(code)] = code + sna[49183:65567] = [1] * 16384 # Bank 1 + snafile = self.write_bin_file(sna, suffix='.sna') + outfile = os.path.join(self.make_directory(), 'out.z80') + stop = start + len(code) + output, error = self.run_trace(f'-S {stop} {snafile} {outfile}') + exp_output = f""" + Stopped at ${stop:04X} + Wrote {outfile} + """ + exp_reg = ( + 'a=1', + 'f=16', + 'bc=61223', + 'de=5112', + 'hl=32119', + 'ix=3442', + 'iy=10030', + 'sp=48873', + 'i=255', + 'r=145', + '^a=255', + '^f=187', + '^bc=32765', + '^de=21432', + '^hl=25431', + f'pc={stop}' + ) + exp_state = ('border=1', '7ffd=1', 'iff=0', 'im=2', 'tstates=178') + self.assertEqual(dedent(exp_output).strip(), output.rstrip()) + self.assertEqual(z80fname, outfile) + self.assertEqual(code, snapshot[start:stop]) + self.assertTrue(all(b == 1 for b in snapshot[0xC000:0x10000])) + self.assertEqual(exp_reg, z80reg) + self.assertEqual(exp_state, z80state)