Apple II emulator in python
# dirty, but it actually works pretty well
# this variable is shared between cpu (producer), speaker and paddle (consumers)
# to provide a way to measure a duration between two events
# the delta between two values is the number of clock cycles that have elapsed.

ticks = 0



# added for fun : change at your will
# the original cpu frequency of the APPLE II is 1023000 Hz

CPU_FREQUENCY = 1023000

clock.py
ArthurFerreira2/reinette-II-plus-dot-py
class Disk() :

    # with the invaluable help from :
    # http://www.hackzapple.com/DISKII/DISKIITECH09.HTM
    # http://www.hackzapple.com/DISKII/DISKIITECH09D.HTM

    def __init__(self) :
        self.data = bytearray(232960)                                           # nibblelized disk image
        self.readOnly  = False                                                  # TODO : update it according to actual nib file attributes
        self.motorOn   = False                                                  # motor status
        self.writeMode = False                                                  # writes to file

        self.phases   = [False, False, False, False]                            # 4 x magnets phases states
        self.phasesB  = [False, False, False, False]                            # phases states Before
        self.phasesBB = [False, False, False, False]                            # phases states Before the Before
        self.pIdx     = 0                                                       # phase index
        self.pIdxB    = 0                                                       # phase index Before

        self.track    = 0                                                       # current track position
        self.halfTrk  = 0                                                       # half track position
        self.nibble   = 0                                                       # ptr to self.data : nibble under head position


    #======================= GETERS AND SETTERS FOR DISK][ RELATED SOFT SWITCHES

    def setWriteMode(self, state) :
        self.writeMode = state

    def getWriteMode(self) :
        return self.writeMode

    def setMotorOn(self, state) :
        self.motorOn = state

    def getMotorOn(self) :
        return self.motorOn

    def setReadOnly(self, state) :
        self.readOnly = state

    def getReadOnly(self) :
        return self.readOnly

    #======================================================= LOAD A FLOPPY IMAGE

    def insertFloppy(self, filename) :
        open(filename, 'rb').readinto(self.data)

    #========================================================== READS AND WRITES

    def read(self) :
        self.nibble = (self.nibble + 1) % 0x1A00                                # spins the disk by one nibble before each read
        return self.data[(self.track * 0x1A00) + self.nibble]

    def write(self, value) :
        self.nibble = (self.nibble + 1) % 0x1A00                                # spins the disk by one nibble before each write
        self.data[(self.track * 0x1A00) + self.nibble] = value

    #====================================================== STEPPER MOTOR (HEAD)

    def stepMotor(self, address) :
        address &= 7
        phase = address >> 1

        self.phasesBB[self.pIdxB] = self.phasesB[self.pIdxB]                    # stepper motor
        self.phasesB[self.pIdx]   = self.phases[self.pIdx]                      # keep track of 2 previous phases
        self.pIdxB = self.pIdx                                                  # keep track of the previous indexes (activated phase)
        self.pIdx  = phase

        if not (address & 1) :                                                  # head not moving (PHASE x OFF)
            self.phases[phase] = False
            return

        if self.phasesBB[(phase + 1) & 3] :                                     # head is moving in by half a track
            self.halfTrk -= 1
            if self.halfTrk < 0 :
                self.halfTrk = 0

        if self.phasesBB[(phase - 1) & 3] :                                     # head is moving out by half a track
            self.halfTrk += 1
            if self.halfTrk > 68 :                                              # tracks are numbered from 0 to 34
                self.halfTrk = 68                                               # and half tracks from 0 to 68 (34*2)

        self.phases[phase] = True                                               # update track#
        self.track = (self.halfTrk + 1) // 2                                    # floor division
        self.nibble = 0                                                         # reset nibble as we change track (optionnal, and not always a good optimisation)

disk.py
ArthurFerreira2/reinette-II-plus-dot-py
from sdl2 import keycode

class Keyctrl() :

    KEYMAP = {   # MODIFIERS : [None, CTRL, SHFT, BOTH]                         # QWERTY US
    keycode.SDLK_a           : [0xC1, 0x81, 0x00, 0x00],
    keycode.SDLK_b           : [0xC2, 0x82, 0x00, 0x00],
    keycode.SDLK_c           : [0xC3, 0x83, 0x00, 0x00],
    keycode.SDLK_d           : [0xC4, 0x84, 0x00, 0x00],
    keycode.SDLK_e           : [0xC5, 0x85, 0x00, 0x00],
    keycode.SDLK_f           : [0xC6, 0x86, 0x00, 0x00],
    keycode.SDLK_g           : [0xC7, 0x87, 0x00, 0x00],
    keycode.SDLK_h           : [0xC8, 0x88, 0x00, 0x00],
    keycode.SDLK_i           : [0xC9, 0x89, 0x00, 0x00],
    keycode.SDLK_j           : [0xCA, 0x8A, 0x00, 0x00],
    keycode.SDLK_k           : [0xCB, 0x8B, 0x00, 0x00],
    keycode.SDLK_l           : [0xCC, 0x8C, 0x00, 0x00],
    keycode.SDLK_m           : [0xCD, 0x8D, 0x00, 0x9D],
    keycode.SDLK_n           : [0xCE, 0x8E, 0x00, 0x9E],
    keycode.SDLK_o           : [0xCF, 0x8F, 0x00, 0x00],
    keycode.SDLK_p           : [0xD0, 0x80, 0x00, 0x90],
    keycode.SDLK_q           : [0xD1, 0x91, 0x00, 0x00],
    keycode.SDLK_r           : [0xD2, 0x92, 0x00, 0x00],
    keycode.SDLK_s           : [0xD3, 0x93, 0x00, 0x00],
    keycode.SDLK_t           : [0xD4, 0x94, 0x00, 0x00],
    keycode.SDLK_u           : [0xD5, 0x95, 0x00, 0x00],
    keycode.SDLK_v           : [0xD6, 0x96, 0x00, 0x00],
    keycode.SDLK_w           : [0xD7, 0x97, 0x00, 0x00],
    keycode.SDLK_x           : [0xD8, 0x98, 0x00, 0x00],
    keycode.SDLK_y           : [0xD9, 0x99, 0x00, 0x00],
    keycode.SDLK_z           : [0xDA, 0x9A, 0x00, 0x00],
    keycode.SDLK_0           : [0xB0, 0x00, 0xA9, 0x00],
    keycode.SDLK_1           : [0xB1, 0x00, 0xA1, 0x00],
    keycode.SDLK_2           : [0xB2, 0x00, 0xC0, 0x00],
    keycode.SDLK_3           : [0xB3, 0x00, 0xA3, 0x00],
    keycode.SDLK_4           : [0xB4, 0x00, 0xA4, 0x00],
    keycode.SDLK_5           : [0xB5, 0x00, 0xA5, 0x00],
    keycode.SDLK_6           : [0xB6, 0x00, 0xDE, 0x00],
    keycode.SDLK_7           : [0xB7, 0x00, 0xA6, 0x00],
    keycode.SDLK_8           : [0xB8, 0x00, 0xAA, 0x00],
    keycode.SDLK_9           : [0xB9, 0x00, 0xA8, 0x00],
    keycode.SDLK_LEFTBRACKET : [0xDB, 0x9B, 0x00, 0x00],
    keycode.SDLK_BACKSLASH   : [0xDC, 0x9C, 0x00, 0x00],
    keycode.SDLK_RIGHTBRACKET: [0xDD, 0x9D, 0x00, 0x00],
    keycode.SDLK_BACKSPACE   : [0x88, 0xDF, 0x00, 0x00],
    keycode.SDLK_LEFT        : [0x88, 0x00, 0x00, 0x00],
    keycode.SDLK_RIGHT       : [0x95, 0x00, 0x00, 0x00],
    keycode.SDLK_SPACE       : [0xA0, 0x00, 0x00, 0x00],
    keycode.SDLK_ESCAPE      : [0x9B, 0x00, 0x00, 0x00],
    keycode.SDLK_RETURN      : [0x8D, 0x00, 0x00, 0x00],
    keycode.SDLK_QUOTE       : [0xA7, 0x00, 0xA2, 0x00],
    keycode.SDLK_EQUALS      : [0xBD, 0x00, 0xAB, 0x00],
    keycode.SDLK_SEMICOLON   : [0xBB, 0x00, 0xBA, 0x00],
    keycode.SDLK_COMMA       : [0xAC, 0x00, 0xBC, 0x00],
    keycode.SDLK_PERIOD      : [0xAE, 0x00, 0xBE, 0x00],
    keycode.SDLK_SLASH       : [0xAF, 0x00, 0xBF, 0x00],
    keycode.SDLK_MINUS       : [0xAD, 0x00, 0xDF, 0x00],
    keycode.SDLK_BACKQUOTE   : [0xE0, 0x00, 0xFE, 0x00]}


    """
        The Apple II didn't had a key buffer.
        I'm using one for two reasons :
        - let the cpu catch all key pressed as input and cpu execution are desynchronized
        - to allow copy paste : I emit all characters as if they were typed on the keyboard
    """

    def __init__(self) :
        self.keyQueue = []
        self.key = 0x00


    def setKey(self, value) :
        self.keyQueue.append(value)


    def setKeyFromKeySym(self, keySym, modifiers) :
        if keySym in Keyctrl.KEYMAP :
            self.keyQueue.append(Keyctrl.KEYMAP[keySym][modifiers])


    def getKey(self) :                                                          # softswitch $C000
        if len(self.keyQueue) :
            if self.keyQueue[0] <= 0x7F :
                self.key = self.keyQueue.pop(0)
            else :
                self.key = self.keyQueue[0]
        return self.key


    def strobe(self) :                                                          # softswitch $C010
        if len(self.keyQueue) :
            self.keyQueue[0] &= 0x7F

keyctrl.py
ArthurFerreira2/reinette-II-plus-dot-py
class Memory() :

    #============================================================ SOME CONSTANTS

    RAMSIZE  = 0xC000                                                           # RAM

    ROMSTART = 0xD000                                                           # ROM
    ROMSIZE  = 0x3000

    LGCSTART = 0xD000                                                           # language card
    LGCSIZE  = 0x3000
    BK2START = 0xD000                                                           # bank 2
    BK2SIZE  = 0x1000

    SL6START = 0xC600                                                           # disk ][ prom in slot 6
    SL6SIZE  = 0x0100

    #============================================================ INITIALIZATION

    def __init__(self, disk, keyctrl, paddle0, paddle1, screen, speaker) :

        self.disk    = disk
        self.keyctrl = keyctrl
        self.paddle0 = paddle0
        self.paddle1 = paddle1
        self.screen  = screen
        self.speaker = speaker

        self.LCWR  = True                                                       # Language Card writable
        self.LCRD  = False                                                      # Language Card readable
        self.LCBK2 = True                                                       # Language Card bank 2 enabled
        self.LCWFF = False                                                      # Language Card pre-write flip flop
        self.DLATCH = 0                                                         # disk ][ one nibble register

        self.ram = bytearray(Memory.RAMSIZE)                                    # 48K of ram in $000-$BFFF
        self.rom = bytearray(Memory.ROMSIZE)                                    # 12K of rom in $D000-$FFFF
        self.lgc = bytearray(Memory.LGCSIZE)                                    # Language Card 12K in $D000-$FFFF
        self.bk2 = bytearray(Memory.BK2SIZE)                                    # bank 2 of Language Card 4K in $D000-$DFFF
        self.sl6 = bytearray(Memory.SL6SIZE)                                    # P5A disk ][ prom in slot 6

        open('assets/appleII+.rom', 'rb').readinto(self.rom)                    # load APPLESOFT ROM
        open('assets/diskII.rom',   'rb').readinto(self.sl6)                    # load disk ][ PROM


    #======================================= MEMORY MAPPED SOFT SWITCHES HANDLER

    def softSwitches(self, address, value = None) :
        """ this function is called from readMem and writeMem

            it complements both functions when address is in page $C0
        """
        if address < 0xC080 :
            if address < 0xC050 :
                if address >= 0xC020 and address <= 0xC03F :                    # TAPEOUT, SPEAKER and space invaders strange behaviour
                    self.speaker.playSound()
                    return 0

                elif address == 0xC000 :                                        # KEYBOARD
                    return self.keyctrl.getKey()
                elif address == 0xC010 :                                        # KBDSTROBE
                    self.keyctrl.strobe()
                    return 0

            elif address < 0xC060 :                                             # VIDEO MODES

                if   address == 0xC050 :                                        # Text off
                    self.screen.setTEXT(False)
                    return 0
                elif address == 0xC051 :                                        # Text on
                    self.screen.setTEXT(True)
                    return 0
                elif address == 0xC052 :                                        # Mixed off
                    self.screen.setMIXED(False)
                    return 0
                elif address == 0xC053 :                                        # Mixed on
                    self.screen.setMIXED(True)
                    return 0
                elif address == 0xC054 :                                        # PAGE2 off
                    self.screen.setPAGE2(False)
                    return 0
                elif address == 0xC055 :                                        # PAGE2 on
                    self.screen.setPAGE2(True)
                    return 0
                elif address == 0xC056 :                                        # HiRes off
                    self.screen.setHIRES(False)
                    return 0
                elif address == 0xC057 :                                        # HiRes on
                    self.screen.setHIRES(True)
                    return 0

            else:                                                               # PADDLES

                if   address == 0xC061 :                                        # Push Button paddle 0
                    return self.paddle0.getButton()
                elif address == 0xC062 :                                        # Push Button paddle 1
                    return self.paddle1.getButton()
                elif address == 0xC064 :                                        # Paddle 0 read
                    return self.paddle0.read()
                elif address == 0xC065 :                                        # Paddle 1 read
                    return self.paddle1.read()
                elif address == 0xC070 :                                        # paddle timer RST
                    self.paddle0.reset()
                    self.paddle1.reset()
                    return 0

        elif address < 0xC0E0 :

            if   address == 0xC080 or address == 0xC084 :                       # LC2RD
                self.LCBK2 = True
                self.LCRD  = True
                self.LCWR  = False
                self.LCWFF = False
                return 0
            elif address == 0xC081 or address == 0xC085 :                       # LC2WR
                self.LCBK2 = True
                self.LCRD  = False
                self.LCWR |= self.LCWFF
                self.LCWFF = value is None
                return 0
            elif address == 0xC082 or address == 0xC086 :                       # ROMONLY2
                self.LCBK2 = True
                self.LCRD  = False
                self.LCWR  = False
                self.LCWFF = False
                return 0
            elif address == 0xC083 or address == 0xC087 :                       # LC2RW
                self.LCBK2 = True
                self.LCRD  = True
                self.LCWR |= self.LCWFF
                self.LCWFF = value is None
                return 0
            elif address == 0xC088 or address == 0xC08C :                       # LC1RD
                self.LCBK2 = False
                self.LCRD  = True
                self.LCWR  = False
                self.LCWFF = False
                return 0
            elif address == 0xC089 or address == 0xC08D :                       # LC1WR
                self.LCBK2 = False
                self.LCRD  = False
                self.LCWR |= self.LCWFF
                self.LCWFF = value is None
                return 0
            elif address == 0xC08A or address == 0xC08E :                       # ROMONLY1
                self.LCBK2 = False
                self.LCRD  = False
                self.LCWR  = False
                self.LCWFF = False
                return 0
            elif address == 0xC08B or address == 0xC08F :                       # LC1RW
                self.LCBK2 = False
                self.LCRD  = True
                self.LCWR |= self.LCWFF
                self.LCWFF = value is None
                return 0

        else :                                                                  # DISK ][ card in slot 6

            if address == 0xC0EC :
                if self.disk.getWriteMode() :                                   # writting dLatch
                    self.disk.write(self.DLATCH)
                    self.screen.setWindowTitle("r/w",
                        f"W[0x{self.disk.track:02X}, 0x{self.disk.nibble:04X}]")
                else :                                                          # reading dLatch
                    self.DLATCH = self.disk.read()
                    self.screen.setWindowTitle("r/w",
                        f"R[0x{self.disk.track:02X}, 0x{self.disk.nibble:04X}]")
                return self.DLATCH

            elif address >= 0xC0E0 and address <= 0xC0E7 :                      # MOVE DRIVE HEAD
                self.disk.stepMotor(address)
                return 0
            elif address == 0xC0ED :                                            # Load Data Latch
                if value :
                    self.DLATCH = value
                return 0
            elif address == 0xC0EE :                                            # latch for READ
                self.disk.setWriteMode(False)
                return 0x80 if self.disk.getReadOnly() else 0x00                # check protection
            elif address == 0xC0EF :                                            # latch for WRITE
                self.disk.setWriteMode(True)
                return 0
            elif address == 0xC0E8 :                                            # MOTOROFF
                self.disk.setMotorOn(False)
                return 0
            elif address == 0xC0E9 :                                            # MOTORON
                self.disk.setMotorOn(True)
                return 0
            elif address == 0xC0EA :                                            # DRIVE0EN (not implemented)
                return 0
            elif address == 0xC0EB :                                            # DRIVE1EN (not implemented)
                return 0

        return 0x00                                                             # catch all

    #============================================================= MEMORY ACCESS


    def readMem(self, address) :                                                # read
        if address < Memory.RAMSIZE :
            return self.ram[address]                                            # RAM
        if address == 0xCFFF :
            self.disk.setMotorOn(False)                                         # reset hardware flag (switches motor off)
            return 0
        if address & 0xFF00 == 0xC000 :
            return self.softSwitches(address)                                   # Soft Switches
        if address & 0xFF00 == Memory.SL6START :
            return self.sl6[address - Memory.SL6START]                          # disk][
        if address >= Memory.ROMSTART :
            if not self.LCRD :
                return self.rom[address - Memory.ROMSTART]                      # ROM
            if self.LCBK2 and address < 0xE000 :
                return self.bk2[address - Memory.BK2START]                      # BK2
            return self.lgc[address - Memory.LGCSTART]                          # LC
        return 0                                                                # catch all


    def writeMem(self, address, value) :                                        # write
        if address < Memory.RAMSIZE :
            self.ram[address] = value                                           # RAM
            return
        if address & 0xFF00 == 0xC000 :
            self.softSwitches(address, value)                                   # Soft Switches
            return
        if self.LCWR and (address >= Memory.ROMSTART) :                         # Language Card
            if self.LCBK2 and address < 0xE000 :
                self.bk2[address - Memory.BK2START] = value                     # BK2
                return
            self.lgc[address - Memory.LGCSTART] = value                         # LC


