From 34b35ddd1e99fe221a28f0b1516dabe98c9569c5 Mon Sep 17 00:00:00 2001 From: Richard Dymond Date: Tue, 11 Jun 2024 17:42:08 -0300 Subject: [PATCH] Enable sna2skool.py to disassemble LD (nn),HL and LD HL,(nn) variants --- skoolkit/disassembler.py | 11 +++++-- sphinx/source/commands.rst | 3 ++ sphinx/source/man/sna2skool.py.rst | 17 ++++++----- tests/test_disassembler.py | 48 ++++++++++++++++++++++++++++++ tests/test_snaskool.py | 42 ++++++++++++++++++++++++-- 5 files changed, 110 insertions(+), 11 deletions(-) diff --git a/skoolkit/disassembler.py b/skoolkit/disassembler.py index 8492ca91..f5afde7b 100644 --- a/skoolkit/disassembler.py +++ b/skoolkit/disassembler.py @@ -133,8 +133,15 @@ def __init__(self, snapshot, config): self.defs = 'DEFS ' self.defw = 'DEFW ' self.create_opcodes() - for opcode in [e.strip() for e in config.opcodes.upper().split(',')]: - if opcode == 'ED70': + opcodes = [e.strip() for e in config.opcodes.upper().split(',')] + if 'ALL' in opcodes: + opcodes = 'ED63,ED6B,ED70,ED71,IM,NEG,RETN,XYCB'.split(',') + for opcode in opcodes: + if opcode == 'ED63': + self.after_ED[0x63] = (self.word_arg, 'LD ({}),HL') + elif opcode == 'ED6B': + self.after_ED[0x6B] = (self.word_arg, 'LD HL,({})') + elif opcode == 'ED70': self.after_ED[0x70] = (self.no_arg, 'IN F,(C)') elif opcode == 'ED71': self.after_ED[0x71] = (self.no_arg, 'OUT (C),0') diff --git a/sphinx/source/commands.rst b/sphinx/source/commands.rst index 63586b40..cddbe2e2 100644 --- a/sphinx/source/commands.rst +++ b/sphinx/source/commands.rst @@ -1146,12 +1146,15 @@ configuration parameters are: The ``Opcodes`` list is empty by default, but may contain any of the following values: +* ``ED63`` - LD (nn),HL (4-byte variant) +* ``ED6B`` - LD HL,(nn) (4-byte variant) * ``ED70`` - IN F,(C) * ``ED71`` - OUT (C),0 * ``IM`` - IM 0/1/2 variants (ED followed by 4E/66/6E/76/7E) * ``NEG`` - NEG variants (ED followed by 4C/54/5C/64/6C/74/7C) * ``RETN`` - RETN variants (ED followed by 55/5D/65/6D/75/7D) * ``XYCB`` - undocumented instructions with DDCB or FDCB opcode prefixes +* ``ALL`` - all of the above Note that if your skool file contains any non-standard instructions (such as 'IN F,(C)') or instructions that derive from non-standard opcode sequences diff --git a/sphinx/source/man/sna2skool.py.rst b/sphinx/source/man/sna2skool.py.rst index 4686fcde..6494a022 100644 --- a/sphinx/source/man/sna2skool.py.rst +++ b/sphinx/source/man/sna2skool.py.rst @@ -91,8 +91,8 @@ configuration parameters are: :ListRefs: When to add a comment that lists routine or entry point referrers: never (``0``), if no other comment is defined at the entry point (``1``, the default), or always (``2``). - :Opcodes: Comma-separated list of additional opcode sequences to disassemble - (see below). + :Opcodes: Comma-separated list of values specifying additional opcode + sequences to disassemble (see below). :Ref: Template used to format the comment for a routine with exactly one referrer (default: ``Used by the routine at {ref}.``). :RefFormat: Template used to format referrers in the ``{ref}`` and ``{refs}`` @@ -129,15 +129,18 @@ configuration parameters are: or don't (``0``, the default). The ``Opcodes`` list is empty by default, but may contain any of the following -hexadecimal opcode sequences: +values: | + | ``ED63`` - LD (nn),HL (4-byte variant) + | ``ED6B`` - LD HL,(nn) (4-byte variant) | ``ED70`` - IN F,(C) | ``ED71`` - OUT (C),0 - -Note that assemblers may not recognise these instructions, so if your skool -file contains any of them, care must be taken when using an assembler on the -output of ``skool2asm.py``. + | ``IM`` - IM 0/1/2 variants (ED followed by 4E/66/6E/76/7E) + | ``NEG`` - NEG variants (ED followed by 4C/54/5C/64/6C/74/7C) + | ``RETN`` - RETN variants (ED followed by 55/5D/65/6D/75/7D) + | ``XYCB`` - undocumented instructions with DDCB or FDCB opcode prefixes + | ``ALL`` - all of the above Configuration parameters must appear in a ``[sna2skool]`` section. For example, to make ``sna2skool.py`` generate hexadecimal skool files with a line width of diff --git a/tests/test_disassembler.py b/tests/test_disassembler.py index 1885653c..056cf87c 100644 --- a/tests/test_disassembler.py +++ b/tests/test_disassembler.py @@ -1803,6 +1803,8 @@ } ADDITIONAL_OPCODES_ED = { + 'ED630180': ('ED63', 'LD (32769),HL'), + 'ED6BFEFF': ('ED6B', 'LD HL,(65534)'), 'ED70': ('ED70', 'IN F,(C)'), 'ED71': ('ED71', 'OUT (C),0'), } @@ -2354,6 +2356,16 @@ def test_additional_opcodes_ed(self): self.assertEqual(len(instructions), 1) self.assertEqual(instructions[0][1], op) + def test_additional_opcodes_ed_all(self): + snapshot = [0] * 4 + disassembler = self._get_disassembler(snapshot, opcodes='ALL') + for hex_bytes, (opcodes, op) in ADDITIONAL_OPCODES_ED.items(): + end = len(hex_bytes) // 2 + snapshot[0:end] = [int(hex_bytes[i:i + 2], 16) for i in range(0, len(hex_bytes), 2)] + instructions = disassembler.disassemble(0, end, 'n') + self.assertEqual(len(instructions), 1) + self.assertEqual(instructions[0][1], op) + def test_additional_opcodes_im(self): snapshot = [0xED, 0, 0, 0] disassembler = self._get_disassembler(snapshot, opcodes='IM') @@ -2363,6 +2375,15 @@ def test_additional_opcodes_im(self): self.assertEqual(len(instructions), 1) self.assertEqual(instructions[0][1], f'IM {mode}') + def test_additional_opcodes_im_all(self): + snapshot = [0xED, 0, 0, 0] + disassembler = self._get_disassembler(snapshot, opcodes='ALL') + for opcode, mode in ((0x4E, 0), (0x66, 0), (0x6E, 0), (0x76, 1), (0x7E, 2)): + snapshot[1] = opcode + instructions = disassembler.disassemble(0, 2, 'n') + self.assertEqual(len(instructions), 1) + self.assertEqual(instructions[0][1], f'IM {mode}') + def test_additional_opcodes_neg(self): snapshot = [0xED, 0, 0, 0] disassembler = self._get_disassembler(snapshot, opcodes='NEG') @@ -2372,6 +2393,15 @@ def test_additional_opcodes_neg(self): self.assertEqual(len(instructions), 1) self.assertEqual(instructions[0][1], 'NEG') + def test_additional_opcodes_neg_all(self): + snapshot = [0xED, 0, 0, 0] + disassembler = self._get_disassembler(snapshot, opcodes='ALL') + for opcode in range(0x4C, 0x7D, 8): + snapshot[1] = opcode + instructions = disassembler.disassemble(0, 2, 'n') + self.assertEqual(len(instructions), 1) + self.assertEqual(instructions[0][1], 'NEG') + def test_additional_opcodes_retn(self): snapshot = [0xED, 0, 0, 0] disassembler = self._get_disassembler(snapshot, opcodes='RETN') @@ -2381,6 +2411,15 @@ def test_additional_opcodes_retn(self): self.assertEqual(len(instructions), 1) self.assertEqual(instructions[0][1], 'RETN') + def test_additional_opcodes_retn_all(self): + snapshot = [0xED, 0, 0, 0] + disassembler = self._get_disassembler(snapshot, opcodes='ALL') + for opcode in range(0x55, 0x7E, 8): + snapshot[1] = opcode + instructions = disassembler.disassemble(0, 2, 'n') + self.assertEqual(len(instructions), 1) + self.assertEqual(instructions[0][1], 'RETN') + def test_additional_opcodes_xycb(self): snapshot = [0] * 4 disassembler = self._get_disassembler(snapshot, opcodes='XYCB') @@ -2390,6 +2429,15 @@ def test_additional_opcodes_xycb(self): self.assertEqual(len(instructions), 1) self.assertEqual(instructions[0][1], op) + def test_additional_opcodes_xycb_all(self): + snapshot = [0] * 4 + disassembler = self._get_disassembler(snapshot, opcodes='ALL') + for hex_bytes, op in ADDITIONAL_OPCODES_XYCB.items(): + snapshot[0:4] = [int(hex_bytes[i:i + 2], 16) for i in range(0, 8, 2)] + instructions = disassembler.disassemble(0, 4, 'n') + self.assertEqual(len(instructions), 1) + self.assertEqual(instructions[0][1], op) + def test_boundary_asm(self): for start, data, op in BOUNDARY_ASM: instructions = self._get_instructions(start, data) diff --git a/tests/test_snaskool.py b/tests/test_snaskool.py index 48ab8b5b..3ce82817 100644 --- a/tests/test_snaskool.py +++ b/tests/test_snaskool.py @@ -3415,6 +3415,40 @@ def test_instruction_width_large(self): self._test_write_skool(snapshot, ctl, exp_skool, params={'InstructionWidth': 17}) def test_opcodes(self): + snapshot = [ + 0xED, 0x70, # IN F,(C) + 0xED, 0x71, # OUT (C),0 + 0xDD, 0xCB, 0x00, 0x3F, # SRL (IX+0),A + 0xFD, 0xCB, 0x00, 0x80, # RES 0,(IY+0),B + 0xED, 0x7C, # NEG + 0xED, 0x7D, # RETN + 0xED, 0x66, # IM 0 + 0xED, 0x76, # IM 1 + 0xED, 0x7E, # IM 2 + 0xED, 0x63, 0x01, 0x80, # LD (32769),HL + 0xED, 0x6B, 0x01, 0xC0, # LD HL,(49153) + ] + ctl = """ + c 00000 + i 00030 + """ + exp_skool = """ + ; Routine at 0 + c00000 IN F,(C) ; + 00002 OUT (C),0 ; + 00004 SRL (IX+0),A ; + 00008 RES 0,(IY+0),B ; + 00012 NEG ; + 00014 RETN ; + 00016 IM 0 ; + 00018 IM 1 ; + 00020 IM 2 ; + 00022 LD (32769),HL ; + 00026 LD HL,(49153) ; + """ + self._test_write_skool(snapshot, ctl, exp_skool, params={'Opcodes': 'ED63,ED6B,ED70,ED71,IM,NEG,RETN,XYCB'}) + + def test_opcodes_all(self): snapshot = [ 0xED, 0x70, # IN F,(C) 0xED, 0x71, # OUT (C),0 @@ -3425,10 +3459,12 @@ def test_opcodes(self): 0xED, 0x6E, # IM 0 0xED, 0x76, # IM 1 0xED, 0x7E, # IM 2 + 0xED, 0x63, 0x00, 0x80, # LD (32768),HL + 0xED, 0x6B, 0x00, 0xC0, # LD HL,(49152) ] ctl = """ c 00000 - i 00022 + i 00030 """ exp_skool = """ ; Routine at 0 @@ -3441,8 +3477,10 @@ def test_opcodes(self): 00016 IM 0 ; 00018 IM 1 ; 00020 IM 2 ; + 00022 LD (32768),HL ; + 00026 LD HL,(49152) ; """ - self._test_write_skool(snapshot, ctl, exp_skool, params={'Opcodes': 'ED70,ED71,IM,NEG,RETN,XYCB'}) + self._test_write_skool(snapshot, ctl, exp_skool, params={'Opcodes': 'ALL'}) def test_semicolons_bcgi(self): snapshot = [0, 201, 0, 0, 0, 65, 0, 0, 0]