From 65389218f40bc4b7ba14959828221fe09d1de18d Mon Sep 17 00:00:00 2001
From: Mike Naberezny <mike@naberezny.com>
Date: Thu, 22 Nov 2012 15:23:28 -0800
Subject: [PATCH] Add support for 65C02 opcode 0x7C: JMP (abs,X)

---
 CHANGES.txt                         |  4 +++-
 py65/assembler.py                   |  2 ++
 py65/devices/mpu65c02.py            |  7 +++++++
 py65/disassembler.py                |  7 +++++++
 py65/tests/devices/test_mpu65c02.py | 13 +++++++++++++
 py65/tests/test_assembler.py        | 15 +++++++++++++--
 py65/tests/test_disassembler.py     |  8 +++++++-
 7 files changed, 52 insertions(+), 4 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 31f7f5a..64eda3a 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -11,7 +11,9 @@
   - Fixed a bug where the MPU status display would wrap unexpectedly
     on some terminals.
 
-  - Added support for 65C02 opcode 0x89 (BIT immediate).
+  - Added support for 65C02 opcode 0x89: BIT #.
+
+  - Added support for 65C02 opcode 0x7C: JMP (abs,x).
 
 0.13 (2012-11-15)
 
diff --git a/py65/assembler.py b/py65/assembler.py
index eca5279..58771c6 100644
--- a/py65/assembler.py
+++ b/py65/assembler.py
@@ -19,6 +19,8 @@ class Assembler:
          re.compile(r'^\$00([0-9A-F]{2})$')],
         ['inx',  # "($0012,X)
          re.compile(r'^\(\$00([0-9A-F]{2}),X\)$')],
+        ['iax',  # "($1234,X)
+         re.compile(r'^\(\$([0-9A-F]{2})([0-9A-F]{2}),X\)$')],
         ['iny',  # "($0012),Y"
          re.compile(r'^\(\$00([0-9A-F]{2})\),Y$')],
         ['ind',  # "($1234)"
diff --git a/py65/devices/mpu65c02.py b/py65/devices/mpu65c02.py
index b4bd99f..a77aaa7 100644
--- a/py65/devices/mpu65c02.py
+++ b/py65/devices/mpu65c02.py
@@ -29,6 +29,9 @@ class MPU(mpu6502.MPU):
     def ZeroPageIndirectAddr(self):
         return self.WordAt(255 & (self.ByteAt(self.pc)))
 
+    def IndirectAbsXAddr(self):
+        return (self.WordAt(self.pc) + self.x) & self.addrMask
+
     def AccumulatorAddr(self):
         return self.a
 
@@ -261,6 +264,10 @@ class MPU(mpu6502.MPU):
     def inst_0x3a(self):
         self.opDECR(None)
 
+    @instruction(name="JMP", mode="iax", cycles=6)
+    def inst_0x7c(self):
+        self.pc = self.WordAt(self.IndirectAbsXAddr())
+
     @instruction(name="BRA", mode="rel", cycles=1, extracycles=1)
     def inst_0x80(self):
         self.BranchRelAddr()
diff --git a/py65/disassembler.py b/py65/disassembler.py
index 6a9cc19..902c816 100644
--- a/py65/disassembler.py
+++ b/py65/disassembler.py
@@ -78,6 +78,13 @@ class Disassembler:
             disasm += ' (%s,X)' % address_or_label
             length = 2
 
+        elif addressing == 'iax':
+            address = self._mpu.WordAt(pc + 1)
+            address_or_label = self._address_parser.label_for(
+                address, '$' + self.addrFmt % address)
+            disasm += ' (%s,X)' % address_or_label
+            length = 3
+
         elif addressing == 'rel':
             opv = self._mpu.ByteAt(pc + 1)
             targ = pc + 2
diff --git a/py65/tests/devices/test_mpu65c02.py b/py65/tests/devices/test_mpu65c02.py
index 22cd621..8da97f6 100644
--- a/py65/tests/devices/test_mpu65c02.py
+++ b/py65/tests/devices/test_mpu65c02.py
@@ -463,6 +463,19 @@ class MPUTests(unittest.TestCase, Common6502Tests):
         self.assertEqual(mpu.NEGATIVE, mpu.p & mpu.NEGATIVE)
         self.assertEqual(0, mpu.p & mpu.ZERO)
 
+    # JMP Indirect Absolute X-Indexed
+
+    def test_jmp_iax_jumps_to_address(self):
+        mpu = self._make_mpu()
+        mpu.x = 2
+        # $0000 JMP ($ABCD,X)
+        # $ABCF Vector to $1234
+        self._write(mpu.memory, 0x0000, (0x7C, 0xCD, 0xAB))
+        self._write(mpu.memory, 0xABCF, (0x34, 0x12))
+        mpu.step()
+        self.assertEqual(0x1234, mpu.pc)
+        self.assertEqual(6, mpu.processorCycles)
+
     # LDA Zero Page, Indirect
 
     def test_lda_zp_ind_loads_a_sets_n_flag(self):
diff --git a/py65/tests/test_assembler.py b/py65/tests/test_assembler.py
index 75d113a..a5bfa70 100644
--- a/py65/tests/test_assembler.py
+++ b/py65/tests/test_assembler.py
@@ -470,8 +470,19 @@ class AssemblerTests(unittest.TestCase):
     def dont_test_assembles_7b(self):
         pass
 
-    def dont_test_assembles_7c(self):
-        pass
+    def test_assembles_7c_6502(self):
+        self.assertRaises(SyntaxError,
+                          self.assemble, "JMP ($1234,X)")
+
+    def test_assembles_7c_65c02(self):
+        mpu = MPU65C02()
+        self.assertEqual([0x7c, 0x34, 0x12],
+                         self.assemble('JMP ($1234,X)', 0x0000, mpu))
+
+    def test_assembles_07_65c02(self):
+        mpu = MPU65C02()
+        self.assertEqual([0x07, 0x42],
+                         self.assemble('RMB0 $42', 0x0000, mpu))
 
     def test_assembles_7d(self):
         self.assertEqual([0x7d, 0x00, 0x44],
diff --git a/py65/tests/test_disassembler.py b/py65/tests/test_disassembler.py
index 18bfe65..1841592 100644
--- a/py65/tests/test_disassembler.py
+++ b/py65/tests/test_disassembler.py
@@ -633,11 +633,17 @@ class DisassemblerTests(unittest.TestCase):
         self.assertEqual(1, length)
         self.assertEqual('???', disasm)
 
-    def test_disassembles_7c(self):
+    def test_disassembles_7c_6502(self):
         length, disasm = self.disassemble([0x7c])
         self.assertEqual(1, length)
         self.assertEqual('???', disasm)
 
+    def test_disassembles_7c_65c02(self):
+        mpu = MPU65C02()
+        length, disasm = self.disassemble([0x7c, 0x34, 0x12], 0x0000, mpu)
+        self.assertEqual(3, length)
+        self.assertEqual('JMP ($1234,X)', disasm)
+
     def test_disassembles_7d(self):
         length, disasm = self.disassemble([0x7d, 0x00, 0x44])
         self.assertEqual(3, length)