memory.py
ArthurFerreira2/reinette-II-plus-dot-py
import clock

class Paddle() :

    def __init__(self) :
        self.pushButton = 0                                                     # Push Button
        self.position = 127                                                     # Position ranging from 0 (left) to 255 right, 127 is the middle
        self.countdown = 0.0                                                    # Countdown, since reset
        self.countdownTrigger = 0                                               # the cpu tick at which the countdown was reseted


    def getButton(self) :
        return self.pushButton


    def setButton(self, value) :
        self.pushButton = 0xFF if value else 0x00


    def reset(self) :
        self.countdown = self.position * self.position                          # initialize the countdown - depends on the actual position
        self.countdownTrigger = clock.ticks                                     # records the time this was done


    def read(self) :
        self.countdown -= (clock.ticks - self.countdownTrigger) / 5.6           # decreases the countdown
        if self.countdown <= 0 :                                                # timeout
            return 0x00                                                         # returns 0
        return 0x80                                                             # not timeout, return something with the MSB set


    def update(self, value) :                                                   # called from event loop when a related
        self.position = value                                                   # key has been pressed or released


"""
paste this (F3) to test your changes

NEW
10 A =  PDL (0)
20 B =  PDL (1)
30 C =  PEEK (49249) : REM PB0
40 D =  PEEK (49250) : REM PB1
50 PRINT "X:";A;" Y:";B;
60 IF C > 127 THEN  PRINT " 0";
70 IF D > 127 THEN  PRINT " 1";
80 PRINT
90 GOTO 10
RUN

"""

paddle.py
ArthurFerreira2/reinette-II-plus-dot-py
"""
    puce6502, a MOS 6502 cpu emulator
    Last modified 21st of August 2023 - python version
    Copyright (c) 2023 Arthur Ferreira

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions :

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    THE SOFTWARE.
"""

import clock                                                                    # global variable ticks

class Puce6502() :

    def __init__(self, readMem, writeMem):

        self.readMem = readMem
        self.writeMem = writeMem

        # CONSTANTS
        self.CARRY = 0x01
        self.ZERO  = 0x02
        self.INTR  = 0x04
        self.DECIM = 0x08
        self.BREAK = 0x10
        self.UNDEF = 0x20
        self.OFLOW = 0x40
        self.SIGN  = 0x80

        self.C = 0                                                              # CARRY
        self.Z = 0                                                              # ZERO
        self.I = 0                                                              # INTR
        self.D = 0                                                              # DECIM
        self.B = 0                                                              # BREAK
        self.U = 0                                                              # UNDEF
        self.V = 0                                                              # OFLOW
        self.S = 0                                                              # SIGN

        self.A  = 0                                                             # Accumulator,
        self.X  = 0                                                             # X index
        self.Y  = 0                                                             # Y index
        self.PC = 0                                                             # Program Counter
        self.SP = 0                                                             # Stack Pointer

        self.rst()                                                              # perform a reset


    def setPC(self, address) :
        self.PC = address


    def getPC(self) :
        return self.PC


    def getP(self):
        return self.C*self.CARRY + self.Z*self.ZERO + self.I*self.INTR + self.D*self.DECIM + self.B*self.BREAK + self.U*self.UNDEF + self.V*self.OFLOW + self.S*self.SIGN


    def setP(self, value):
        self.C = 1 if value & self.CARRY else 0
        self.Z = 1 if value & self.ZERO  else 0
        self.I = 1 if value & self.INTR  else 0
        self.D = 1 if value & self.DECIM else 0
        self.B = 1 if value & self.BREAK else 0
        self.U = 1 if value & self.UNDEF else 0
        self.V = 1 if value & self.OFLOW else 0
        self.S = 1 if value & self.SIGN  else 0


    def rst(self) :                                                             # Reset
        self.PC = self.readMem(0xFFFC) | (self.readMem(0xFFFD) << 8)
        self.SP = 0xFD
        self.I = 1
        self.U = 1
        clock.ticks += 7


    def irq(self) :                                                             # Interupt Request
        if not self.I :
            return
        self.PC = (self.PC + 1) & 0xFFFF
        self.writeMem(0x100 + self.SP, (self.PC >> 8) & 0xFF)
        self.SP = (self.SP - 1) & 0xFF
        self.writeMem(0x100 + self.SP, self.PC & 0xFF)
        self.SP = (self.SP - 1) & 0xFF
        self.writeMem(0x100 + self.SP, self.getP() & ~self.BREAK)
        self.SP = (self.SP - 1) & 0xFF
        self.PC = self.readMem(0xFFFE) | (self.readMem(0xFFFF) << 8)
        clock.ticks += 7


    def nmi(self) :                                                             # Non Maskable Interupt
        self.I = 1
        self.PC = (self.PC + 1) & 0xFFFF
        self.writeMem(0x100 + self.SP, (self.PC >> 8) & 0xFF)
        self.SP = (self.SP - 1) & 0xFF
        self.writeMem(0x100 + self.SP, self.PC & 0xFF)
        self.SP = (self.SP - 1) & 0xFF
        self.writeMem(0x100 + self.SP, self.getP() & ~self.BREAK)
        self.SP = (self.SP - 1) & 0xFF
        self.PC = self.readMem(0xFFFA) | (self.readMem(0xFFFB) << 8)
        clock.ticks += 7


    """  Addressing modes abbreviations used in the comments down below :

    IMP : Implied or Implicit              : DEX, RTS, CLC : 25 instructions
    ACC : Accumulator                      : ASL, ROR, DEC :  4 instructions
    IMM : Immediate                        : LDA #$A5      : 11 instructions
    ZPG : Zero Page                        : LDA $81       : 21 instructions
    ZPX : Zero Page Indexed with X         : LDA $55,X     : 16 instructions
    ZPY : Zero Page Indexed with Y         : LDX $55,Y     :  2 instructions
    REL : Relative                         : BEQ LABEL12   :  8 instructions
    ABS : Absolute                         : LDA $2000     : 23 instructions
    ABX : Absolute Indexed with X          : LDA $2000,X   : 15 instructions
    ABY : Absolute Indexed with Y          : LDA $2000,Y   :  9 instructions
    IND : Indirect                         : JMP ($1020)   :  1 instructions
    IZX : ZP (Pre)Indexed Indirect with X  : LDA ($55,X)   :  8 instructions
    IZY : ZP Indirect (Post)Indexed with Y : LDA ($55),Y   :  8 instructions

    The variables value8, value16 and address are used to temporarely
    hold 8bits, 16bits data and memory addresses (which are also 16bits wide)


    Optimizations :
    https://www.reddit.com/r/EmuDev/comments/16f021n/comment/k02qnl8/?utm_source=share&utm_medium=web2x&context=3

    using a 'binary search tree' to speed up instruction lookup

    00  10  20  30  40  50  60  70  80  90  A0  B0  C0  D0  E0  FF
                                  1
                  2                               2
          3               3               3               3
      4       4       4       4       4       4       4       4
    5   5   5   5   5   5   5   5   5   5   5   5   5   5   5   5

    Thank you 'phire' for the idea :-)

    """

    def run(self, cycleCount) :

        cycleCount += clock.ticks                                               # cycleCount becomes the target ticks value
        while (clock.ticks < cycleCount) :

            inst = self.readMem(self.PC)                                        # fetch instruction
            self.PC = (self.PC + 1) & 0xFFFF                                    # increment Program Counter

            if inst < 0x80 :
                if inst < 0x40 :
                    if inst < 0x20 :
                        if inst < 0x10 :
                            if inst < 0x08 :
                            # 0x00 to 0x07

                                if inst ==  0x00 :                              # IMP BRK
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.writeMem(0x100 + self.SP, ((self.PC) >> 8) & 0xFF)
                                    self.SP = (self.SP - 1) & 0xFF
                                    self.writeMem(0x100 + self.SP, self.PC & 0xFF)
                                    self.SP = (self.SP - 1) & 0xFF
                                    self.writeMem(0x100 + self.SP, self.getP() | self.BREAK)
                                    self.SP = (self.SP - 1) & 0xFF
                                    self.I = 1
                                    self.D = 0
                                    self.PC = self.readMem(0xFFFE) | (self.readMem(0xFFFF) << 8)
                                    clock.ticks += 7
                                    continue

                                elif inst ==  0x01 :                            # IZX ORA
                                    value8 = (self.readMem(self.PC) + self.X) & 0xFF
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = self.readMem(value8)
                                    value8 = (value8 + 1) & 0xFF
                                    address |= self.readMem(value8) << 8
                                    self.A |= self.readMem(address)
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 6
                                    continue

                                elif inst ==  0x05 :                            # ZPG ORA
                                    self.A |= self.readMem(self.readMem(self.PC))
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 3
                                    continue

                                elif inst ==  0x06 :                            # ZPG ASL
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value16 = self.readMem(address) << 1
                                    self.C = value16 > 0xFF
                                    value16 &= 0xFF
                                    self.writeMem(address, value16)
                                    self.Z = value16 == 0
                                    self.S = value16 > 0x7F
                                    clock.ticks += 5
                                    continue

                            else :
                            # 0x08 to 0x0F

                                if inst ==  0x08 :                              # IMP PHP
                                    self.writeMem(0x100 + self.SP, self.getP() | self.BREAK)
                                    self.SP = (self.SP - 1) & 0xFF
                                    clock.ticks += 3
                                    continue

                                elif inst ==  0x09 :                            # IMM ORA
                                    self.A |= self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0x0A :                            # ACC ASL
                                    value16 = self.A << 1
                                    self.C = value16 > 0xFF
                                    self.A = value16 & 0xFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0x0D :                            # ABS ORA
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.A |= self.readMem(address)
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0x0E :                            # ABS ASL
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value16 = self.readMem(address) << 1
                                    self.C = value16 > 0xFF
                                    value16 &= 0xFF
                                    self.writeMem(address, value16)
                                    self.Z = value16 == 0
                                    self.S = value16 > 0x7F
                                    clock.ticks += 6
                                    continue

                        else :
                            if inst < 0x18 :
                            # 0x10 to 0x17

                                if inst ==  0x10 :                              # REL BPL
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    if not self.S :                             # jump taken
                                        clock.ticks += 1
                                        if address & self.SIGN :
                                            address |= 0xFF00
                                        if ((self.PC & 0xFF) + address) & 0xFF00 :
                                            clock.ticks += 1
                                        self.PC = (self.PC + address) & 0xFFFF
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0x11 :                            # IZY ORA
                                    value8 = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = self.readMem(value8)
                                    value8 = (value8 + 1) & 0xFF
                                    address |= self.readMem(value8) << 8
                                    if ((address & 0xFF) + self.Y) & 0xFF00 :
                                        clock.ticks += 6
                                    else :
                                        clock.ticks += 5
                                    address = (address + self.Y) & 0xFFFF
                                    self.A |= self.readMem(address)
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    continue

                                elif inst ==  0x15 :                            # ZPX ORA
                                    self.A |= self.readMem((self.readMem(self.PC) + self.X) & 0xFF)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0x16 :                            # ZPX ASL
                                    address = (self.readMem(self.PC) + self.X) & 0xFF
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value16 = self.readMem(address) << 1
                                    self.writeMem(address, value16 & 0xFF)
                                    self.C = value16 > 0xFF
                                    self.Z = value16 == 0
                                    self.S = (value16 & 0xFF) > 0x7F
                                    clock.ticks += 6
                                    continue

                            else :
                            # 0x18 to 0x1F

                                if inst ==  0x18 :                              # IMP CLC
                                    self.C = 0
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0x1D :                            # ABX ORA
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    if (address + self.X) & 0xFF00 :
                                        clock.ticks += 5
                                    else :
                                        clock.ticks += 4
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = (address + self.X) & 0xFFFF
                                    self.A |= self.readMem(address)
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    continue

                                elif inst ==  0x19 :                            # ABY ORA
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    if (address + self.Y) & 0xFF00 :
                                        clock.ticks += 5
                                    else:
                                        clock.ticks += 4
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = (address + self.Y) & 0xFFFF
                                    self.A |= self.readMem(address)
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    continue

                                elif inst ==  0x1E :                            # ABX ASL
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = (address + self.X) & 0xFFFF
                                    value16 = self.readMem(address) << 1
                                    self.C = value16 > 0xFF
                                    value16 &= 0xFF
                                    self.writeMem(address, value16)
                                    self.Z = value16 == 0
                                    self.S = value16 > 0x7F
                                    clock.ticks += 7
                                    continue

                    else :
                        if inst < 0x30 :
                            if inst < 0x28 :
                            # 0x20 to 0x27

                                if inst ==  0x24 :                              # ZPG BIT
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 = self.readMem(address)
                                    self.Z = (self.A & value8) == 0
                                    self.setP((self.getP() & 0x3F) | (value8 & 0xC0))
                                    clock.ticks += 3
                                    continue

                                elif inst ==  0x20 :                            # ABS JSR
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.writeMem(0x100 + self.SP, (self.PC >> 8) & 0xFF)
                                    self.SP = (self.SP - 1) & 0xFF
                                    self.writeMem(0x100 + self.SP, self.PC & 0xFF)
                                    self.SP = (self.SP - 1) & 0xFF
                                    self.PC = address
                                    clock.ticks += 6
                                    continue

                                elif inst ==  0x21 :                            # IZX AND
                                    value8 = (self.readMem(self.PC) + self.X) & 0xFF
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = self.readMem(value8)
                                    value8 = (value8 + 1) & 0xFF
                                    address |= self.readMem(value8) << 8
                                    self.A &= self.readMem(address)
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 6
                                    continue


                                elif inst ==  0x25 :                            # ZPG AND
                                    self.A &= self.readMem(self.readMem(self.PC))
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 3
                                    continue

                                elif inst ==  0x26 :                            # ZPG ROL
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value16 = (self.readMem(address) << 1) | self.C
                                    self.C = (value16 & 0x100) != 0
                                    value16 &= 0xFF
                                    self.writeMem(address, value16)
                                    self.Z = value16 == 0
                                    self.S = value16 > 0x7F
                                    clock.ticks += 5
                                    continue

                            else :
                            # 0x28 to 0x2F

                                if inst ==  0x28 :                              # IMP PLP
                                    self.SP = (self.SP + 1) & 0xFF
                                    self.setP(self.readMem(0x100 + self.SP) | self.UNDEF)
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0x29 :                            # IMM AND
                                    self.A &= self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0x2A :                            # ACC ROL
                                    value16 = (self.A << 1) | self.C
                                    self.C = (value16 & 0x100) != 0
                                    self.A = value16 & 0xFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0x2C :                            # ABS BIT
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 = self.readMem(address)
                                    self.Z = (self.A & value8) == 0
                                    self.setP((self.getP() & 0x3F) | (value8 & 0xC0))
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0x2D :                            # ABS AND
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.A &= self.readMem(address)
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0x2E :                            # ABS ROL
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value16 = (self.readMem(address) << 1) | self.C
                                    self.C = (value16 & 0x100) != 0
                                    value16 &= 0xFF
                                    self.writeMem(address, value16)
                                    self.Z = value16 == 0
                                    self.S = value16 > 0x7F
                                    clock.ticks += 6
                                    continue


                        else :
                            if inst < 0x38 :
                            # 0x30 to 0x37

                                if inst ==  0x30 :                              # REL BMI
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    if self.S :
                                        clock.ticks += 1
                                        if address & self.SIGN :
                                            address |= 0xFF00
                                        if ((self.PC & 0xFF) + address) & 0xFF00 :
                                            clock.ticks += 1
                                        self.PC = (self.PC + address) & 0xFFFF
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0x31 :                            # IZY AND
                                    value8 = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = self.readMem(value8)
                                    value8 = (value8 + 1) & 0xFF
                                    address |= self.readMem(value8) << 8
                                    if ((address & 0xFF) + self.Y) & 0xFF00 :
                                        clock.ticks += 6
                                    else :
                                        clock.ticks += 5
                                    address = (address + self.Y) & 0xFFFF
                                    self.A &= self.readMem(address)
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    continue

                                elif inst ==  0x35 :                            # ZPX AND
                                    address = (self.readMem(self.PC) + self.X) & 0xFF
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.A &= self.readMem(address)
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0x36 :                            # ZPX ROL
                                    address = (self.readMem(self.PC) + self.X) & 0xFF
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value16 = (self.readMem(address) << 1) | self.C
                                    self.C = value16 > 0xFF
                                    value16 &= 0xFF
                                    self.writeMem(address, value16)
                                    self.Z = value16 == 0
                                    self.S = value16 > 0x7F
                                    clock.ticks += 6
                                    continue

                            else :
                            # 0x38 to 0x3F

                                if inst ==  0x38 :                              # IMP SEC
                                    self.C = 1
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0x39 :                            # ABY AND
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    if (address + self.Y) & 0xFF00 :
                                        clock.ticks += 5
                                    else :
                                        clock.ticks += 4
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = (address + self.Y) & 0xFFFF
                                    self.A &= self.readMem(address)
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    continue

                                elif inst ==  0x3D :                            # ABX AND
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    if (address + self.X) & 0xFF00 :
                                        clock.ticks += 5
                                    else :
                                        clock.ticks += 4
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = (address + self.X) & 0xFFFF
                                    self.A &= self.readMem(address)
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    continue

                                elif inst ==  0x3E :                            # ABX ROL
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = (address + self.X) & 0xFFFF
                                    value16 = (self.readMem(address) << 1) | self.C
                                    self.C = value16 > 0xFF
                                    value16 &= 0xFF
                                    self.writeMem(address, value16)
                                    self.Z = value16 == 0
                                    self.S = value16 > 0x7F
                                    clock.ticks += 7
                                    continue

                else :
                    if inst < 0x60 :
                        if inst < 0x50 :
                            if inst < 0x48 :
                            # 0x40 to  0x47

                                if inst ==  0x40 :                              # IMP RTI
                                    self.SP = (self.SP + 1) & 0xFF
                                    self.setP(self.readMem(0x100 + self.SP))
                                    self.SP = (self.SP + 1) & 0xFF
                                    self.PC = self.readMem(0x100 + self.SP)
                                    self.SP = (self.SP + 1) & 0xFF
                                    self.PC |= self.readMem(0x100 + self.SP) << 8
                                    clock.ticks += 6
                                    continue

                                elif inst ==  0x41 :                            # IZX EOR
                                    value8 = (self.readMem(self.PC) + self.X) & 0xFF
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = self.readMem(value8)
                                    value8 = (value8 + 1) & 0xFF
                                    address |= self.readMem(value8) << 8
                                    self.A ^= self.readMem(address)
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 6
                                    continue

                                elif inst ==  0x45 :                            # ZPG EOR
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.A ^= self.readMem(address)
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 3
                                    continue

                                elif inst ==  0x46 :                            # ZPG LSR
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 = self.readMem(address)
                                    self.C = (value8 & 1) != 0
                                    value8 = value8 >> 1
                                    self.writeMem(address, value8)
                                    self.Z = value8 == 0
                                    self.S = value8 > 0x7F
                                    clock.ticks += 5
                                    continue
                            else :
                                # 0x48 to 0x4F

                                if inst ==  0x48 :                              # IMP PHA
                                    self.writeMem(0x100 + self.SP, self.A)
                                    self.SP = (self.SP - 1) & 0xFF
                                    clock.ticks += 3
                                    continue

                                elif inst ==  0x49 :                            # IMM EOR
                                    self.A ^= self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0x4A :                            # ACC LSR
                                    self.C = (self.A & 1) != 0
                                    self.A = self.A >> 1
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0x4C :                            # ABS JMP
                                    self.PC = self.readMem(self.PC) | (self.readMem(self.PC + 1) << 8)
                                    clock.ticks += 3
                                    continue

                                elif inst ==  0x4D :                            # ABS EOR
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.A ^= self.readMem(address)
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0x4E :                            # ABS LSR
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 = self.readMem(address)
                                    self.C = (value8 & 1) != 0
                                    value8 = value8 >> 1
                                    self.writeMem(address, value8)
                                    self.Z = value8 == 0
                                    self.S = value8 > 0x7F
                                    clock.ticks += 6
                                    continue

                        else :
                            if inst < 0x58 :
                            # 0x50 to 0x5F

                                if inst ==  0x50 :                              # REL BVC
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    if not self.V :
                                        clock.ticks += 1
                                        if address & self.SIGN:
                                            address |= 0xFF00
                                        if ((self.PC & 0xFF) + address) & 0xFF00 :
                                            clock.ticks += 1
                                        self.PC = (self.PC + address) & 0xFFFF
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0x51 :                            # IZY EOR
                                    value8 = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = self.readMem(value8)
                                    value8 = (value8 + 1) & 0xFF
                                    address |= self.readMem(value8) << 8
                                    if ((address & 0xFF) + self.Y) & 0xFF00 :
                                        clock.ticks += 6
                                    else :
                                        clock.ticks += 5
                                    self.A ^= self.readMem((address + self.Y) & 0xFFFF)
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    continue

                                elif inst ==  0x55 :                            # ZPX EOR
                                    address = (self.readMem(self.PC) + self.X) & 0xFF
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.A ^= self.readMem(address)
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0x56 :                            # ZPX LSR
                                    address = (self.readMem(self.PC) + self.X) & 0xFF
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 = self.readMem(address)
                                    self.C = (value8 & 1) != 0
                                    value8 = value8 >> 1
                                    self.writeMem(address, value8)
                                    self.Z = value8 == 0
                                    self.S = value8 > 0x7F
                                    clock.ticks += 6
                                    continue

                                elif inst ==  0x58 :                            # IMP CLI
                                    self.I = 0
                                    clock.ticks += 2
                                    continue

                            else :
                                # 0x58 to 0x5F

                                if inst ==  0x59 :                              # ABY EOR
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    if (address + self.Y) & 0xFF00 :
                                        clock.ticks += 5
                                    else :
                                        clock.ticks += 4
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = (address + self.Y) & 0xFFFF
                                    self.A ^= self.readMem(address)
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    continue

                                elif inst ==  0x5D :                            # ABX EOR
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    if (address + self.X) & 0xFF00 :
                                        clock.ticks += 5
                                    else :
                                        clock.ticks += 4
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = (address + self.X) & 0xFFFF
                                    self.A ^= self.readMem(address)
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    continue

                                elif inst ==  0x5E :                            # ABX LSR
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = (address + self.X) & 0xFFFF
                                    value8 = self.readMem(address)
                                    self.C = (value8 & 1) != 0
                                    value8 = value8 >> 1
                                    self.writeMem(address, value8)
                                    self.Z = value8 == 0
                                    self.S = value8 > 0x7F
                                    clock.ticks += 7
                                    continue

                    else :
                        if inst < 0x70 :
                            if inst < 0x68 :
                            # 0x60 to 0x67

                                if inst ==  0x60 :                              # IMP RTS
                                    self.SP = (self.SP + 1) & 0xFF
                                    self.PC = self.readMem(0x100 + self.SP)
                                    self.SP = (self.SP + 1) & 0xFF
                                    self.PC |= self.readMem(0x100 + self.SP) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    clock.ticks += 6
                                    continue

                                elif inst ==  0x61 :                            # IZX ADC
                                    value8 = (self.readMem(self.PC) + self.X) & 0xFF
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = self.readMem(value8)
                                    value8 = (value8 + 1) & 0xFF
                                    address |= self.readMem(value8) << 8
                                    value8 = self.readMem(address)
                                    value16 = (self.A + value8 + self.C) & 0xFFFF
                                    self.V = ((value16 ^ self.A) & (value16 ^ value8) & 0x0080) != 0
                                    if self.D :
                                        value16 += ((((value16 + 0x66) ^ self.A ^ value8) >> 3) & 0x22) * 3
                                    self.C = value16 > 0xFF
                                    self.A = value16 & 0xFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 6
                                    continue

                                elif inst ==  0x65 :                            # ZPG ADC
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 = self.readMem(address)
                                    value16 = (self.A + value8 + self.C) & 0xFFFF
                                    self.V = ((value16 ^ self.A) & (value16 ^ value8) & 0x0080) != 0
                                    if self.D :
                                        value16 += ((((value16 + 0x66) ^ self.A ^ value8) >> 3) & 0x22) * 3
                                    self.C = value16 > 0xFF
                                    self.A = value16 & 0xFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 3
                                    continue

                                elif inst ==  0x66 :                            # ZPG ROR
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 = self.readMem(address)
                                    value16 = (value8 >> 1) | (self.C << 7)
                                    self.C = (value8 & 0x1) != 0
                                    value16 &= 0xFF
                                    self.writeMem(address, value16)
                                    self.Z = value16 == 0
                                    self.S = value16 > 0x7F
                                    clock.ticks += 5
                                    continue
                            else :
                                # 0x68 to 0x6F

                                if inst ==  0x68 :                              # IMP PLA
                                    self.SP = (self.SP + 1) & 0xFF
                                    self.A = self.readMem(0x100 + self.SP)
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0x69 :                            # IMM ADC
                                    value8 = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value16 = (self.A + value8 + self.C) & 0xFFFF
                                    self.V = ((value16 ^ self.A) & (value16 ^ value8) & 0x0080) != 0
                                    if self.D :
                                        value16 += ((((value16 + 0x66) ^ self.A ^ value8) >> 3) & 0x22) * 3
                                    self.C = value16 > 0xFF
                                    self.A = value16 & 0xFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0x6A :                            # ACC ROR
                                    value16 = (self.A >> 1) | (self.C << 7)
                                    self.C = (self.A & 0x1) != 0
                                    self.A = value16 & 0xFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0x6C :                            # IND JMP
                                    address = self.readMem(self.PC) | self.readMem(self.PC + 1) << 8
                                    self.PC = self.readMem(address) | (self.readMem(address + 1) << 8)
                                    clock.ticks += 5
                                    continue

                                elif inst ==  0x6D :                            # ABS ADC
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 = self.readMem(address)
                                    value16 = (self.A + value8 + self.C) & 0xFFFF
                                    self.V = ((value16 ^ self.A) & (value16 ^ value8) & 0x0080) != 0
                                    if self.D :
                                        value16 += ((((value16 + 0x66) ^ self.A ^ value8) >> 3) & 0x22) * 3
                                    self.C = value16 > 0xFF
                                    self.A = value16 & 0xFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0x6E :                            # ABS ROR
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 = self.readMem(address)
                                    value16 = (value8 >> 1) | (self.C << 7)
                                    self.C = (value8 & 0x1) != 0
                                    value16 = value16 & 0xFF
                                    self.writeMem(address, value16)
                                    self.Z = value16 == 0
                                    self.S = value16 > 0x7F
                                    clock.ticks += 6
                                    continue

                        else :
                            if inst < 0x78 :
                            # 0x70 to 0x77

                                if inst ==  0x70 :                              # REL BVS
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    if self.V :
                                        clock.ticks += 1
                                        if ((self.PC & 0xFF) + address) & 0xFF00 :
                                            clock.ticks += 1
                                        if address & self.SIGN :
                                            address |= 0xFF00
                                        self.PC = (self.PC + address) & 0xFFFF
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0x71 :                            # IZY ADC
                                    value8 = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = self.readMem(value8)
                                    if (address + self.Y) & 0xFF00 :
                                        clock.ticks += 1
                                    value8 = (value8 + 1) & 0xFF
                                    address |= self.readMem(value8) << 8
                                    address = (address + self.Y) & 0xFFFF
                                    value8 = self.readMem(address)
                                    value16 = (self.A + value8 + self.C) & 0xFFFF
                                    self.V = ((value16 ^ self.A) & (value16 ^ value8) & 0x0080) != 0
                                    if self.D :
                                        value16 += ((((value16 + 0x66) ^ self.A ^ value8) >> 3) & 0x22) * 3
                                    self.C = value16 > 0xFF
                                    self.A = value16 & 0xFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 5
                                    continue

                                elif inst ==  0x75 :                            # ZPX ADC
                                    address = (self.readMem(self.PC) + self.X) & 0xFF
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 = self.readMem(address)
                                    value16 = (self.A + value8 + self.C) & 0xFFFF
                                    self.V = ((value16 ^ self.A) & (value16 ^ value8) & 0x0080) != 0
                                    if self.D :
                                        value16 += ((((value16 + 0x66) ^ self.A ^ value8) >> 3) & 0x22) * 3
                                    self.C = value16 > 0xFF
                                    self.A = value16 & 0xFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0x76 :                            # ZPX ROR
                                    address = (self.readMem(self.PC) + self.X) & 0xFF
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 = self.readMem(address)
                                    value16 = (value8 >> 1) | (self.C << 7)
                                    self.C = (value8 & 0x1) != 0
                                    value16 = value16 & 0xFF
                                    self.writeMem(address, value16)
                                    self.Z = value16 == 0
                                    self.S = value16 > 0x7F
                                    clock.ticks += 6
                                    continue

                            else :
                                # 0x78 to 0x7F

                                if inst ==  0x78 :                              # IMP SEI
                                    self.I = 1
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0x79 :                            # ABY ADC
                                    if (self.readMem(self.PC) + self.Y) & 0xFF00 :
                                        clock.ticks += 1
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = (address + self.Y) & 0xFFFF
                                    value8 = self.readMem(address)
                                    value16 = (self.A + value8 + self.C) & 0xFFFF
                                    self.V = ((value16 ^ self.A) & (value16 ^ value8) & 0x0080) != 0
                                    if self.D:
                                        value16 += ((((value16 + 0x66) ^ self.A ^ value8) >> 3) & 0x22) * 3
                                    self.C = value16 > 0xFF
                                    self.A = value16 & 0xFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0x7D :                            # ABX ADC
                                    if (self.readMem(self.PC) + self.X) & 0xFF00 :
                                        clock.ticks += 1
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = (address + self.X) & 0xFFFF
                                    value8 = self.readMem(address)
                                    value16 = (self.A + value8 + self.C) & 0xFFFF
                                    self.V = ((value16 ^ self.A) & (value16 ^ value8) & 0x0080) != 0
                                    if self.D :
                                        value16 += ((((value16 + 0x66) ^ self.A ^ value8) >> 3) & 0x22) * 3
                                    self.C = value16 > 0xFF
                                    self.A = value16 & 0xFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0x7E :                            # ABX ROR
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = (address + self.X) & 0xFFFF
                                    value8 = self.readMem(address)
                                    value16 = (value8 >> 1) | (self.C << 7)
                                    self.C = (value8 & 0x1) != 0
                                    value16 = value16 & 0xFF
                                    self.writeMem(address, value16)
                                    self.Z = value16 == 0
                                    self.S = value16 > 0x7F
                                    clock.ticks += 7
                                    continue

            else :
                if inst < 0xC0 :
                    if inst < 0xA0 :
                        if inst < 0x90 :
                            if inst < 0x88 :
                            # 0x80 to 0x87

                                if inst ==  0x85 :                              # ZPG STA
                                    self.writeMem(self.readMem(self.PC), self.A)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    clock.ticks += 3
                                    continue

                                elif inst ==  0x81 :                            # IZX STA
                                    value8 = (self.readMem(self.PC) + self.X) & 0xFF
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = self.readMem(value8)
                                    value8 = (value8 + 1) & 0xFF
                                    address |= self.readMem(value8) << 8
                                    self.writeMem(address, self.A)
                                    clock.ticks += 6
                                    continue

                                elif inst ==  0x84 :                            # ZPG STY
                                    self.writeMem(self.readMem(self.PC), self.Y)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    clock.ticks += 3
                                    continue


                                elif inst ==  0x86 :                            # ZPG STX
                                    self.writeMem(self.readMem(self.PC), self.X)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    clock.ticks += 3
                                    continue

                            else :
                                # 0x88 to 0x8F

                                if inst ==  0x88 :                              # IMP DEY
                                    self.Y = (self.Y - 1) & 0xFF
                                    self.Z = (self.Y & 0xFF) == 0
                                    self.S = (self.Y & self.SIGN) != 0
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0x8D :                            # ABS STA
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.writeMem(address, self.A)
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0x8A :                            # IMP TXA
                                    self.A = self.X
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0x8C :                            # ABS STY
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.writeMem(address, self.Y)
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0x8E :                            # ABS STX
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.writeMem(address, self.X)
                                    clock.ticks += 4
                                    continue

                        else :
                            if inst < 0x98 :
                            # 0x90 to 0x97

                                if inst ==  0x91 :                              # IZY STA
                                    value8 = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = self.readMem(value8)
                                    value8 = (value8 + 1) & 0xFF
                                    address |= self.readMem(value8) << 8
                                    address = (address + self.Y) & 0xFFFF
                                    self.writeMem(address, self.A)
                                    clock.ticks += 6
                                    continue

                                elif inst ==  0x90 :                            # REL BCC
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    if not self.C  :
                                        clock.ticks += 1
                                        if ((self.PC & 0xFF) + address) & 0xFF00 :
                                            clock.ticks += 1
                                        if address & self.SIGN :
                                            address |= 0xFF00
                                        self.PC = (self.PC + address) & 0xFFFF
                                    clock.ticks += 2
                                    continue


                                elif inst ==  0x94 :                            # ZPX STY
                                    address = (self.readMem(self.PC) + self.X) & 0xFF
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.writeMem(address, self.Y)
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0x95 :                            # ZPX STA
                                    self.writeMem((self.readMem(self.PC) + self.X) & 0xFF, self.A)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0x96 :                            # ZPY STX
                                    self.writeMem((self.readMem(self.PC) + self.Y) & 0xFF, self.X)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    clock.ticks += 4
                                    continue

                            else :
                                # 0x98 to 0x9F

                                if inst ==  0x98 :                              # IMP TYA
                                    self.A = self.Y
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0x99 :                            # ABY STA
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = (address + self.Y) & 0xFFFF
                                    self.writeMem(address, self.A)
                                    clock.ticks += 5
                                    continue

                                elif inst ==  0x9A :                            # IMP TXS
                                    self.SP = self.X
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0x9D :                            # ABX STA
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = (address + self.X) & 0xFFFF
                                    self.writeMem(address, self.A)
                                    clock.ticks += 5
                                    continue

                    else :
                        if inst < 0xB0 :
                            if inst < 0xA8 :
                            # 0xA0 to 0xA7

                                if inst ==  0xA4 :                              # ZPG LDY
                                    self.Y = self.readMem(self.readMem(self.PC))
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.Z = self.Y == 0
                                    self.S = self.Y > 0x7F
                                    clock.ticks += 3
                                    continue

                                elif inst ==  0xA5 :                            # ZPG LDA
                                    self.A = self.readMem(self.readMem(self.PC))
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 3
                                    continue

                                elif inst ==  0xA0 :                            # IMM LDY
                                    self.Y = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.Z = self.Y == 0
                                    self.S = self.Y > 0x7F
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0xA1 :                            # IZX LDA
                                    value8 = (self.readMem(self.PC) + self.X) & 0xFF
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = self.readMem(value8)
                                    value8 = (value8 + 1) & 0xFF
                                    address |= self.readMem(value8) << 8
                                    self.A = self.readMem(address)
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 6
                                    continue

                                elif inst ==  0xA2 :                            # IMM LDX
                                    address = self.PC
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.X = self.readMem(address)
                                    self.Z = self.X == 0
                                    self.S = self.X > 0x7F
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0xA6 :                            # ZPG LDX
                                    self.X = self.readMem(self.readMem(self.PC))
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.Z = self.X == 0
                                    self.S = self.X > 0x7F
                                    clock.ticks += 3
                                    continue

                            else :
                                # 0xA8 to 0xAF

                                if inst ==  0xAD :                              # ABS LDA
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.A = self.readMem(address)
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0xA8 :                            # IMP TAY
                                    self.Y = self.A
                                    self.Z = self.Y == 0
                                    self.S = self.Y > 0x7F
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0xA9 :                            # IMM LDA
                                    self.A = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0xAA :                            # IMP TAX
                                    self.X = self.A
                                    self.Z = self.X == 0
                                    self.S = self.X > 0x7F
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0xAC :                            # ABS LDY
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.Y = self.readMem(address)
                                    self.Z = self.Y == 0
                                    self.S = self.Y > 0x7F
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0xAE :                            # ABS LDX
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.X = self.readMem(address)
                                    self.Z = self.X == 0
                                    self.S = self.X > 0x7F
                                    clock.ticks += 4
                                    continue

                        else :
                            if inst < 0xB8 :
                            # 0xB0 to 0xB7

                                if inst ==  0xB1 :                              # IZY LDA
                                    value8 = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = self.readMem(value8)
                                    value8 = (value8 + 1) & 0xFF
                                    address |= self.readMem(value8) << 8
                                    self.A = self.readMem((address + self.Y) & 0xFFFF)
                                    if ((address & 0xFF) + self.Y) & 0xFF00 :
                                        clock.ticks += 6
                                    else :
                                        clock.ticks += 5
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    continue

                                elif inst ==  0xB0 :                            # REL BCS
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    if self.C :
                                        clock.ticks += 1
                                        if address & self.SIGN :
                                            address |= 0xFF00
                                        if ((self.PC & 0xFF) + address) & 0xFF00 :
                                            clock.ticks += 1
                                        self.PC = (self.PC + address) & 0xFFFF
                                    clock.ticks += 2
                                    continue


                                elif inst ==  0xB4 :                            # ZPX LDY
                                    address = (self.readMem(self.PC) + self.X) & 0xFF
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.Y = self.readMem(address)
                                    self.Z = self.Y == 0
                                    self.S = self.Y > 0x7F
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0xB5 :                            # ZPX LDA
                                    address = (self.readMem(self.PC) + self.X) & 0xFF
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.A = self.readMem(address)
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0xB6 :                            # ZPY LDX
                                    address = (self.readMem(self.PC) + self.Y) & 0xFF
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.X = self.readMem(address)
                                    self.Z = self.X == 0
                                    self.S = self.X > 0x7F
                                    clock.ticks += 4
                                    continue

                            else :
                                # 0xB8 to 0xBF

                                if inst ==  0xBD :                              # ABX LDA
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    if (address + self.X) & 0xFF00 :
                                        clock.ticks += 5
                                    else :
                                        clock.ticks += 4
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = (address + self.X) & 0xFFFF
                                    self.A = self.readMem(address)
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    continue

                                elif inst ==  0xB8 :                            # IMP CLV
                                    self.V = 0
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0xB9 :                            # ABY LDA
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    if (address + self.Y) & 0xFF00 :
                                        clock.ticks += 5
                                    else :
                                        clock.ticks += 4
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = (address + self.Y) & 0xFFFF
                                    self.A = self.readMem(address)
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    continue

                                elif inst ==  0xBA :                            # IMP TSX
                                    self.X = self.SP
                                    self.Z = self.X == 0
                                    self.S = self.X > 0x7F
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0xBC :                            # ABX LDY
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    if (address + self.X) & 0xFF00 :
                                        clock.ticks += 5
                                    else :
                                        clock.ticks += 4
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = (address + self.X) & 0xFFFF
                                    self.Y = self.readMem(address)
                                    self.Z = self.Y == 0
                                    self.S = self.Y > 0x7F
                                    continue


                                elif inst ==  0xBE :                            # ABY LDX
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    if (address + self.Y) & 0xFF00 :
                                        clock.ticks += 5
                                    else :
                                        clock.ticks += 4
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = (address + self.Y) & 0xFFFF
                                    self.X = self.readMem(address)
                                    self.Z = self.X == 0
                                    self.S = self.X > 0x7F
                                    continue

                else :
                    if inst < 0xE0 :
                        if inst < 0xD0 :
                            if inst < 0xC8 :
                            # 0xC0 to  0xC7

                                if inst ==  0xC6 :                              # ZPG DEC
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 = self.readMem(address)
                                    value8 = (value8 - 1) & 0xFF
                                    self.writeMem(address, value8)
                                    self.Z = value8 == 0
                                    self.S = value8 > 0x7F
                                    clock.ticks += 5
                                    continue

                                elif inst ==  0xC0 :                            # IMM CPY
                                    value8 = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.Z = ((self.Y - value8) & 0xFF) == 0
                                    self.S = ((self.Y - value8) & self.SIGN) != 0
                                    self.C = (self.Y >= value8) != 0
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0xC1 :                            # IZX CMP
                                    value8 = (self.readMem(self.PC) + self.X) & 0xFF
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = self.readMem(value8)
                                    value8 = (value8 + 1) & 0xFF
                                    address |= self.readMem(value8) << 8
                                    value8 = self.readMem(address)
                                    self.Z = ((self.A - value8) & 0xFF) == 0
                                    self.S = ((self.A - value8) & self.SIGN) != 0
                                    self.C = (self.A >= value8) != 0
                                    clock.ticks += 6
                                    continue

                                elif inst ==  0xC4 :                            # ZPG CPY
                                    value8 = self.readMem(self.readMem(self.PC))
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.Z = ((self.Y - value8) & 0xFF) == 0
                                    self.S = ((self.Y - value8) & self.SIGN) != 0
                                    self.C = (self.Y >= value8) != 0
                                    clock.ticks += 3
                                    continue

                                elif inst ==  0xC5 :                            # ZPG CMP
                                    value8 = self.readMem(self.readMem(self.PC))
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.Z = ((self.A - value8) & 0xFF) == 0
                                    self.S = ((self.A - value8) & self.SIGN) != 0
                                    self.C = (self.A >= value8) != 0
                                    clock.ticks += 3
                                    continue

                            else :
                                # 0xC8 to 0xCF

                                if inst ==  0xC8 :                              # IMP INY
                                    self.Y = (self.Y + 1) & 0xFF
                                    self.Z = self.Y  == 0
                                    self.S = self.Y > 0x7F
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0xC9 :                            # IMM CMP
                                    value8 = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.Z = ((self.A - value8) & 0xFF) == 0
                                    self.S = ((self.A - value8) & self.SIGN) != 0
                                    self.C = (self.A >= value8) != 0
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0xCA :                              # IMP DEX
                                    self.X = (self.X - 1) & 0xFF
                                    self.Z = (self.X & 0xFF) == 0
                                    self.S = self.X > 0x7F
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0xCC :                            # ABS CPY
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 = self.readMem(address)
                                    self.Z = ((self.Y - value8) & 0xFF) == 0
                                    self.S = ((self.Y - value8) & self.SIGN) != 0
                                    self.C = (self.Y >= value8) != 0
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0xCD :                            # ABS CMP
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 = self.readMem(address)
                                    self.Z = ((self.A - value8) & 0xFF) == 0
                                    self.S = ((self.A - value8) & self.SIGN) != 0
                                    self.C = (self.A >= value8) != 0
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0xCE :                            # ABS DEC
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 = self.readMem(address)
                                    value8 = (value8 - 1) & 0xFF
                                    self.writeMem(address, value8)
                                    self.Z = value8 == 0
                                    self.S = value8 > 0x7F
                                    clock.ticks += 3
                                    continue

                        else :
                            if inst < 0xD8 :
                            # 0xD0 to 0xD7

                                if inst ==  0xD0 :                              # REL BNE
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    if not self.Z :
                                        clock.ticks += 1
                                        if address & self.SIGN :
                                            address |= 0xFF00
                                        if ((self.PC & 0xFF) + address) & 0xFF00 :
                                            clock.ticks += 1
                                        self.PC = (self.PC + address) & 0xFFFF
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0xD1 :                            # IZY CMP
                                    value8 = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = self.readMem(value8)
                                    if (address + self.Y) & 0xFF00 :
                                        clock.ticks += 6
                                    else :
                                        clock.ticks += 5
                                    value8 = (value8 + 1) & 0xFF
                                    address |= self.readMem(value8) << 8
                                    address = (address + self.Y) & 0xFFFF
                                    value8 = self.readMem(address)
                                    self.Z = ((self.A - value8) & 0xFF) == 0
                                    self.S = ((self.A - value8) & self.SIGN) != 0
                                    self.C = (self.A >= value8) != 0
                                    continue

                                elif inst ==  0xD5 :                            # ZPX CMP
                                    address = (self.readMem(self.PC) + self.X) & 0xFF
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 = self.readMem(address)
                                    self.Z = ((self.A - value8) & 0xFF) == 0
                                    self.S = ((self.A - value8) & self.SIGN) != 0
                                    self.C = (self.A >= value8) != 0
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0xD6 :                            # ZPX DEC
                                    address = (self.readMem(self.PC) + self.X) & 0xFF
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 = self.readMem(address)
                                    value8 = (value8 - 1) & 0xFF
                                    self.writeMem(address, value8)
                                    self.Z = value8 == 0
                                    self.S = value8 > 0x7F
                                    clock.ticks += 6
                                    continue

                            else :
                                # 0xD8 to 0xDF

                                if inst ==  0xD8 :                              # IMP CLD
                                    self.D = 0
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0xD9 :                            # ABY CMP
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    if (address + self.Y) & 0xFF00 :
                                        clock.ticks += 5
                                    else :
                                        clock.ticks += 4
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = (address + self.Y) & 0xFFFF
                                    value8 = self.readMem(address)
                                    self.Z = ((self.A - value8) & 0xFF) == 0
                                    self.S = ((self.A - value8) & self.SIGN) != 0
                                    self.C = (self.A >= value8) != 0
                                    continue

                                elif inst ==  0xDD :                            # ABX CMP
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    if (address + self.X) & 0xFF00 :
                                        clock.ticks += 5
                                    else :
                                        clock.ticks += 4
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = (address + self.X) & 0xFFFF
                                    value8 = self.readMem(address)
                                    self.Z = ((self.A - value8) & 0xFF) == 0
                                    self.S = ((self.A - value8) & self.SIGN) != 0
                                    self.C = (self.A >= value8) != 0
                                    continue

                                elif inst ==  0xDE :                            # ABX DEC
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = (address + self.X) & 0xFFFF
                                    value8 = self.readMem(address)
                                    value8 = (value8 - 1) & 0xFF
                                    self.writeMem(address, value8)
                                    self.Z = value8 == 0
                                    self.S = (value8 & self.SIGN) != 0
                                    clock.ticks += 7
                                    continue

                    else :
                        if inst < 0xF0 :
                            if inst < 0xE8 :
                            # 0xE0 to 0xE7

                                if inst ==  0xE6 :                              # ZPG INC
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 = (self.readMem(address) + 1) & 0xFF
                                    self.writeMem(address, value8)
                                    self.Z = value8 == 0
                                    self.S = value8 > 0x7F
                                    clock.ticks += 5
                                    continue

                                elif inst ==  0xE0 :                            # IMM CPX
                                    value8 = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.Z = ((self.X - value8) & 0xFF) == 0
                                    self.S = ((self.X - value8) & self.SIGN) != 0
                                    self.C = (self.X >= value8) != 0
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0xE1 :                            # IZX SBC
                                    value8 = (self.readMem(self.PC) + self.X) & 0xFF
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = self.readMem(value8)
                                    value8 = (value8 + 1) & 0xFF
                                    address |= self.readMem(value8) << 8
                                    value8 = self.readMem(address)
                                    value8 ^= 0xFF
                                    if self.D :
                                        value8 -= 0x0066
                                    value16 = (self.A + value8 + self.C) & 0xFFFF
                                    self.V = ((value16 ^ self.A) & (value16 ^ value8) & 0x0080) != 0
                                    if self.D :
                                        value16 += ((((value16 + 0x66) ^ self.A ^ value8) >> 3) & 0x22) * 3
                                    self.C = value16 > 0xFF
                                    self.A = value16 & 0xFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 6
                                    continue

                                elif inst ==  0xE4 :                            # ZPG CPX
                                    value8 = self.readMem(self.readMem(self.PC))
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    self.Z = ((self.X - value8) & 0xFF) == 0
                                    self.S = ((self.X - value8) & self.SIGN) != 0
                                    self.C = (self.X >= value8) != 0
                                    clock.ticks += 3
                                    continue

                                elif inst ==  0xE5 :                            # ZPG SBC
                                    value8 = self.readMem(self.readMem(self.PC))
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 ^= 0xFF
                                    if self.D :
                                        value8 -= 0x0066
                                    value16 = (self.A + value8 + self.C) & 0xFFFF
                                    self.V = ((value16 ^ self.A) & (value16 ^ value8) & 0x0080) != 0
                                    if self.D :
                                        value16 += ((((value16 + 0x66) ^ self.A ^ value8) >> 3) & 0x22) * 3
                                    self.C = value16 > 0xFF
                                    self.A = value16 & 0xFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 3
                                    continue

                            else :

                                if inst ==  0xEA :                              # IMP NOP
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0xE8 :                            # IMP INX
                                    self.X = (self.X + 1) & 0xFF
                                    self.Z = self.X == 0
                                    self.S = self.X > 0x7F
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0xE9 :                            # IMM SBC
                                    value8 = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 ^= 0xFF
                                    if self.D :
                                        value8 -= 0x0066
                                    value16 = (self.A + value8 + self.C) & 0xFFFF
                                    self.V = ((value16 ^ self.A) & (value16 ^ value8) & 0x0080) != 0
                                    if self.D :
                                        value16 += ((((value16 + 0x66) ^ self.A ^ value8) >> 3) & 0x22) * 3
                                    self.C = value16 > 0xFF
                                    self.A = value16 & 0xFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0xEC :                            # ABS CPX
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 = self.readMem(address)
                                    self.Z = ((self.X - value8) & 0xFF) == 0
                                    self.S = ((self.X - value8) & self.SIGN) != 0
                                    self.C = (self.X >= value8) != 0
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0xED :                            # ABS SBC
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 = self.readMem(address)
                                    value8 ^= 0xFF
                                    if self.D :
                                        value8 -= 0x0066
                                    value16 = (self.A + value8 + self.C) & 0xFFFF
                                    self.V = ((value16 ^ self.A) & (value16 ^ value8) & 0x0080) != 0
                                    if self.D :
                                        value16 += ((((value16 + 0x66) ^ self.A ^ value8) >> 3) & 0x22) * 3
                                    self.C = value16 > 0xFF
                                    self.A = value16 & 0xFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0xEE :                            # ABS INC
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 = self.readMem(address)
                                    value8 = (value8 + 1) & 0xFF
                                    self.writeMem(address, value8)
                                    self.Z = value8 == 0
                                    self.S = value8 > 0x7F
                                    clock.ticks += 6
                                    continue

                        else :
                            if inst < 0xF8 :
                            # 0xF0 to 0xF7

                                if inst ==  0xF0 :                              # REL BEQ
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    if self.Z :
                                        clock.ticks += 1
                                        if address & self.SIGN :
                                            address |= 0xFF00
                                        if ((self.PC & 0xFF) + address) & 0xFF00 :
                                            clock.ticks += 1
                                        self.PC = (self.PC + address) & 0xFFFF
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0xF1 :                            # IZY SBC
                                    value8 = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = self.readMem(value8)
                                    if (address + self.Y) & 0xFF00 :
                                        clock.ticks += 1
                                    value8 = (value8 + 1) & 0xFF
                                    address |= self.readMem(value8) << 8
                                    address = (address + self.Y) & 0xFFFF
                                    value8 = self.readMem(address)
                                    value8 ^= 0xFF
                                    if self.D :
                                        value8 -= 0x0066
                                    value16 = (self.A + value8 + self.C) & 0xFFFF
                                    self.V = ((value16 ^ self.A) & (value16 ^ value8) & 0x0080) != 0
                                    if self.D :
                                        value16 += ((((value16 + 0x66) ^ self.A ^ value8) >> 3) & 0x22) * 3
                                    self.C = value16 > 0xFF
                                    self.A = value16 & 0xFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 5
                                    continue

                                elif inst ==  0xF5 :                            # ZPX SBC
                                    address = (self.readMem(self.PC) + self.X) & 0xFF
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 = self.readMem(address)
                                    value8 ^= 0xFF
                                    if self.D :
                                        value8 -= 0x0066
                                    value16 = (self.A + value8 + self.C) & 0xFFFF
                                    self.V = ((value16 ^ self.A) & (value16 ^ value8) & 0x0080) != 0
                                    if self.D :
                                        value16 += ((((value16 + 0x66) ^ self.A ^ value8) >> 3) & 0x22) * 3
                                    self.C = value16 > 0xFF
                                    self.A = value16 & 0xFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0xF6 :                            # ZPX INC
                                    address = (self.readMem(self.PC) + self.X) & 0xFF
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    value8 = self.readMem(address)
                                    value8 = (value8 + 1) & 0xFF
                                    self.writeMem(address, value8)
                                    self.Z = value8 == 0
                                    self.S = value8 > 0x7F
                                    clock.ticks += 6
                                    continue

                            else :
                                # 0xF8 to 0xFF

                                if inst ==  0xF8 :                              # IMP SED
                                    self.D = 1
                                    clock.ticks += 2
                                    continue

                                elif inst ==  0xF9 :                            # ABY SBC
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    if (address + self.Y) & 0xFF00 :
                                        clock.ticks += 1
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = (address + self.Y) & 0xFFFF
                                    value8 = self.readMem(address)
                                    value8 ^= 0xFF
                                    if self.D :
                                        value8 -= 0x0066
                                    value16 = (self.A + value8 + self.C) & 0xFFFF
                                    self.V = ((value16 ^ self.A) & (value16 ^ value8) & 0x0080) != 0
                                    if self.D :
                                        value16 += ((((value16 + 0x66) ^ self.A ^ value8) >> 3) & 0x22) * 3
                                    self.C = value16 > 0xFF
                                    self.A = value16 & 0xFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0xFD :                            # ABX SBC
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    if (address + self.X) & 0xFF00 :
                                        clock.ticks += 1
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = (address + self.X) & 0xFFFF
                                    value8 = self.readMem(address)
                                    value8 ^= 0xFF
                                    if self.D :
                                        value8 -= 0x0066
                                    value16 = (self.A + value8 + self.C) & 0xFFFF
                                    self.V = ((value16 ^ self.A) & (value16 ^ value8) & 0x0080) != 0
                                    if self.D :
                                        value16 += ((((value16 + 0x66) ^ self.A ^ value8) >> 3) & 0x22) * 3
                                    self.C =  (value16 & 0xFF00) != 0
                                    self.A = value16 & 0xFF
                                    self.Z = self.A == 0
                                    self.S = self.A > 0x7F
                                    clock.ticks += 4
                                    continue

                                elif inst ==  0xFE :                            # ABX INC
                                    address = self.readMem(self.PC)
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address |= self.readMem(self.PC) << 8
                                    self.PC = (self.PC + 1) & 0xFFFF
                                    address = (address + self.X) & 0xFFFF
                                    value8 = self.readMem(address)
                                    value8 = (value8 + 1) & 0xFF
                                    self.writeMem(address, value8)
                                    self.Z = value8 == 0
                                    self.S = value8 > 0x7F
                                    clock.ticks += 7
                                    continue

        return(self.PC)

puce6502.py
ArthurFerreira2/reinette-II-plus-dot-py
#!/bin/env python3

"""
  Tests for puce6502, a MOS 6502 cpu emulator
  Last modified 21st of september 2023 - python version
  Copyright (c) 2023 Arthur Ferreira (arthur.ferreira2@gmail.com)

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions :

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
"""

"""
  6502 functonnal tests
  using Klaus Dormann's 6502 functonnal tests published at :
  https://github.com/Klaus2m5/6502_65C02_functional_tests

  The code down below was used during developpment for tests, optimization
  and troubleshooting. It is not required for normal operation

  It is provided so you can check the accuracy of the emultaion, modify it to
  to your will and verify your changes
"""

import puce6502, clock
import sys

# mnemonics
mn = [
    "BRK","ORA","UND","UND","UND","ORA","ASL","UND","PHP","ORA","ASL","UND","UND","ORA","ASL","UND",
    "BPL","ORA","UND","UND","UND","ORA","ASL","UND","CLC","ORA","UND","UND","UND","ORA","ASL","UND",
    "JSR","AND","UND","UND","BIT","AND","ROL","UND","PLP","AND","ROL","UND","BIT","AND","ROL","UND",
    "BMI","AND","UND","UND","UND","AND","ROL","UND","SEC","AND","UND","UND","UND","AND","ROL","UND",
    "RTI","EOR","UND","UND","UND","EOR","LSR","UND","PHA","EOR","LSR","UND","JMP","EOR","LSR","UND",
    "BVC","EOR","UND","UND","UND","EOR","LSR","UND","CLI","EOR","UND","UND","UND","EOR","LSR","UND",
    "RTS","ADC","UND","UND","UND","ADC","ROR","UND","PLA","ADC","ROR","UND","JMP","ADC","ROR","UND",
    "BVS","ADC","UND","UND","UND","ADC","ROR","UND","SEI","ADC","UND","UND","UND","ADC","ROR","UND",
    "UND","STA","UND","UND","STY","STA","STX","UND","DEY","UND","TXA","UND","STY","STA","STX","UND",
    "BCC","STA","UND","UND","STY","STA","STX","UND","TYA","STA","TXS","UND","UND","STA","UND","UND",
    "LDY","LDA","LDX","UND","LDY","LDA","LDX","UND","TAY","LDA","TAX","UND","LDY","LDA","LDX","UND",
    "BCS","LDA","UND","UND","LDY","LDA","LDX","UND","CLV","LDA","TSX","UND","LDY","LDA","LDX","UND",
    "CPY","CMP","UND","UND","CPY","CMP","DEC","UND","INY","CMP","DEX","UND","CPY","CMP","DEC","UND",
    "BNE","CMP","UND","UND","UND","CMP","DEC","UND","CLD","CMP","UND","UND","UND","CMP","DEC","UND",
    "CPX","SBC","UND","UND","CPX","SBC","INC","UND","INX","SBC","NOP","UND","CPX","SBC","INC","UND",
    "BEQ","SBC","UND","UND","UND","SBC","INC","UND","SED","SBC","UND","UND","UND","SBC","INC","UND"
]

# addressing modes for the match case below
am = [
    0, 10, 0, 0, 0, 3, 3, 0, 0, 2, 1, 0, 0, 7, 7, 0,
    6, 11, 0, 0, 0, 4, 4, 0, 0, 9, 0, 0, 0, 8, 8, 0,
    7, 10, 0, 0, 3, 3, 3, 0, 0, 2, 1, 0, 7, 7, 7, 0,
    6, 11, 0, 0, 0, 4, 4, 0, 0, 9, 0, 0, 0, 8, 8, 0,
    0, 10, 0, 0, 0, 3, 3, 0, 0, 2, 1, 0, 7, 7, 7, 0,
    6, 11, 0, 0, 0, 4, 4, 0, 0, 9, 0, 0, 0, 8, 8, 0,
    0, 10, 0, 0, 0, 3, 3, 0, 0, 2, 1, 0,12, 7, 7, 0,
    6, 11, 0, 0, 0, 4, 4, 0, 0, 9, 0, 0, 0, 8, 8, 0,
    0, 10, 0, 0, 3, 3, 3, 0, 0, 0, 0, 0, 7, 7, 7, 0,
    6, 11, 0, 0, 4, 4, 5, 0, 0, 9, 0, 0, 0, 8, 0, 0,
    2, 10, 2, 0, 3, 3, 3, 0, 0, 2, 0, 0, 7, 7, 7, 0,
    6, 11, 0, 0, 4, 4, 5, 0, 0, 9, 0, 0, 8, 8, 9, 0,
    2, 10, 0, 0, 3, 3, 3, 0, 0, 2, 0, 0, 7, 7, 7, 0,
    6, 11, 0, 0, 0, 4, 4, 0, 0, 9, 0, 0, 0, 8, 8, 0,
    2, 10, 0, 0, 3, 3, 3, 0, 0, 2, 0, 0, 7, 7, 7, 0,
    6, 11, 0, 0, 0, 4, 4, 0, 0, 9, 0, 0, 0, 8, 8, 0
]


ram = bytearray(0x10000)

def readMem(address) :
    return ram[address]

def writeMem(address, value) :
    ram[address] = value

open('6502_functional_test.bin', 'rb').readinto(ram)

cpu = puce6502.Puce6502(readMem, writeMem)
cpu.rst();                                                                      # reset the CPU
cpu.PC = 0x400;                                                                 # set Program Counter to start of code

oldticks = clock.ticks
oldPC = cpu.PC
newPC = cpu.PC                                                                  # for detecting the BNE $FE when a test fails


if len(sys.argv)  != 2 :
    print("Usage : puce6502Tests.py a|b", end = '\n\n')
    print("where option a runs the functonnal test to check the Accurary of the emulation")
    print("and option b runs the functonnal tests with no output for Benchmarking (total of 96240573 clock cycles)")
    exit()

if sys.argv[1] == 'a' :

    while(True) :

        b1 = ram[(newPC + 1) & 0xFFFF]
        b2 = ram[(newPC + 2) & 0xFFFF]
        op = ram[newPC]

        print(f"{newPC:04X} {op:02X} ", end='')

        match am[op] :
            case  0x0 :                                                         # implied
                print(f"       {mn[op]}          ", end='')
            case  1 :                                                           # accumulator
                print(f"       {mn[op]} A        ", end='')
            case  2 :                                                           # immediate
                print(f"{b1:02X}     {mn[op]} #${b1:02X}     ", end='')
            case  3 :                                                           # zero page
                print(f"{b1:02X}     {mn[op]} ${b1:02X}      ", end='')
            case  4 :                                                           # zero page, X indexed
                print(f"{b1:02X}     {mn[op]} ${b1:02X},X    ", end='')
            case  5 :                                                           # zero page, Y indexed
                print(f"{b1:02X}     {mn[op]} ${b1:02X},Y    ", end='')
            case  6 :                                                           # relative
                print(f"{b1:02X}     {mn[op]} ${b1:02X}      ", end='')
            case  10 :                                                          # X indexed, indirect
                print(f"{b1:02X}     {mn[op]} (${b1:02X},X)  ", end='')
            case  11 :                                                          # indirect, Y indexed
                print(f"{b1:02X}     {mn[op]} (${b1:02X}),Y  ", end='')
            case  7 :                                                           # absolute
                print(f"{b1:02X}{b2:02X}   {mn[op]} ${b2:02X}{b1:02X}    ", end='')
            case  8 :                                                           # absolute, X indexed
                print(f"{b1:02X}{b2:02X}   {mn[op]} ${b2:02X}{b1:02X},X  ", end='')
            case  9 :                                                           # absolute, Y indexed
                print(f"{b1:02X}{b2:02X}   {mn[op]} ${b2:02X}{b1:02X},Y  ", end='')
            case  12 :                                                          # indirect
                print(f"{b1:02X}{b2:02X}   {mn[op]} (${b2:02X}{b1:02X})  ", end='')

        newPC = cpu.run(1)

        print(f"A={cpu.A:02X}  X={cpu.X:02X}  Y={cpu.Y:02X}  S={cpu.SP:02X}  *S={ram[0x0100 + cpu.SP]:02X}  {'N' if cpu.S else '-'}{'V' if cpu.V else '-'}{'U' if cpu.U else '-'}{'B' if cpu.B else '-'}{'D' if cpu.D else '-'}{'I' if cpu.I else '-'}{'Z' if cpu.Z else '-'}{'C' if cpu.C else '-'}", end='')

        print(f"   Cycles: {clock.ticks - oldticks}   Total: {clock.ticks}")
        oldticks = clock.ticks

        if newPC == 0x3469 :                                                    # 6502_functional_test SUCCESS
            print(f"\nReached end of 6502_functional_test @ {newPC:04X} : SUCCESS !")
            break

        if newPC == oldPC :                                                     # dump memory to file in case a loop is detected
            print(f"\n\nLoop detected @ {newPC:04X} - dumping memory ...\n\n")
            with open('memory.dump', 'wb') as f:
                f.write(mem.ram)
            exit()

        oldPC = newPC

else :                                                                           # Benchmarks

    cpu.run(96240573)



"""
    # test results :

    <--->
    3457 69 55     ADC #$55       A=AA  X=0E  Y=FF  S=FF  *S=34  NVUB----   Cycles: 2   Total: 96240555
    3459 C9 AA     CMP #$AA       A=AA  X=0E  Y=FF  S=FF  *S=34  -VUB--ZC   Cycles: 2   Total: 96240557
    345B D0 FE     BNE $FE        A=AA  X=0E  Y=FF  S=FF  *S=34  -VUB--ZC   Cycles: 2   Total: 96240559
    345D AD 0002   LDA $0200      A=2B  X=0E  Y=FF  S=FF  *S=34  -VUB---C   Cycles: 4   Total: 96240563
    3460 C9 2B     CMP #$2B       A=2B  X=0E  Y=FF  S=FF  *S=34  -VUB--ZC   Cycles: 2   Total: 96240565
    3462 D0 FE     BNE $FE        A=2B  X=0E  Y=FF  S=FF  *S=34  -VUB--ZC   Cycles: 2   Total: 96240567
    3464 A9 F0     LDA #$F0       A=F0  X=0E  Y=FF  S=FF  *S=34  NVUB---C   Cycles: 2   Total: 96240569
    3466 8D 0002   STA $0200      A=F0  X=0E  Y=FF  S=FF  *S=34  NVUB---C   Cycles: 4   Total: 96240573

    Reached end of 6502_functional_test @ 3469 : SUCCESS !


    This means puce6502 passes the 6502 functionnal tests :

                            ; S U C C E S S ************************************************
                            ; -------------
                                    success         ;if you get here everything went well
    3469 : 4c6934          >        jmp *           ;test passed, no errors

                            ; -------------
                            ; S U C C E S S ************************************************


    # "Benchmarks", using a core i5-8365u laptop

    $ time python ./puce6502Tests.py

    match case version :
    real    0m43.328s
    user    0m0.015s
    sys     0m0.000s


    if elif version :
    real    0m39.126s
    user    0m0.000s
    sys     0m0.015s


    if elif with binary search tree deep 4 :
    real    0m23.438s
    user    0m0.000s
    sys     0m0.015s


    if elif with binary search tree deep 5 :
    real    0m22.976s
    user    0m0.015s
    sys     0m0.000s

"""
puce6502Tests.py
ArthurFerreira2/reinette-II-plus-dot-py
#!/bin/env python3

import sys, os
import ctypes
from sdl2 import *                                                              # pip install pysdl2 pysdl2-dll

import puce6502, memory, keyctrl, screen, speaker, paddle, disk, clock

# import cProfile
# from pstats import Stats, SortKey

# profiler = cProfile.Profile()
# profiler.enable()



helpMsg = b"""
        reinette II plus dot py

Drag'n'drop a .nib file to load a floppy

F1   : this help
F2   : take a screenshot
F3   : paste text from clipboard
F4   : mute / unmute sound
F5   : toggle monochrome (in HGR only)
F6   : decrease zoom down to 1:1
F7   : increase zoom up to 8:1
F8   : decrease desired FPS
F9   : increase desired FPS
F10  : toggle pause
F11  : here is your RESET key
F12  : power cycle

Joystick : 1,2,3 and 5 on keypad.
           CTRL and ALT for the buttons


More at github.com/ArthurFerreira2
"""


#============================================ CPU AND PERIPHERALS INITIALIZATION

screen  = screen.Screen()                                                       # must be instanciated before speaker as it inits SDL2
speaker = speaker.Speaker()
paddle0 = paddle.Paddle()                                                       # a joystick is two paddles packed together
paddle1 = paddle.Paddle()
keyctrl = keyctrl.Keyctrl()                                                     # keyboard controller

disk = disk.Disk()                                                              # instanciate one disk drive
if len(sys.argv) > 1 :                                                          # load floppy if provided at command line
    disk.insertFloppy(sys.argv[1])
    screen.setWindowTitle("nib", os.path.basename(sys.argv[1][:-4]))            # adding name to title, removing the .nib extension

mem = memory.Memory(disk, keyctrl, paddle0, paddle1, screen, speaker)           # memory has side effects on peripherals through soft swiches
cpu = puce6502.Puce6502(mem.readMem, mem.writeMem)                              # cpu instantiation with pointer to functions to read and write  memory


#===================================================================== MAIN LOOP

running = True
paused  = False
event = SDL_Event()

while running :


    #=================================================== PROCESS SOME CPU CYCLES

    if not paused :
        cpu.run(clock.CPU_FREQUENCY / screen.FPS)                               # execute instructions until we reach the number clock cycles for one frame

    retries = 20                                                                # OVERCLOCKING CPU during disk access
    while disk.getMotorOn() and retries :                                       # speeds up disk access but causes a drop in fps
        cpu.run(10000)                                                          # execute instruction during 10000 extra clock cyles
        retries -= 1                                                            # retries prevents an infinite loop if motor don't go off


    #============================================================== UPDATE VIDEO

    screen.update(mem.ram)                                                      # refresh screen


    #==================================================== CATCH USER INTERACTION

    while SDL_PollEvent(ctypes.byref(event)) :

        alt   = SDL_GetModState() & KMOD_ALT
        ctrl  = SDL_GetModState() & KMOD_CTRL
        shift = SDL_GetModState() & KMOD_SHIFT

        paddle0.setButton(alt)                                                  # update push button 0 state
        paddle1.setButton(ctrl)                                                 # update push button 1 state


        #================================================== QUIT ON WINDOW CLOSE

        if event.type == SDL_QUIT :                                             # Window Manager sent TERM signal
            running = False


        #==================================== LOAD A FLOPPY IMAGE ON DRAG'N DROP

        elif event.type == SDL_DROPFILE :                                       # user dropped a file
            filename = event.drop.file                                          # get its full pathname
            disk.insertFloppy(filename)                                         # load the .nib into memory
            floppyName = os.path.basename(filename[:-4]).decode('utf-8')        # extracting 'name' from filename, removing the .nib extention
            if floppyName.find('(') == -1 :
                screen.setWindowTitle("nib", floppyName)                        # adding name to title, removing anything after the first '('
            else :
                screen.setWindowTitle("nib", floppyName[:floppyName.find('(')]) # adding name to title, removing anygthing after the first '('
            # SDL_free(filename)                                                  # free filename memory - crashes on linux : munmap_chunk(): invalid pointer Aborted (core dumped)

            paused = False                                                      # might already be the case
            mem.writeMem(0x03F4, 0)                                             # unset the Power-UP byte
            cpu.rst()                                                           # and do a cold reset
            continue


        # ================================================= APPLICATION CONTROLS

        elif event.type == SDL_KEYDOWN :                                        # a key has been pressed down

            if event.key.keysym.sym == SDLK_F1 :                                # F1 -> help box
                SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION,
                                         b"Help", helpMsg, None)
                continue

            elif event.key.keysym.sym == SDLK_F2 :                              # F2 -> take a screenshot
                screen.takeScreenshot()
                continue

            elif event.key.keysym.sym == SDLK_F3 :                              # F3 -> paste text from clipboard
                if SDL_HasClipboardText() :
                    clipboardText = SDL_GetClipboardText()
                    for c in clipboardText :                                    # process char by char
                        keyctrl.setKey(0x8D if c == 0x0A else c | 0x80)         # translates Line Feeds into Carriage Returns and sets bit 7 on
                    # SDL_free(ctypes.cast(clipboardText, ctypes.c_char_p))       # release the ressource - crashes on linux : munmap_chunk(): invalid pointer Aborted (core dumped)
                continue

            elif event.key.keysym.sym == SDLK_F4 :                              # F4 -> toggle mute
                speaker.toggleMute()
                continue

            elif event.key.keysym.sym == SDLK_F5 :                              # F5 -> toggle monochrome mode
                screen.toggleMonochrome()
                continue

            elif event.key.keysym.sym == SDLK_F6 :                              # F6 -> decrease window size
                screen.setZoom(-1)
                continue

            elif event.key.keysym.sym == SDLK_F7 :                              # F7 -> increase window size
                screen.setZoom(+1)
                continue

            elif event.key.keysym.sym == SDLK_F8 :                              # F8 -> decrease target FPS
                screen.FPS -= 1
                continue

            elif event.key.keysym.sym == SDLK_F9 :                              # F9 -> increase target FPS
                screen.FPS += 1
                continue

            elif event.key.keysym.sym == SDLK_F10 :                             # F10 -> toggle pause
                paused = not paused
                screen.setWindowTitle("paused", paused)
                continue

            elif event.key.keysym.sym == SDLK_F11 :                             # F11 -> reset the cpu
                cpu.rst()
                continue

            elif event.key.keysym.sym == SDLK_F12 :                             # F12 -> power cycle
                mem.writeMem(0x3F4, 0)                                          # unset the Power-UP byte
                cpu.rst()                                                       # reset the cpu
                continue

            #================================================= EMULATED JOYSTICK

            elif event.key.keysym.sym == SDLK_KP_1 :                            # hold pdl0 <-
                paddle0.update(0)
                continue
            elif event.key.keysym.sym == SDLK_KP_3 :                            # hold pdl0 ->
                paddle0.update(255)
                continue
            elif event.key.keysym.sym == SDLK_KP_5 :                            # hold pdl1 <-
                paddle1.update(0)
                continue
            elif event.key.keysym.sym == SDLK_KP_2 :                            # hold pdl1 ->
                paddle1.update(255)
                continue

            #================================================= EMULATED KEYBOARD

            if event.key.repeat == 0 :
                keyctrl.setKeyFromKeySym(event.key.keysym.sym, (1 if ctrl else 0) + (2 if shift else 0))  # taking into account the key modifiers

        elif event.type == SDL_KEYUP :
            if   event.key.keysym.sym == SDLK_KP_1 :                            # release pdl0 ->
                paddle0.update(127)
                continue
            elif event.key.keysym.sym == SDLK_KP_3 :                            # release pdl0 <-
                paddle0.update(127)
                continue
            elif event.key.keysym.sym == SDLK_KP_5 :                            # release pdl1 ->
                paddle1.update(127)
                continue
            elif event.key.keysym.sym == SDLK_KP_2 :                            # release pdl1 <-
                paddle1.update(127)
                continue


# profiler.disable()

# stream = open('profilingStats.txt', 'w')
# stats = Stats(profiler, stream = stream)

# stats.strip_dirs()
# stats.sort_stats('time')
# stats.dump_stats('prof_stats.dump')                                            # snakeviz prof_stats.dump
# stats.print_stats()

# stream.close()

reinetteII+.py
ArthurFerreira2/reinette-II-plus-dot-py
from sdl2 import *                                                              # pip install pysdl2 pysdl2-dll
import ctypes
from datetime import datetime

class Screen() :

    #================================================================= CONSTANTS

    GR_COLOR = [                                                                # the 16 low res colors
        [0,   0,   0  ], [226, 57,  86 ], [28,  116, 205], [126, 110, 173],
        [31,  129, 128], [137, 130, 122], [86,  168, 228], [144, 178, 223],
        [151, 88,  34 ], [234, 108, 21 ], [158, 151, 143], [255, 206, 240],
        [144, 192, 49 ], [255, 253, 166], [159, 210, 213], [255, 255, 255]
    ]

    HGR_COLORS = [                                                              # the high res colors (2 light levels)
        [0,   0,   0  ], [144, 192,  49], [126, 110, 173], [255, 255, 255],
        [0,   0,   0  ], [234, 108,  21], [ 86, 168, 228], [255, 255, 255],
        [0,   0,   0  ], [ 63,  55,  86], [ 72,  96,  25], [255, 255, 255],
        [0,   0,   0  ], [ 43,  84, 114], [117,  54,  10], [255, 255, 255]
    ]

    GR_OFFSET = [                                                               # helper for TEXT and GR video generation
        0x0000, 0x0080, 0x0100, 0x0180, 0x0200, 0x0280, 0x0300, 0x0380,         # lines 0-7
        0x0028, 0x00A8, 0x0128, 0x01A8, 0x0228, 0x02A8, 0x0328, 0x03A8,         # lines 8-15
        0x0050, 0x00D0, 0x0150, 0x01D0, 0x0250, 0x02D0, 0x0350, 0x03D0          # lines 16-23
    ]

    HGR_OFFSET = [                                                              # helper for HGR video generation
        0x0000, 0x0400, 0x0800, 0x0C00, 0x1000, 0x1400, 0x1800, 0x1C00,         # lines 0-7
        0x0080, 0x0480, 0x0880, 0x0C80, 0x1080, 0x1480, 0x1880, 0x1C80,         # lines 8-15
        0x0100, 0x0500, 0x0900, 0x0D00, 0x1100, 0x1500, 0x1900, 0x1D00,         # lines 16-23
        0x0180, 0x0580, 0x0980, 0x0D80, 0x1180, 0x1580, 0x1980, 0x1D80,
        0x0200, 0x0600, 0x0A00, 0x0E00, 0x1200, 0x1600, 0x1A00, 0x1E00,
        0x0280, 0x0680, 0x0A80, 0x0E80, 0x1280, 0x1680, 0x1A80, 0x1E80,
        0x0300, 0x0700, 0x0B00, 0x0F00, 0x1300, 0x1700, 0x1B00, 0x1F00,
        0x0380, 0x0780, 0x0B80, 0x0F80, 0x1380, 0x1780, 0x1B80, 0x1F80,
        0x0028, 0x0428, 0x0828, 0x0C28, 0x1028, 0x1428, 0x1828, 0x1C28,
        0x00A8, 0x04A8, 0x08A8, 0x0CA8, 0x10A8, 0x14A8, 0x18A8, 0x1CA8,
        0x0128, 0x0528, 0x0928, 0x0D28, 0x1128, 0x1528, 0x1928, 0x1D28,
        0x01A8, 0x05A8, 0x09A8, 0x0DA8, 0x11A8, 0x15A8, 0x19A8, 0x1DA8,
        0x0228, 0x0628, 0x0A28, 0x0E28, 0x1228, 0x1628, 0x1A28, 0x1E28,
        0x02A8, 0x06A8, 0x0AA8, 0x0EA8, 0x12A8, 0x16A8, 0x1AA8, 0x1EA8,
        0x0328, 0x0728, 0x0B28, 0x0F28, 0x1328, 0x1728, 0x1B28, 0x1F28,
        0x03A8, 0x07A8, 0x0BA8, 0x0FA8, 0x13A8, 0x17A8, 0x1BA8, 0x1FA8,
        0x0050, 0x0450, 0x0850, 0x0C50, 0x1050, 0x1450, 0x1850, 0x1C50,
        0x00D0, 0x04D0, 0x08D0, 0x0CD0, 0x10D0, 0x14D0, 0x18D0, 0x1CD0,
        0x0150, 0x0550, 0x0950, 0x0D50, 0x1150, 0x1550, 0x1950, 0x1D50,
        0x01D0, 0x05D0, 0x09D0, 0x0DD0, 0x11D0, 0x15D0, 0x19D0, 0x1DD0,
        0x0250, 0x0650, 0x0A50, 0x0E50, 0x1250, 0x1650, 0x1A50, 0x1E50,
        0x02D0, 0x06D0, 0x0AD0, 0x0ED0, 0x12D0, 0x16D0, 0x1AD0, 0x1ED0,         # lines 168-183
        0x0350, 0x0750, 0x0B50, 0x0F50, 0x1350, 0x1750, 0x1B50, 0x1F50,         # lines 176-183
        0x03D0, 0x07D0, 0x0BD0, 0x0FD0, 0x13D0, 0x17D0, 0x1BD0, 0x1FD0          # lines 184-191
    ]

    NORMAL  = 0
    INVERSE = 1
    FLASH   = 2


    def __init__(self) :

        #============================ VARIABLES USED DURING THE VIDEO PRODUCTION

        self.ATTRIBUTES = [ Screen.NORMAL if x>0x7F else Screen.INVERSE if x<0x40 else Screen.FLASH for x in range(0x100)]

        self.TEXT  = True                                                       # $C050 CLRTEXT   / $C051 SETTEXT
        self.MIXED = False                                                      # $C052 CLRMIXED  / $C053 SETMIXED
        self.PAGE2 = False                                                      # $C054 PAGE2 off / $C055 PAGE2 on
        self.HIRES = False                                                      # $C056 GR        / $C057 HGR

        self.previousMode = self.currentMode = 0                                # used to flush the video caches on mode change
        self.TextCache   = [[-1 for x in range(40)] for y in range( 24)]        # video caches
        self.LoResCache  = [[-1 for x in range(40)] for y in range( 24)]
        self.HiResCache  = [[-1 for x in range(40)] for y in range(192)]
        self.previousBit = [[ 0 for x in range(40)] for y in range(192)]

        self.pixelGR = SDL_Rect(0, 0, 7, 4)                                     # a block in LoRes
        self.dstRect = SDL_Rect(0, 0, 7, 8)                                     # the dst character in rdr
        self.charRects = [SDL_Rect(7 * x, 0, 7, 8) for x in  range(0, 128)]     # the src from the norm and rev textures

        self.zoom = 2
        self.monochrome = False

        self.FPS = 60.0                                                         # NTSC Frame Rate
        self.frameStart = 0
        self.frameNumber = 0                                                    # TEXT cursor flashes at 2Hz

        self.title = {"paused" : False,                                         # update the window title with dynamic data
                      "fps"    : self.FPS,
                      "r/w"    : '',
                      "nib"    : 'no floppy',
                      }
        self.titleFade = 30

        #==================================================== SDL INITIALIZATION

        SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)
        self.wdo = SDL_CreateWindow(b"reinette II plus dot py",
                                    SDL_WINDOWPOS_CENTERED,
                                    SDL_WINDOWPOS_CENTERED,
                                    280 * self.zoom, 192 * self.zoom,
                                    SDL_WINDOW_OPENGL)

        self.ico = SDL_LoadBMP(b"assets/icon.bmp")
        SDL_SetWindowIcon(self.wdo, self.ico)

        self.rdr = SDL_CreateRenderer(self.wdo, -1,
                                     SDL_RENDERER_SOFTWARE | SDL_RENDERER_PRESENTVSYNC)  # yes, software : not using double buffering

        SDL_SetRenderDrawBlendMode(self.rdr, SDL_BLENDMODE_NONE)
        SDL_RenderSetScale(self.rdr, self.zoom, self.zoom)
        SDL_EventState(SDL_DROPFILE, SDL_ENABLE)                                # to allow drag'n drop of .nib files

        #============================ LOAD NORMAL AND REVERSE CHARACTERS BITMAPS

        tmpSurface = SDL_LoadBMP(b"assets/font-normal.bmp")                     # load the normal font
        self.normCharTexture = SDL_CreateTextureFromSurface(self.rdr, tmpSurface)
        SDL_FreeSurface(tmpSurface)

        tmpSurface = SDL_LoadBMP(b"assets/font-reverse.bmp")                    # load the reverse font
        self.revCharTexture = SDL_CreateTextureFromSurface(self.rdr, tmpSurface)
        SDL_FreeSurface(tmpSurface)


    #============================================= DESTRUCTOR : SDL2 TERMINATION

    def __del__(self):
        SDL_DestroyTexture(self.normCharTexture)
        SDL_DestroyTexture(self.revCharTexture)
        SDL_DestroyRenderer(self.rdr)
        SDL_DestroyWindow(self.wdo)
        SDL_AudioQuit()
        SDL_Quit()


    #======================= GETERS AND SETTERS FOR VIDEO RELATED SOFT SWITCHES

    def setTEXT(self, value) :
        self.TEXT = value
        self.currentMode = 0 if value else 1

    def setMIXED(self, value) :
        self.MIXED = value
        self.currentMode = 2 if value else 3

    def setHIRES(self, value) :
        self.HIRES = value
        self.currentMode = 4 if value else 5

    def setPAGE2(self, value) :
        self.PAGE2 = value


    def getTEXT(self) :
        return self.TEXT

    def getMIXED(self) :
        return self.MIXED

    def getHIRES(self) :
        return self.HIRES

    def getPAGE2(self) :
        return self.PAGE2


    #================================================================ SCREENSHOT

    def takeScreenshot(self):
        sshot = SDL_GetWindowSurface(self.wdo)                                  # alocate a new surface
        SDL_RenderReadPixels(self.rdr,                                          # copy the screen into the surface
                             None,
                             SDL_GetWindowPixelFormat(self.wdo) ,
                             ctypes.cast(sshot.contents.pixels, ctypes.POINTER(Sint16)),
                             280 * 4 * self.zoom)                               # 4 bytes per pyxel (RGBA)

        date = datetime.now().strftime("-%Y-%m-%d-%H-%M-%S")                    # forge the filename
        filename = f"screenshots//{self.title['nib']}{date}.bmp"
        SDL_SaveBMP(sshot, bytes(filename, 'ascii'))                            # save under the screenshots folder
        SDL_FreeSurface(sshot)                                                  # release the surface


    #============================================================== WINDOW TITLE

    def setWindowTitle(self, attribute, value) :
        self.title[attribute] = value                                           # update the attribute
        if attribute == "r/w" :
            self.titleFade = 30


    def updateWindowTitle(self) :
        if self.title["paused"] :
            title = 'reinette II plus dot py   *PAUSED*'
            SDL_SetWindowTitle(self.wdo, bytes(title, 'ascii'))
            return

        self.titleFade -= 1
        if self.titleFade < 0 :
            self.title["r/w"] = ""

        title = f"reinette II plus dot py   {self.title['fps']:05.2f} FPS   {self.title['r/w']}   {self.title['nib']}"
        SDL_SetWindowTitle(self.wdo, bytes(title, 'ascii'))


    #============================================== TOGGLE MONOCHROME (HGR ONLY)

    def toggleMonochrome(self) :
        self.monochrome = not self.monochrome
        self.previousMode = 9


    #============================================================= WINDOW RESIZE

    def setZoom(self, value) :
        self.zoom += value                                                      # update zoom
        if self.zoom < 1 :
            self.zoom = 1
        if self.zoom > 8 :
            self.zoom = 8

        SDL_SetWindowSize(self.wdo, 280 * self.zoom, 192 * self.zoom)           # update window size
        SDL_RenderSetScale(self.rdr, self.zoom, self.zoom)                      # update renderer size
        self.previousMode = 9                                                   # provoke a video cache flush


    #============================================================ VIDEO RENDERER

    def update(self, ram) :

        #===================================================== CLEAR VIDEO CACHE

        if self.previousMode != self.currentMode :                              # Clear the video caches when video mode change
            self.TextCache   = [[-1 for x in range(40)] for y in range( 24)]
            self.LoResCache  = [[-1 for x in range(40)] for y in range( 24)]
            self.HiResCache  = [[-1 for x in range(40)] for y in range(192)]
            self.previousBit = [[ 0 for x in range(40)] for y in range(192)]
            self.previousMode = self.currentMode

        #========================================================= HGR VIDEO OUT

        if not self.TEXT and self.HIRES :                                       # HIGH RES GRAPHICS, mixed or not
            bits=[0] * 16
            vRamBase = 0x2000 + self.PAGE2 * 0x2000
            lastLine = 160 if self.MIXED else 192
            colorIdx = 0;                                                       # to index the color arrays

            for line in range(0, lastLine) :                                    # for every line
                for col in range(0, 40, 2) :                                    # for every 7 horizontal dots
                    x = col * 7
                    even = 0
                    addr = vRamBase + Screen.HGR_OFFSET[line] + col             # addr is always even
                    word = (ram[addr + 1] << 8) + ram[addr]                     # store the two next bytes into 'word'

                    if self.HiResCache[line][col] != word :                     # check if this group of 7 dots need a redraw
                        self.HiResCache[line][col] = word                       # update the video cache

                        for bit in range(0, 16) :                               # store all bits 'word' into 'bits'
                            bits[bit] = (word >> bit) & 1
                        colorSet = bits[7] * 4                                  # select the right color set

                        pbit = self.previousBit[line][col]                      # the bit value of the left dot
                        bit = 0                                                 # starting at 1st bit of 1st byte
                        while bit < 15 :                                        # until we reach bit7 of 2nd byte
                            if bit == 7 :                                       # moving into the second byte
                                colorSet = bits[15] * 4                         # update the color set
                                bit += 1                                        # skip bit 7

                            if self.monochrome :
                                colorIdx = bits[bit] * 3                        # black if bit==0, white if bit==1
                            else :
                                colorIdx = even + colorSet + (bits[bit] << 1) + pbit

                            SDL_SetRenderDrawColor(self.rdr,
                                                   Screen.HGR_COLORS[colorIdx][0],
                                                   Screen.HGR_COLORS[colorIdx][1],
                                                   Screen.HGR_COLORS[colorIdx][2],
                                                   SDL_ALPHA_OPAQUE)
                            SDL_RenderDrawPoint(self.rdr, x, line)

                            x += 1
                            pbit = bits[bit]                                    # proceed to the next pixel
                            bit += 1
                            even = 0 if even else 8                             # one pixel every other is darker

                        if col < 37 and self.previousBit[line][col+2] != pbit : # check color franging effect on next dot
                            self.previousBit[line][col + 2] = pbit              # set pbit
                            self.HiResCache[line][col + 2] = -1                 # invalidate video cache for the next dot


        #========================================================== GR VIDEO OUT

        elif not self.TEXT :                                                    # lOW RES GRAPHICS, mixed or not
            vRamBase = 0x400 + self.PAGE2 * 0x0400
            lastLine = 20 if self.MIXED else 24

            for col in range(0, 40) :                                           # for each column
                self.pixelGR.x = col * 7
                for line in range(0, lastLine) :                                # for each row
                    self.pixelGR.y = line * 8                                   # first block

                    glyph = ram[vRamBase + Screen.GR_OFFSET[line] + col]        # read video memory
                    if (self.LoResCache[line][col] != glyph) :
                        self.LoResCache[line][col] = glyph                      # update the video cache

                        colorIdx = glyph & 0x0F                                 # first nibble
                        SDL_SetRenderDrawColor(self.rdr,
                                               Screen.GR_COLOR[colorIdx][0],
                                               Screen.GR_COLOR[colorIdx][1],
                                               Screen.GR_COLOR[colorIdx][2],
                                               SDL_ALPHA_OPAQUE)
                        SDL_RenderFillRect(self.rdr, self.pixelGR)

                        self.pixelGR.y += 4                                     # second block
                        colorIdx = (glyph & 0xF0) >> 4                          # second nibble
                        SDL_SetRenderDrawColor(self.rdr,
                                               Screen.GR_COLOR[colorIdx][0],
                                               Screen.GR_COLOR[colorIdx][1],
                                               Screen.GR_COLOR[colorIdx][2],
                                               SDL_ALPHA_OPAQUE)
                        SDL_RenderFillRect(self.rdr, self.pixelGR)


        #======================================================== TEXT VIDEO OUT

        if self.TEXT or self.MIXED :                                            # TEXT 40 COLUMNS, can be mixed with lo or hi res
            vRamBase = 0x400 + self.PAGE2 * 0x0400
            firstLine = 0 if self.TEXT else 20
            self.frameNumber = (self.frameNumber + 1) % self.FPS                # for flashing characters (including the cursor)
            flashing = self.frameNumber % self.FPS > self.FPS / 2               # flashing twice a second

            for col in range(0, 40) :                                           # for each column
                self.dstRect.x = col * 7

                for line in range(firstLine, 24) :                              # for each row
                    self.dstRect.y = line * 8

                    glyph = ram[vRamBase + Screen.GR_OFFSET[line] + col]        # read video memory
                    glyphAttr = self.ATTRIBUTES[glyph]

                    if (glyphAttr == Screen.FLASH) or (self.TextCache[line][col] != glyph) :

                        self.TextCache[line][col] = glyph                       # update the video cache
                        glyph &= 0x7F                                           # unset bit 7
                        if glyph > 0x5F :
                            glyph &= 0x3F                                       # shifts to match the ASCII codes
                        if glyph < 0x20 :
                            glyph |= 0x40

                        if (glyphAttr == Screen.NORMAL) or \
                           (glyphAttr == Screen.FLASH and flashing) :
                            SDL_RenderCopy(self.rdr, self.normCharTexture,
                                           self.charRects[glyph], self.dstRect)
                        else :                                                  # it"s reverse of flashing off
                            SDL_RenderCopy(self.rdr, self.revCharTexture,
                                           self.charRects[glyph], self.dstRect)


    #============================================= SYNC TO FPS AND RENDER SCREEN

        frameTime = SDL_GetTicks() - self.frameStart                            # elapsed time since last call
        frameDelay = 1000.0 / self.FPS                                          # theorical frame duration for targeted FPS
        if frameTime < frameDelay :
            SDL_Delay(int(frameDelay - frameTime))                              # wait until next 1/FPS sec is reached

        self.setWindowTitle("fps", 1000.0 / (SDL_GetTicks() - self.frameStart)) # update the window title with the actual FPS
        self.updateWindowTitle()
        SDL_RenderPresent(self.rdr)                                             # render to screen

        self.frameStart = SDL_GetTicks()                                        # start of frame

screen.py
ArthurFerreira2/reinette-II-plus-dot-py
from sdl2 import audio                                                          # pip install pysdl2
import ctypes
import clock

class Speaker() :
    """
    emulates the speaker of the apple II

    Uses basic sdl audio (not using the SLD_audio lib)
    SDL_Init(SDL_INIT_AUDIO) has to be executed before the instanciation
    """

    BUFFER_LENGHT = 8192                                                        # found to be large enought
    FREQUENCY = 88000                                                           # sample frequency of the audio device
    RATE = clock.CPU_FREQUENCY / FREQUENCY


    def __init__(self) :
        """
        Initialise the sdl2 audio device and allocate buffers

        Allocate 3 buffers for the -volume, 0 and +volume values.
        Inits sdl audio devices (signed 8 bits, 88KHz ...).
        And set pause (muted) to False
        """

        self.buffer = []                                                        # three audio buffers,
        self.buffer.append((ctypes.c_int * Speaker.BUFFER_LENGHT)())            # one used when SPKR is 0
        self.buffer.append((ctypes.c_int * Speaker.BUFFER_LENGHT)())            # the other when SPKR is 1
        self.buffer.append((ctypes.c_int * Speaker.BUFFER_LENGHT)())            # silence, when speaker hasn't been switched for a while

        self.muted = False                                                      # mute/unmute switch

        for i in range(0, Speaker.BUFFER_LENGHT) :
            self.buffer[0][i] = -32
            self.buffer[1][i] = +32
            self.buffer[2][i] = 0

        self.previousTick = clock.ticks                                         # holds the previous ticks value
        self.SPKR = 1                                                           # $C030 Speaker toggle

        desired = audio.SDL_AudioSpec(0, 0, 0, 0)                               # Jeez, why have I done this ? I'm not changing it now ...
        desired.freq = Speaker.FREQUENCY
        desired.format = audio.AUDIO_S8                                         # signed 8 bits
        desired.channels = 1                                                    # mono
        desired.samples = 512

        self.device = audio.SDL_OpenAudioDevice(None, 0, desired, None, False)  # get the audio device ID
        audio.SDL_PauseAudioDevice(self.device, False)                          # unmute it


    def toggleMute(self) :
        self.muted = not self.muted


    def playSound(self) :
        """
        Plays sound

        Calculates the period elapsed since the last speaker toggle.
        If that period is too long, plays silence.
        Otherwise sends alternatively a positive or negative signal
        during that duration.
        """

        self.SPKR = 0 if self.SPKR else 1                                       # toggle speaker state
        duration = int((clock.ticks - self.previousTick) / Speaker.RATE)          # lenght of the square wave
        self.previousTick = clock.ticks

        if not self.muted :
            if duration > Speaker.BUFFER_LENGHT :
                audio.SDL_QueueAudio(self.device, self.buffer[2], Speaker.BUFFER_LENGHT)  # silence
            else :
                audio.SDL_QueueAudio(self.device, self.buffer[self.SPKR], duration)

speaker.py
ArthurFerreira2/reinette-II-plus-dot-py