/*
 * 6510dtvcore.c - Cycle based 6510 emulation core.
 *
 * Written by
 *  Ettore Perazzoli <ettore@comm2000.it>
 *  Andreas Boose <viceteam@t-online.de>
 *
 * DTV sections written by
 *  M.Kiesel <mayne@users.sourceforge.net>
 *  Hannu Nuotio <hannu.nuotio@tut.fi>
 *
 * Cycle based rewrite by
 *  Hannu Nuotio <hannu.nuotio@tut.fi>
 *
 * This file is part of VICE, the Versatile Commodore Emulator.
 * See README for copyright notice.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 *  02111-1307  USA.
 *
 */

/* This file is included by (some) CPU definition files */
/*  vic20cpu.c->mainviccpu.c
    c64sccpu.c->mainc64cpu.c
*/

#ifdef DRIVE_CPU
#define CPU_STR "Drive CPU"
#else
#define CPU_STR "Main CPU"
#endif

#include "traps.h"

#ifndef C64DTV
/* The C64DTV can use different shadow registers for accu read/write. */
/* For standard 6510, this is not the case. */
#define reg_a_write reg_a
#define reg_a_read  reg_a
#endif

/* ------------------------------------------------------------------------- */

#define LOCAL_SET_NZ(val)        (flag_z = flag_n = (val))

#define LOCAL_SET_OVERFLOW(val)   \
    do {                          \
        if (val) {                \
            reg_p |= P_OVERFLOW;  \
        } else {                  \
            reg_p &= ~P_OVERFLOW; \
        }                         \
    } while (0)

#define LOCAL_SET_BREAK(val)   \
    do {                       \
        if (val) {             \
            reg_p |= P_BREAK;  \
        } else {               \
            reg_p &= ~P_BREAK; \
        }                      \
    } while (0)

#define LOCAL_SET_DECIMAL(val)   \
    do {                         \
        if (val) {               \
            reg_p |= P_DECIMAL;  \
        } else {                 \
            reg_p &= ~P_DECIMAL; \
        }                        \
    } while (0)

#define LOCAL_SET_INTERRUPT(val)   \
    do {                           \
        if (val) {                 \
            reg_p |= P_INTERRUPT;  \
        } else {                   \
            reg_p &= ~P_INTERRUPT; \
        }                          \
    } while (0)

#define LOCAL_SET_CARRY(val)   \
    do {                       \
        if (val) {             \
            reg_p |= P_CARRY;  \
        } else {               \
            reg_p &= ~P_CARRY; \
        }                      \
    } while (0)

#define LOCAL_SET_SIGN(val)      (flag_n = (val) ? 0x80 : 0)
#define LOCAL_SET_ZERO(val)      (flag_z = !(val))
#define LOCAL_SET_STATUS(val)    (reg_p = ((val) & ~(P_ZERO | P_SIGN)), \
                                  LOCAL_SET_ZERO((val) & P_ZERO),       \
                                  flag_n = (val))

#define LOCAL_OVERFLOW()         (reg_p & P_OVERFLOW)
#define LOCAL_BREAK()            (reg_p & P_BREAK)
#define LOCAL_DECIMAL()          (reg_p & P_DECIMAL)
#define LOCAL_INTERRUPT()        (reg_p & P_INTERRUPT)
#define LOCAL_CARRY()            (reg_p & P_CARRY)
#define LOCAL_SIGN()             (flag_n & 0x80)
#define LOCAL_ZERO()             (!flag_z)
#define LOCAL_STATUS()           (reg_p | (flag_n & 0x80) | P_UNUSED    \
                                  | (LOCAL_ZERO() ? P_ZERO : 0))

#ifdef LAST_OPCODE_INFO

/* If requested, gather some info about the last executed opcode for timing
   purposes.  */

/* Remember the number of the last opcode.  By default, the opcode does not
   delay interrupt and does not change the I flag.  */
#define SET_LAST_OPCODE(x) OPINFO_SET(LAST_OPCODE_INFO, (x), 0, 0, 0)

/* Remember that the last opcode delayed a pending IRQ or NMI by one cycle.  */
#define OPCODE_DELAYS_INTERRUPT() OPINFO_SET_DELAYS_INTERRUPT(LAST_OPCODE_INFO, 1)

/* Remember that the last opcode changed the I flag from 0 to 1, so we have
   to dispatch an IRQ even if the I flag is 0 when we check it.  */
#define OPCODE_DISABLES_IRQ() OPINFO_SET_DISABLES_IRQ(LAST_OPCODE_INFO, 1)

/* Remember that the last opcode changed the I flag from 1 to 0, so we must
   not dispatch an IRQ even if the I flag is 1 when we check it.  */
#define OPCODE_ENABLES_IRQ() OPINFO_SET_ENABLES_IRQ(LAST_OPCODE_INFO, 1)

#else

/* Info about the last opcode is not needed.  */
#define SET_LAST_OPCODE(x)
#define OPCODE_DELAYS_INTERRUPT()
#define OPCODE_DISABLES_IRQ()
#define OPCODE_ENABLES_IRQ()

#endif

#ifdef LAST_OPCODE_ADDR
#define SET_LAST_ADDR(x) LAST_OPCODE_ADDR = (x)
#else
#error "please define LAST_OPCODE_ADDR"
#endif

#ifndef C64DTV
/* Export the local version of the registers.  */
#define EXPORT_REGISTERS()          \
    do {                            \
        GLOBAL_REGS.pc = reg_pc ;   \
        GLOBAL_REGS.a = reg_a_read; \
        GLOBAL_REGS.x = reg_x;      \
        GLOBAL_REGS.y = reg_y;      \
        GLOBAL_REGS.sp = reg_sp;    \
        GLOBAL_REGS.p = reg_p;      \
        GLOBAL_REGS.n = flag_n;     \
        GLOBAL_REGS.z = flag_z;     \
    } while (0)

/* Import the public version of the registers.  */
#define IMPORT_REGISTERS()                                 \
    do {                                                   \
        reg_a_write = GLOBAL_REGS.a;                       \
        reg_x = GLOBAL_REGS.x;                             \
        reg_y = GLOBAL_REGS.y;                             \
        reg_sp = GLOBAL_REGS.sp;                           \
        reg_p = GLOBAL_REGS.p;                             \
        flag_n = GLOBAL_REGS.n;                            \
        flag_z = GLOBAL_REGS.z;                            \
        bank_start = bank_limit = 0; /* prevent caching */ \
        JUMP(GLOBAL_REGS.pc);                              \
    } while (0)

#else  /* C64DTV */

/* Export the local version of the registers.  */
#define EXPORT_REGISTERS()                                           \
    do {                                                             \
        GLOBAL_REGS.pc = reg_pc;                                     \
        GLOBAL_REGS.a = dtv_registers[0];                            \
        GLOBAL_REGS.x = dtv_registers[2];                            \
        GLOBAL_REGS.y = dtv_registers[1];                            \
        GLOBAL_REGS.sp = reg_sp;                                     \
        GLOBAL_REGS.p = reg_p;                                       \
        GLOBAL_REGS.n = flag_n;                                      \
        GLOBAL_REGS.z = flag_z;                                      \
        GLOBAL_REGS.r3 = dtv_registers[3];                           \
        GLOBAL_REGS.r4 = dtv_registers[4];                           \
        GLOBAL_REGS.r5 = dtv_registers[5];                           \
        GLOBAL_REGS.r6 = dtv_registers[6];                           \
        GLOBAL_REGS.r7 = dtv_registers[7];                           \
        GLOBAL_REGS.r8 = dtv_registers[8];                           \
        GLOBAL_REGS.r9 = dtv_registers[9];                           \
        GLOBAL_REGS.r10 = dtv_registers[10];                         \
        GLOBAL_REGS.r11 = dtv_registers[11];                         \
        GLOBAL_REGS.r12 = dtv_registers[12];                         \
        GLOBAL_REGS.r13 = dtv_registers[13];                         \
        GLOBAL_REGS.r14 = dtv_registers[14];                         \
        GLOBAL_REGS.r15 = dtv_registers[15];                         \
        GLOBAL_REGS.acm = (reg_a_write_idx << 4) | (reg_a_read_idx); \
        GLOBAL_REGS.yxm = (reg_y_idx << 4) | (reg_x_idx);            \
    } while (0)

/* Import the public version of the registers.  */
#define IMPORT_REGISTERS()                                 \
    do {                                                   \
        dtv_registers[0] = GLOBAL_REGS.a;                  \
        dtv_registers[2] = GLOBAL_REGS.x;                  \
        dtv_registers[1] = GLOBAL_REGS.y;                  \
        reg_sp = GLOBAL_REGS.sp;                           \
        reg_p = GLOBAL_REGS.p;                             \
        flag_n = GLOBAL_REGS.n;                            \
        flag_z = GLOBAL_REGS.z;                            \
        dtv_registers[3] = GLOBAL_REGS.r3;                 \
        dtv_registers[4] = GLOBAL_REGS.r4;                 \
        dtv_registers[5] = GLOBAL_REGS.r5;                 \
        dtv_registers[6] = GLOBAL_REGS.r6;                 \
        dtv_registers[7] = GLOBAL_REGS.r7;                 \
        dtv_registers[8] = GLOBAL_REGS.r8;                 \
        dtv_registers[9] = GLOBAL_REGS.r9;                 \
        dtv_registers[10] = GLOBAL_REGS.r10;               \
        dtv_registers[11] = GLOBAL_REGS.r11;               \
        dtv_registers[12] = GLOBAL_REGS.r12;               \
        dtv_registers[13] = GLOBAL_REGS.r13;               \
        dtv_registers[14] = GLOBAL_REGS.r14;               \
        dtv_registers[15] = GLOBAL_REGS.r15;               \
        reg_a_write_idx = GLOBAL_REGS.acm >> 4;            \
        reg_a_read_idx = GLOBAL_REGS.acm & 0xf;            \
        reg_y_idx = GLOBAL_REGS.yxm >> 4;                  \
        reg_x_idx = GLOBAL_REGS.yxm & 0xf;                 \
        bank_start = bank_limit = 0; /* prevent caching */ \
        JUMP(GLOBAL_REGS.pc);                              \
    } while (0)

#endif /* C64DTV */

#ifdef DEBUG
#define TRACE_NMI()                         \
    do {                                    \
        if (TRACEFLG) {                     \
            debug_nmi(CPU_INT_STATUS, CLK); \
        }                                   \
    } while (0)

#define TRACE_IRQ()                         \
    do {                                    \
        if (TRACEFLG) {                     \
            debug_irq(CPU_INT_STATUS, CLK); \
        }                                   \
    } while (0)

#define TRACE_BRK()                \
    do {                           \
        if (TRACEFLG) {            \
            debug_text("*** BRK"); \
        }                          \
    } while (0)
#else
#define TRACE_NMI()
#define TRACE_IRQ()
#define TRACE_BRK()
#endif

/* Do the IRQ/BRK sequence, including NMI transformation. */
#define DO_IRQBRK()                                                                                                   \
    do {                                                                                                              \
        /* Interrupt vector to use. Assume regular IRQ/BRK. */                                                        \
        uint16_t handler_vector = 0xfffe;                                                                             \
                                                                                                                      \
        PUSH(reg_pc >> 8);                                                                                            \
        CLK_INC();                                                                                                    \
        PUSH(reg_pc & 0xff);                                                                                          \
        CLK_INC();                                                                                                    \
        PUSH(LOCAL_STATUS());                                                                                         \
        CLK_INC();                                                                                                    \
                                                                                                                      \
        /* Process alarms up to this point to get nmi_clk updated. */                                                 \
        while (CLK >= alarm_context_next_pending_clk(ALARM_CONTEXT)) {                                                \
            alarm_context_dispatch(ALARM_CONTEXT, CLK);                                                               \
        }                                                                                                             \
                                                                                                                      \
        /* If an NMI would occur at this cycle... */                                                                  \
        if ((CPU_INT_STATUS->global_pending_int & IK_NMI) && (CLK >= (CPU_INT_STATUS->nmi_clk + INTERRUPT_DELAY))) {  \
            /* Transform the IRQ/BRK into an NMI. */                                                                  \
            handler_vector = 0xfffa;                                                                                  \
            TRACE_NMI();                                                                                              \
            if (monitor_mask[CALLER] & (MI_STEP)) {                                                                   \
                monitor_check_icount_interrupt();                                                                     \
            }                                                                                                         \
            interrupt_ack_nmi(CPU_INT_STATUS);                                                                        \
        }                                                                                                             \
                                                                                                                      \
        LOCAL_SET_INTERRUPT(1);                                                                                       \
        addr = LOAD(handler_vector);                                                                                  \
        CLK_INC();                                                                                                    \
        addr |= (LOAD(handler_vector + 1) << 8);                                                                      \
        CLK_INC();                                                                                                    \
        JUMP(addr);                                                                                                   \
    } while (0)

/* Perform the interrupts in `int_kind'.  If we have both NMI and IRQ,
   execute NMI.  */
/* FIXME: LOCAL_STATUS() should check byte ready first.  */
#define DO_INTERRUPT(int_kind)                                                 \
    do {                                                                       \
        uint8_t ik = (int_kind);                                               \
        uint16_t addr;                                                         \
                                                                               \
        if (ik & (IK_IRQ | IK_IRQPEND | IK_NMI)) {                             \
            if ((ik & IK_NMI)                                                  \
                && interrupt_check_nmi_delay(CPU_INT_STATUS, CLK)) {           \
                TRACE_NMI();                                                   \
                if (monitor_mask[CALLER] & (MI_STEP)) {                        \
                    monitor_check_icount_interrupt();                          \
                }                                                              \
                interrupt_ack_nmi(CPU_INT_STATUS);                             \
                if (!SKIP_CYCLE) {                                             \
                    LOAD_DUMMY(reg_pc);     /* dummy reads */                  \
                    CLK_INC();                                                 \
                    LOAD_DUMMY(reg_pc);                                        \
                    CLK_INC();                                                 \
                }                                                              \
                LOCAL_SET_BREAK(0);                                            \
                PUSH(reg_pc >> 8);                                             \
                CLK_INC();                                                     \
                PUSH(reg_pc & 0xff);                                           \
                CLK_INC();                                                     \
                PUSH(LOCAL_STATUS());                                          \
                CLK_INC();                                                     \
                addr = LOAD(0xfffa);                                           \
                CLK_INC();                                                     \
                addr |= (LOAD(0xfffb) << 8);                                   \
                CLK_INC();                                                     \
                LOCAL_SET_INTERRUPT(1);                                        \
                JUMP(addr);                                                    \
                SET_LAST_OPCODE(0);                                            \
            } else if ((ik & (IK_IRQ | IK_IRQPEND))                            \
                     && (!LOCAL_INTERRUPT()                                    \
                         || OPINFO_DISABLES_IRQ(LAST_OPCODE_INFO))             \
                     && interrupt_check_irq_delay(CPU_INT_STATUS, CLK)) {      \
                TRACE_IRQ();                                                   \
                if (monitor_mask[CALLER] & (MI_STEP)) {                        \
                    monitor_check_icount_interrupt();                          \
                }                                                              \
                interrupt_ack_irq(CPU_INT_STATUS);                             \
                if (!SKIP_CYCLE) {                                             \
                    LOAD_DUMMY(reg_pc);     /* dummy reads */                  \
                    CLK_INC();                                                 \
                    LOAD_DUMMY(reg_pc);                                        \
                    CLK_INC();                                                 \
                }                                                              \
                LOCAL_SET_BREAK(0);                                            \
                DO_IRQBRK();                                                   \
                SET_LAST_OPCODE(0);                                            \
            }                                                                  \
        }                                                                      \
        if (ik & (IK_TRAP | IK_RESET)) {                                       \
            if (ik & IK_TRAP) {                                                \
                EXPORT_REGISTERS();                                            \
                interrupt_do_trap(CPU_INT_STATUS, (uint16_t)reg_pc);           \
                IMPORT_REGISTERS();                                            \
                if (CPU_INT_STATUS->global_pending_int & IK_RESET) {           \
                    ik |= IK_RESET;                                            \
                }                                                              \
            }                                                                  \
            if (ik & IK_RESET) {                                               \
                interrupt_ack_reset(CPU_INT_STATUS);                           \
                cpu_reset();                                                   \
                addr = LOAD(0xfffc);                                           \
                addr |= (LOAD(0xfffd) << 8);                                   \
                bank_start = bank_limit = 0; /* prevent caching */             \
                LOCAL_SET_INTERRUPT(1);                                        \
                cpu_is_jammed = 0;                                             \
                JUMP(addr);                                                    \
                DMA_ON_RESET;                                                  \
            }                                                                  \
        }                                                                      \
        if (ik & (IK_MONITOR | IK_DMA)) {                                      \
            if (ik & IK_MONITOR) {                                             \
                if (monitor_force_import(CALLER)) {                            \
                    IMPORT_REGISTERS();                                        \
                }                                                              \
                if (monitor_mask[CALLER]) {                                    \
                    EXPORT_REGISTERS();                                        \
                }                                                              \
                if (monitor_mask[CALLER] & (MI_STEP)) {                        \
                    monitor_check_icount((uint16_t)reg_pc);                    \
                    IMPORT_REGISTERS();                                        \
                }                                                              \
                if (monitor_mask[CALLER] & (MI_BREAK)) {                       \
                    if (monitor_check_breakpoints(CALLER, (uint16_t)reg_pc)) { \
                        monitor_startup(CALLER);                               \
                        IMPORT_REGISTERS();                                    \
                    }                                                          \
                }                                                              \
                if (monitor_mask[CALLER] & (MI_WATCH)) {                       \
                    monitor_check_watchpoints(LAST_OPCODE_ADDR, (uint16_t)reg_pc); \
                    IMPORT_REGISTERS();                                        \
                }                                                              \
            }                                                                  \
            if (ik & IK_DMA) {                                                 \
                EXPORT_REGISTERS();                                            \
                DMA_FUNC;                                                      \
                interrupt_ack_dma(CPU_INT_STATUS);                             \
                IMPORT_REGISTERS();                                            \
            }                                                                  \
        }                                                                      \
    } while (0)

/* ------------------------------------------------------------------------- */

/* Addressing modes.  For convenience, page boundary crossing cycles and
   ``idle'' memory reads are handled here as well. */

#define GET_TEMP(dest) dest = new_value;

#define GET_IMM(dest) dest = (uint8_t)(p1);
/* same as above, for NOOP */
#define GET_IMM_DUMMY()

#define GET_ABS(dest)           \
    dest = (uint8_t)(LOAD(p2)); \
    CLK_INC();

/* same as above, for NOOP */
#define GET_ABS_DUMMY()      \
    LOAD_DUMMY(p2);          \
    CLK_INC();

#define SET_ABS(value) \
    STORE(p2, value);  \
    CLK_INC();

#define SET_ABS_RMW(old_value, new_value) \
    if (!SKIP_CYCLE) {                    \
        STORE_DUMMY(p2, old_value);       \
        CLK_INC();                        \
    }                                     \
    STORE(p2, new_value);                 \
    CLK_INC();

#define INT_ABS_I_R(reg_i)                                     \
    if (!SKIP_CYCLE && ((((p2) & 0xff) + reg_i) > 0xff)) {     \
        LOAD_DUMMY((((p2) + reg_i) & 0xff) | ((p2) & 0xff00)); \
        CLK_INC();                                             \
    }

#define INT_ABS_I_W(reg_i)                                     \
    if (!SKIP_CYCLE) {                                         \
        LOAD_DUMMY((((p2) + reg_i) & 0xff) | ((p2) & 0xff00)); \
        CLK_INC();                                             \
    }

#define GET_ABS_X(dest)        \
    INT_ABS_I_R(reg_x)         \
    dest = LOAD((p2) + reg_x); \
    CLK_INC();
/* same as above, for NOOP */
#define GET_ABS_X_DUMMY()      \
    INT_ABS_I_R(reg_x)         \
    LOAD_DUMMY((p2) + reg_x);  \
    CLK_INC();

#define GET_ABS_Y(dest)        \
    INT_ABS_I_R(reg_y)         \
    dest = LOAD((p2) + reg_y); \
    CLK_INC();

#define GET_ABS_X_RMW(dest)    \
    INT_ABS_I_W(reg_x)         \
    dest = LOAD((p2) + reg_x); \
    CLK_INC();

#define GET_ABS_Y_RMW(dest)    \
    INT_ABS_I_W(reg_y)         \
    dest = LOAD((p2) + reg_y); \
    CLK_INC();

#define SET_ABS_X(value)      \
    INT_ABS_I_W(reg_x)        \
    STORE(p2 + reg_x, value); \
    CLK_INC();

#define SET_ABS_Y(value)      \
    INT_ABS_I_W(reg_y)        \
    STORE(p2 + reg_y, value); \
    CLK_INC();

#define SET_ABS_I_RMW(reg_i, old_value, new_value) \
    if (!SKIP_CYCLE) {                             \
        STORE_DUMMY(p2 + reg_i, old_value);        \
        CLK_INC();                                 \
    }                                              \
    STORE(p2 + reg_i, new_value);                  \
    CLK_INC();

#define SET_ABS_X_RMW(old_value, new_value) SET_ABS_I_RMW(reg_x, old_value, new_value)

#define SET_ABS_Y_RMW(old_value, new_value) SET_ABS_I_RMW(reg_y, old_value, new_value)

#define GET_ZERO(dest)    \
    dest = LOAD_ZERO(p1); \
    CLK_INC();

/* same as above, for NOOP */
#define GET_ZERO_DUMMY() \
    LOAD_ZERO_DUMMY(p1); \
    CLK_INC();

#define SET_ZERO(value)    \
    STORE_ZERO(p1, value); \
    CLK_INC();

#define SET_ZERO_RMW(old_value, new_value) \
    if (!SKIP_CYCLE) {                     \
        STORE_ZERO_DUMMY(p1, old_value);   \
        CLK_INC();                         \
    }                                      \
    STORE_ZERO(p1, new_value);             \
    CLK_INC();

#define INT_ZERO_I            \
    if (!SKIP_CYCLE) {        \
        LOAD_ZERO_DUMMY(p1);  \
        CLK_INC();            \
    }

/* load zp, x */    
#define GET_ZERO_X(dest)          \
    INT_ZERO_I                    \
    dest = LOAD_ZERO(p1 + reg_x); \
    CLK_INC();
/* same as above, for NOOP */
#define GET_ZERO_X_DUMMY()        \
    INT_ZERO_I                    \
    LOAD_ZERO_DUMMY(p1 + reg_x);  \
    CLK_INC();

#define GET_ZERO_Y(dest)          \
    INT_ZERO_I                    \
    dest = LOAD_ZERO(p1 + reg_y); \
    CLK_INC();

#define SET_ZERO_X(value)          \
    INT_ZERO_I                     \
    STORE_ZERO(p1 + reg_x, value); \
    CLK_INC();

#define SET_ZERO_Y(value)          \
    INT_ZERO_I                     \
    STORE_ZERO(p1 + reg_y, value); \
    CLK_INC();

#define SET_ZERO_I_RMW(reg_i, old_value, new_value) \
    if (!SKIP_CYCLE) {                              \
        STORE_ZERO_DUMMY(p1 + reg_i, old_value);    \
        CLK_INC();                                  \
    }                                               \
    STORE_ZERO(p1 + reg_i, new_value);              \
    CLK_INC();

#define SET_ZERO_X_RMW(old_value, new_value) SET_ZERO_I_RMW(reg_x, old_value, new_value)

#define SET_ZERO_Y_RMW(old_value, new_value) SET_ZERO_I_RMW(reg_y, old_value, new_value)

#define INT_IND_X                   \
    unsigned int tmpa, addr;        \
    LOAD_ZERO_DUMMY(p1);            \
    CLK_INC();                      \
    tmpa = (p1 + reg_x) & 0xff;     \
    addr = LOAD_ZERO(tmpa);         \
    CLK_INC();                      \
    tmpa = (tmpa + 1) & 0xff;       \
    addr |= (LOAD_ZERO(tmpa) << 8); \
    CLK_INC();

/* load (zp, x) */
#define GET_IND_X(dest) \
    INT_IND_X           \
    dest = LOAD(addr);  \
    CLK_INC();

#define SET_IND_X(value)    \
    {                       \
        INT_IND_X           \
        STORE(addr, value); \
        CLK_INC();          \
    }

#define INT_IND_Y_R()                                          \
    unsigned int tmpa, addr;                                   \
    tmpa = LOAD_ZERO(p1);                                      \
    CLK_INC();                                                 \
    tmpa |= (LOAD_ZERO(p1 + 1) << 8);                          \
    CLK_INC();                                                 \
    if (!SKIP_CYCLE && ((((tmpa) & 0xff) + reg_y) > 0xff)) {   \
        LOAD_DUMMY((tmpa & 0xff00) | ((tmpa + reg_y) & 0xff)); \
        CLK_INC();                                             \
    }                                                          \
    addr = (tmpa + reg_y) & 0xffff;                            \

#define INT_IND_Y_W()                                          \
    unsigned int tmpa, addr;                                   \
    tmpa = LOAD_ZERO(p1);                                      \
    CLK_INC();                                                 \
    tmpa |= (LOAD_ZERO(p1 + 1) << 8);                          \
    CLK_INC();                                                 \
    if (!SKIP_CYCLE) {                                         \
        LOAD_DUMMY((tmpa & 0xff00) | ((tmpa + reg_y) & 0xff)); \
        CLK_INC();                                             \
    }                                                          \
    addr = (tmpa + reg_y) & 0xffff;
/* like above, for SHA_IND_Y */
#define INT_IND_Y_W_NOADDR()                                          \
    unsigned int tmpa;                                                \
    tmpa = LOAD_ZERO(p1);                                             \
    CLK_INC();                                                        \
    tmpa |= (LOAD_ZERO(p1 + 1) << 8);                                 \
    CLK_INC();                                                        \
    if (!SKIP_CYCLE) {                                                \
        LOAD_CHECK_BA_LOW((tmpa & 0xff00) | ((tmpa + reg_y) & 0xff)); \
        CLK_INC();                                                    \
    }

/* load (zp),y */
#define GET_IND_Y(dest) \
    INT_IND_Y_R()       \
    dest = LOAD(addr);  \
    CLK_INC();

#define GET_IND_Y_RMW(dest) \
    INT_IND_Y_W()           \
    dest = LOAD(addr);      \
    CLK_INC();

#define SET_IND_Y(value)    \
    {                       \
        INT_IND_Y_W()       \
        STORE(addr, value); \
        CLK_INC();          \
    }

#define SET_IND_RMW(old_value, new_value) \
    if (!SKIP_CYCLE) {                    \
        STORE_DUMMY(addr, old_value);     \
        CLK_INC();                        \
    }                                     \
    STORE(addr, new_value);               \
    CLK_INC();

#define SET_ABS_SH_I(addr, reg_and, reg_i)                      \
    do {                                                        \
        unsigned int tmp2, tmp3, value;                         \
                                                                \
        tmp3 = reg_and & (((addr) >> 8) + 1);                   \
        /* Set by main cpu to signal steal after above fetch */ \
        if (OPINFO_ENABLES_IRQ(LAST_OPCODE_INFO)) {             \
            /* Remove the signal */                             \
            LAST_OPCODE_INFO &= ~OPINFO_ENABLES_IRQ_MSK;        \
            /* Value is not ANDed */                            \
            value = reg_and;                                    \
        } else {                                                \
            value = tmp3;                                       \
        }                                                       \
        tmp2 = addr + reg_i;                                    \
        if ((((addr) & 0xff) + reg_i) > 0xff) {                 \
            tmp2 = (tmp2 & 0xff) | ((tmp3) << 8);               \
        }                                                       \
        STORE(tmp2, (value));                                   \
        CLK_INC();                                              \
    } while (0)

#define INC_PC(value) (reg_pc += (value))

/* ------------------------------------------------------------------------- */

/* Opcodes.  */

/*
   A couple of caveats about PC:

   - the VIC-II emulation requires PC to be incremented before the first
     write access (this is not (very) important when writing to the zero
     page);

   - `p0', `p1' and `p2' can only be used *before* incrementing PC: some
     machines (eg. the C128) might depend on this.
*/

#define ADC(get_func, pc_inc)                                                                      \
    do {                                                                                           \
        unsigned int tmp_value;                                                                    \
        unsigned int tmp;                                                                          \
                                                                                                   \
        get_func(tmp_value);                                                                       \
                                                                                                   \
        if (LOCAL_DECIMAL()) {                                                                     \
            tmp = (reg_a_read & 0xf) + (tmp_value & 0xf) + (reg_p & 0x1);                          \
            if (tmp > 0x9) {                                                                       \
                tmp += 0x6;                                                                        \
            }                                                                                      \
            if (tmp <= 0x0f) {                                                                     \
                tmp = (tmp & 0xf) + (reg_a_read & 0xf0) + (tmp_value & 0xf0);                      \
            } else {                                                                               \
                tmp = (tmp & 0xf) + (reg_a_read & 0xf0) + (tmp_value & 0xf0) + 0x10;               \
            }                                                                                      \
            LOCAL_SET_ZERO(!((reg_a_read + tmp_value + (reg_p & 0x1)) & 0xff));                    \
            LOCAL_SET_SIGN(tmp & 0x80);                                                            \
            LOCAL_SET_OVERFLOW(((reg_a_read ^ tmp) & 0x80) && !((reg_a_read ^ tmp_value) & 0x80)); \
            if ((tmp & 0x1f0) > 0x90) {                                                            \
                tmp += 0x60;                                                                       \
            }                                                                                      \
            LOCAL_SET_CARRY((tmp & 0xff0) > 0xf0);                                                 \
        } else {                                                                                   \
            tmp = tmp_value + reg_a_read + (reg_p & P_CARRY);                                      \
            LOCAL_SET_NZ(tmp & 0xff);                                                              \
            LOCAL_SET_OVERFLOW(!((reg_a_read ^ tmp_value) & 0x80) && ((reg_a_read ^ tmp) & 0x80)); \
            LOCAL_SET_CARRY(tmp > 0xff);                                                           \
        }                                                                                          \
        reg_a_write = tmp;                                                                         \
        INC_PC(pc_inc);                                                                            \
    } while (0)

#define ANC()                                  \
    do {                                       \
        reg_a_write = (uint8_t)(reg_a_read & p1); \
        LOCAL_SET_NZ(reg_a_read);              \
        LOCAL_SET_CARRY(LOCAL_SIGN());         \
        INC_PC(2);                             \
    } while (0)

#define AND(get_func, pc_inc)                     \
    do {                                          \
        unsigned int value;                       \
        get_func(value)                           \
        reg_a_write = (uint8_t)(reg_a_read & value); \
        LOCAL_SET_NZ(reg_a_read);                 \
        INC_PC(pc_inc);                           \
    } while (0)

/*
The result of the ANE opcode is A = ((A | CONST) & X & IMM), with CONST apparently
being both chip- and temperature dependent. There is also a dependency on the RDY
line, ie somehow bit4 and bit0 are affected in the cycle when a DMA starts.

The commonly used value for CONST in various documents is 0xee, which is however
not to be taken for granted (as it is unstable). see here:
http://visual6502.org/wiki/index.php?title=6502_Opcode_8B_(XAA,_ANE)

as seen in the list, there are several possible values, and its origin is still
kinda unknown. instead of the commonly used 0xee we use 0xef here, since this
appears to work with all known occurances of this opcode in real code:

known occurances of this opcode in actual code are:

- spectipede (original tape), use of ANE is unstable. bits 7,6,5,0 MUST be set
  in the magic constant (that makes it not work with the common 0xee, but 0xef
  works)
- turrican 3 (by smash designs), use of ANE is unstable. bits 6,1,0 MUST be set
  in the magic constant (that makes it not work with the common 0xee, but 0xef
  works)
- the ocean/imagine tape loader (yie ar kung fu, rambo first blood part ii,
  comic bakery), use of ANE is stable.

also see here:

https://sourceforge.net/tracker/?func=detail&aid=2110948&group_id=223021&atid=1057617
  
FIXME: in the unlikely event that other code surfaces that depends on another
CONST value, it probably has to be made configureable somehow if no value can
be found that works for both.

FIXME: perhaps we really have to add some randomness to (some) bits
*/

#define ANE_MAGIC       0xef
#define ANE_RDY_MAGIC   0xee

/* FIXME: perhaps we should make the log level a user setting */
#if 1
static int ane_log_level = 1; /* 0: none, 1: unstable only 2: all */

#define ANE_LOGGING(rdy)                                                                    \
    do {                                                                                    \
        unsigned int result = ((reg_a_read | (rdy ? ANE_RDY_MAGIC : ANE_MAGIC)) & reg_x & p1); \
        unsigned int unstablebits = ((reg_a_read ^ 0xff) & (p1 & reg_x));                   \
        if ((ane_log_level == 2) || ((ane_log_level == 1) && (unstablebits != 0))) {        \
            if (unstablebits == 0) {                                                        \
                log_warning(LOG_DEFAULT, "%04x ANE #$%02x ; A=$%02x X=$%02x -> A=$%02x%s",  \
                    reg_pc, p1, reg_a_read, reg_x, result, rdy ? " (RDY cycle)" : "");      \
            } else {                                                                        \
                log_warning(LOG_DEFAULT, "%04x ANE #$%02x ; A=$%02x X=$%02x -> A=$%02x (unstable bits: %c%c%c%c%c%c%c%c)%s", \
                    reg_pc, p1, reg_a_read, reg_x, result,                                  \
                    unstablebits & 0x80 ? '*' : '.', unstablebits & 0x40 ? '*' : '.',       \
                    unstablebits & 0x20 ? '*' : '.', unstablebits & 0x10 ? '*' : '.',       \
                    unstablebits & 0x08 ? '*' : '.', unstablebits & 0x04 ? '*' : '.',       \
                    unstablebits & 0x02 ? '*' : '.', unstablebits & 0x01 ? '*' : '.',       \
                    rdy ? " (RDY cycle)" : ""                                               \
                    );                                                                      \
            }                                                                               \
        }                                                                                   \
    } while (0)
#else
#define ANE_LOGGING(rdy)
#endif
    
#define ANE()                                                       \
    do {                                                            \
        /* Set by main-cpu to signal steal after first fetch */     \
        if (OPINFO_ENABLES_IRQ(LAST_OPCODE_INFO)) {                 \
            /* Remove the signal */                                 \
            LAST_OPCODE_INFO &= ~OPINFO_ENABLES_IRQ_MSK;            \
            /* TODO: the real behaviour is more complex */          \
            ANE_LOGGING(1);                                         \
            reg_a_write = (uint8_t)((reg_a_read | ANE_RDY_MAGIC) & reg_x & p1); \
        } else {                                                    \
            ANE_LOGGING(0);                                         \
            reg_a_write = (uint8_t)((reg_a_read | ANE_MAGIC) & reg_x & p1); \
        }                                                           \
        LOCAL_SET_NZ(reg_a_read);                                   \
        INC_PC(2);                                                  \
        /* Pretend to be NOP #$nn to not trigger the special case   \
           when cycles are stolen after the second fetch */         \
        SET_LAST_OPCODE(0x80);                                      \
    } while (0)

/* The fanciest opcode ever... ARR! */
#define ARR()                                                       \
    do {                                                            \
        unsigned int tmp;                                           \
                                                                    \
        tmp = reg_a_read & (p1);                                    \
        if (LOCAL_DECIMAL()) {                                      \
            int tmp_2 = tmp;                                        \
            tmp_2 |= (reg_p & P_CARRY) << 8;                        \
            tmp_2 >>= 1;                                            \
            LOCAL_SET_SIGN(LOCAL_CARRY());                          \
            LOCAL_SET_ZERO(!tmp_2);                                 \
            LOCAL_SET_OVERFLOW((tmp_2 ^ tmp) & 0x40);               \
            if (((tmp & 0xf) + (tmp & 0x1)) > 0x5) {                \
                tmp_2 = (tmp_2 & 0xf0) | ((tmp_2 + 0x6) & 0xf);     \
            }                                                       \
            if (((tmp & 0xf0) + (tmp & 0x10)) > 0x50) {             \
                tmp_2 = (tmp_2 & 0x0f) | ((tmp_2 + 0x60) & 0xf0);   \
                LOCAL_SET_CARRY(1);                                 \
            } else {                                                \
                LOCAL_SET_CARRY(0);                                 \
            }                                                       \
            reg_a_write = tmp_2;                                    \
        } else {                                                    \
            tmp |= (reg_p & P_CARRY) << 8;                          \
            tmp >>= 1;                                              \
            LOCAL_SET_NZ(tmp);                                      \
            LOCAL_SET_CARRY(tmp & 0x40);                            \
            LOCAL_SET_OVERFLOW((tmp & 0x40) ^ ((tmp & 0x20) << 1)); \
            reg_a_write = tmp;                                      \
        }                                                           \
        INC_PC(2);                                                  \
    } while (0)

#define ASL(pc_inc, get_func, set_func)      \
    do {                                     \
        unsigned int old_value, new_value;   \
        get_func(old_value)                  \
        LOCAL_SET_CARRY(old_value & 0x80);   \
        new_value = (old_value << 1) & 0xff; \
        LOCAL_SET_NZ(new_value);             \
        INC_PC(pc_inc);                      \
        set_func(old_value, new_value);      \
    } while (0)

#define ASL_A()                             \
    do {                                    \
        LOCAL_SET_CARRY(reg_a_read & 0x80); \
        reg_a_write = reg_a_read << 1;      \
        LOCAL_SET_NZ(reg_a_read);           \
        INC_PC(1);                          \
    } while (0)

#define ASR()                        \
    do {                             \
        unsigned int tmp;            \
                                     \
        tmp = reg_a_read & (p1);     \
        LOCAL_SET_CARRY(tmp & 0x01); \
        reg_a_write = tmp >> 1;      \
        LOCAL_SET_NZ(reg_a_read);    \
        INC_PC(2);                   \
    } while (0)

#define BIT(get_func, pc_inc)                \
    do {                                     \
        unsigned int tmp;                    \
        get_func(tmp)                        \
        LOCAL_SET_SIGN(tmp & 0x80);          \
        LOCAL_SET_OVERFLOW(tmp & 0x40);      \
        LOCAL_SET_ZERO(!(tmp & reg_a_read)); \
        INC_PC(pc_inc);                      \
    } while (0)

#ifdef C64DTV

#define BRANCH(cond)                                              \
    do {                                                          \
        INC_PC(2);                                                \
                                                                  \
        if (cond) {                                               \
            unsigned int dest_addr;                               \
                                                                  \
            burst_broken = 1;                                     \
            dest_addr = reg_pc + (signed char)(p1);               \
                                                                  \
            if (!SKIP_CYCLE) {                                    \
                LOAD(reg_pc);                                     \
                CLK_INC();                                        \
                if ((reg_pc ^ dest_addr) & 0xff00) {              \
                    LOAD((reg_pc & 0xff00) | (dest_addr & 0xff)); \
                    CLK_INC();                                    \
                } else {                                          \
                    OPCODE_DELAYS_INTERRUPT();                    \
                }                                                 \
            }                                                     \
            JUMP(dest_addr & 0xffff);                             \
        }                                                         \
    } while (0)

#else /* !C64DTV */

#define BRANCH(cond)                                          \
    do {                                                      \
        INC_PC(2);                                            \
                                                              \
        if (cond) {                                           \
            unsigned int dest_addr;                           \
                                                              \
            dest_addr = reg_pc + (signed char)(p1);           \
                                                              \
            LOAD_DUMMY(reg_pc);                               \
            CLK_INC();                                        \
            if ((reg_pc ^ dest_addr) & 0xff00) {              \
                LOAD_DUMMY((reg_pc & 0xff00) | (dest_addr & 0xff)); \
                CLK_INC();                                    \
            } else {                                          \
                OPCODE_DELAYS_INTERRUPT();                    \
            }                                                 \
            JUMP(dest_addr & 0xffff);                         \
        }                                                     \
    } while (0)

#endif

#define BRK() \
    do { \
        uint16_t addr;          \
        EXPORT_REGISTERS(); \
        TRACE_BRK();        \
        INC_PC(2);          \
        LOCAL_SET_BREAK(1); \
        DO_IRQBRK();        \
    } while (0)

/* The JAM (0x02) opcode is also used to patch the ROM.  The function trap_handler()
   returns nonzero if this is not a patch, but a `real' JAM instruction. */

#define JAM_02()                                                                      \
    do {                                                                              \
        uint32_t trap_result;                                                            \
        EXPORT_REGISTERS();                                                           \
        if (!ROM_TRAP_ALLOWED() || (trap_result = ROM_TRAP_HANDLER()) == (uint32_t)-1) { \
            cpu_is_jammed = 1;                                                        \
            REWIND_FETCH_OPCODE(CLK);                                                 \
            JAM();                                                                    \
        } else {                                                                      \
            if (trap_result) {                                                        \
                REWIND_FETCH_OPCODE(CLK);                                             \
                SET_OPCODE(trap_result);                                              \
                IMPORT_REGISTERS();                                                   \
                goto trap_skipped;                                                    \
            } else {                                                                  \
                IMPORT_REGISTERS();                                                   \
            }                                                                         \
        }                                                                             \
    } while (0)

#define CLC()               \
    do {                    \
        INC_PC(1);          \
        LOCAL_SET_CARRY(0); \
    } while (0)

#define CLD()                 \
    do {                      \
        INC_PC(1);            \
        LOCAL_SET_DECIMAL(0); \
    } while (0)

#ifdef OPCODE_UPDATE_IN_FETCH

#define CLI()                                             \
    do {                                                  \
        INC_PC(1);                                        \
        /* Set by main-cpu to signal steal during CLI */  \
        if (!OPINFO_ENABLES_IRQ(LAST_OPCODE_INFO)) {      \
            if (LOCAL_INTERRUPT()) {                      \
                OPCODE_ENABLES_IRQ();                     \
            }                                             \
        } else {                                          \
            /* Remove the signal and the related delay */ \
            LAST_OPCODE_INFO &= ~OPINFO_ENABLES_IRQ_MSK;  \
        }                                                 \
        LOCAL_SET_INTERRUPT(0);                           \
    } while (0)

#else /* !OPCODE_UPDATE_IN_FETCH */

#define CLI()                     \
    do {                          \
        INC_PC(1);                \
        if (LOCAL_INTERRUPT()) {  \
            OPCODE_ENABLES_IRQ(); \
        }                         \
        LOCAL_SET_INTERRUPT(0);   \
    } while (0)

#endif

#define CLV()                  \
    do {                       \
        INC_PC(1);             \
        LOCAL_SET_OVERFLOW(0); \
    } while (0)

#define CP(reg, get_func, pc_inc)     \
    do {                              \
        unsigned int tmp;             \
        uint8_t value;                   \
        get_func(value)               \
        tmp = reg - value;            \
        LOCAL_SET_CARRY(tmp < 0x100); \
        LOCAL_SET_NZ(tmp & 0xff);     \
        INC_PC(pc_inc);               \
    } while (0)

#define DCP(pc_inc, get_func, set_func)           \
    do {                                          \
        unsigned int old_value, new_value;        \
        get_func(old_value)                       \
        new_value = (old_value - 1) & 0xff;       \
        LOCAL_SET_CARRY(reg_a_read >= new_value); \
        LOCAL_SET_NZ((reg_a_read - new_value));   \
        INC_PC(pc_inc);                           \
        set_func(old_value, new_value)            \
    } while (0)

#define DEC(pc_inc, get_func, set_func)     \
    do {                                    \
        unsigned int old_value, new_value;  \
        get_func(old_value)                 \
        new_value = (old_value - 1) & 0xff; \
        LOCAL_SET_NZ(new_value);            \
        INC_PC(pc_inc);                     \
        set_func(old_value, new_value)      \
    } while (0)

#define DEX()                \
    do {                     \
        reg_x--;             \
        LOCAL_SET_NZ(reg_x); \
        INC_PC(1);           \
    } while (0)

#define DEY()                \
    do {                     \
        reg_y--;             \
        LOCAL_SET_NZ(reg_y); \
        INC_PC(1);           \
    } while (0)

#define EOR(get_func, pc_inc)                       \
    do {                                            \
        unsigned int value;                         \
        get_func(value)                             \
        reg_a_write = (uint8_t)(reg_a_read ^ (value)); \
        LOCAL_SET_NZ(reg_a_read);                   \
        INC_PC(pc_inc);                             \
    } while (0)

#define INC(pc_inc, get_func, set_func)     \
    do {                                    \
        unsigned int old_value, new_value;  \
        get_func(old_value)                 \
        new_value = (old_value + 1) & 0xff; \
        LOCAL_SET_NZ(new_value);            \
        INC_PC(pc_inc);                     \
        set_func(old_value, new_value)      \
    } while (0)

#define INX()                \
    do {                     \
        reg_x++;             \
        LOCAL_SET_NZ(reg_x); \
        INC_PC(1);           \
    } while (0)

#define INY()                \
    do {                     \
        reg_y++;             \
        LOCAL_SET_NZ(reg_y); \
        INC_PC(1);           \
    } while (0)

#define ISB(pc_inc, get_func, set_func)     \
    do {                                    \
        unsigned int old_value, new_value;  \
        get_func(old_value)                 \
        new_value = (old_value + 1) & 0xff; \
        SBC(GET_TEMP, 0);                   \
        INC_PC(pc_inc);                     \
        set_func(old_value, new_value)      \
    } while (0)

#define JMP(addr)   \
    do {            \
        JUMP(addr); \
    } while (0)

#define JMP_IND()                                                    \
    do {                                                             \
        uint16_t dest_addr;                                              \
        dest_addr = LOAD(p2);                                        \
        CLK_INC();                                                   \
        dest_addr |= (LOAD((p2 & 0xff00) | ((p2 + 1) & 0xff)) << 8); \
        CLK_INC();                                                   \
        JUMP(dest_addr);                                             \
    } while (0)

/* HACK: fix JSR MSB in monitor CPU history */
#ifdef FEATURE_CPUMEMHISTORY
#define JSR_FIXUP_MSB(x)    monitor_cpuhistory_fix_p2(x)
#else
#define JSR_FIXUP_MSB(x)
#endif

#define JSR()                                     \
    do {                                          \
        uint8_t addr_msb;                         \
        uint16_t dest_addr;                       \
        if (!SKIP_CYCLE) {                        \
            STACK_PEEK();                         \
            CLK_INC();                            \
        }                                         \
        INC_PC(2);                                \
        PUSH(((reg_pc) >> 8) & 0xff);             \
        CLK_INC();                                \
        PUSH((reg_pc) & 0xff);                    \
        CLK_INC();                                \
        addr_msb = LOAD(reg_pc);                  \
        JSR_FIXUP_MSB(addr_msb);                  \
        dest_addr = (uint16_t)(p1 | (addr_msb << 8)); \
        CLK_INC();                                \
        JUMP(dest_addr);                          \
    } while (0)

#define LAS()                                          \
    do {                                               \
        unsigned int value;                            \
        GET_ABS_Y(value)                               \
        reg_a_write = reg_x = reg_sp = reg_sp & value; \
        LOCAL_SET_NZ(reg_a_read);                      \
        INC_PC(3);                                     \
    } while (0)

#define LAX(get_func, pc_inc)     \
    do {                          \
        get_func(reg_x);          \
        reg_a_write = reg_x;      \
        LOCAL_SET_NZ(reg_a_read); \
        INC_PC(pc_inc);           \
    } while (0)

#define LD(dest, get_func, pc_inc) \
    do {                           \
        get_func(dest);            \
        LOCAL_SET_NZ(dest);        \
        INC_PC(pc_inc);            \
    } while (0)

#define LSR(pc_inc, get_func, set_func)    \
    do {                                   \
        unsigned int old_value, new_value; \
        get_func(old_value)                \
        LOCAL_SET_CARRY(old_value & 0x01); \
        new_value = old_value >> 1;        \
        LOCAL_SET_NZ(new_value);           \
        INC_PC(pc_inc);                    \
        set_func(old_value, new_value)     \
    } while (0)

#define LSR_A()                             \
    do {                                    \
        LOCAL_SET_CARRY(reg_a_read & 0x01); \
        reg_a_write = reg_a_read >> 1;      \
        LOCAL_SET_NZ(reg_a_read);           \
        INC_PC(1);                          \
    } while (0)

/*
The result of the LXA opcode is A = X = ((A | CONST) & IMM), with CONST apparently
being both chip- and temperature dependent. There is also a dependency on the RDY
line, ie somehow bit4 and bit0 are affected in the cycle when a DMA starts.

The commonly used value for CONST in various documents is 0xee, which is however
not to be taken for granted (as it is unstable).

FIXME: in the unlikely event that other code surfaces that depends on another
CONST value, it probably has to be made configureable somehow if no value can
be found that works for both.

FIXME: perhaps we really have to add some randomness to (some) bits
*/

#define LXA_MAGIC       0xee    /* needs to be 0xee for wizball */
#define LXA_RDY_MAGIC   0xee

/* FIXME: perhaps we should make the log level a user setting */
#if 1
static int lxa_log_level = 1; /* 0: none, 1: unstable only 2: all */

#define LXA_LOGGING(rdy)                                                                    \
    do {                                                                                    \
        unsigned int result = (reg_a_read | (rdy ? LXA_RDY_MAGIC : LXA_MAGIC)) & p1;        \
        unsigned int unstablebits = (reg_a_read ^ 0xff) & p1;                               \
        if ((lxa_log_level == 2) || ((lxa_log_level == 1) && (unstablebits != 0))) {        \
            if (unstablebits == 0) {                                                        \
                log_warning(LOG_DEFAULT, "%04x LAX #$%02x ; A=$%02x -> A=X=$%02x%s",        \
                    reg_pc, p1, reg_a_read, result, rdy ? " (RDY cycle)" : "");             \
            } else {                                                                        \
                log_warning(LOG_DEFAULT, "%04x LAX #$%02x ; A=$%02x -> A=X=$%02x (unstable bits: %c%c%c%c%c%c%c%c)%s", \
                    reg_pc, p1, reg_a_read, result,                                         \
                    unstablebits & 0x80 ? '*' : '.', unstablebits & 0x40 ? '*' : '.',       \
                    unstablebits & 0x20 ? '*' : '.', unstablebits & 0x10 ? '*' : '.',       \
                    unstablebits & 0x08 ? '*' : '.', unstablebits & 0x04 ? '*' : '.',       \
                    unstablebits & 0x02 ? '*' : '.', unstablebits & 0x01 ? '*' : '.',       \
                    rdy ? " (RDY cycle)" : ""                                               \
                    );                                                                      \
            }                                                                               \
        }                                                                                   \
    } while (0)
#else
#define LXA_LOGGING(rdy)
#endif

#define LXA()                                                       \
    do {                                                            \
        /* Set by main-cpu to signal steal after first fetch */     \
        if (OPINFO_ENABLES_IRQ(LAST_OPCODE_INFO)) {                 \
            /* Remove the signal */                                 \
            LAST_OPCODE_INFO &= ~OPINFO_ENABLES_IRQ_MSK;            \
            /* TODO: the real behaviour is more complex */          \
            LXA_LOGGING(1);                                         \
            reg_a_write = reg_x = (uint8_t)((reg_a_read | LXA_RDY_MAGIC) & p1); \
        } else {                                                    \
            LXA_LOGGING(0);                                         \
            reg_a_write = reg_x = (uint8_t)((reg_a_read | LXA_MAGIC) & p1); \
        }                                                           \
        LOCAL_SET_NZ(reg_a_read);                                   \
        INC_PC(2);                                                  \
        /* Pretend to be NOP #$nn to not trigger the special case   \
           when cycles are stolen after the second fetch */         \
        SET_LAST_OPCODE(0x80);                                      \
    } while (0)

#define ORA(get_func, pc_inc)                       \
    do {                                            \
        unsigned int value;                         \
        get_func(value)                             \
        reg_a_write = (uint8_t)(reg_a_read | (value)); \
        LOCAL_SET_NZ(reg_a_write);                  \
        INC_PC(pc_inc);                             \
    } while (0)

#define NOOP(get_func, pc_inc) \
    do {                       \
        get_func()             \
        INC_PC(pc_inc);        \
    } while (0)

#define PHA()             \
    do {                  \
        PUSH(reg_a_read); \
        CLK_INC();        \
        INC_PC(1);        \
    } while (0)

#define PHP()                           \
    do {                                \
        PUSH(LOCAL_STATUS() | P_BREAK); \
        CLK_INC();                      \
        INC_PC(1);                      \
    } while (0)

#define PLA()                     \
    do {                          \
        if (!SKIP_CYCLE) {        \
            STACK_PEEK();         \
            CLK_INC();            \
        }                         \
        reg_a_write = PULL();     \
        CLK_INC();                \
        LOCAL_SET_NZ(reg_a_read); \
        INC_PC(1);                \
    } while (0)

#define PLP()                                                 \
    do {                                                      \
        uint8_t s;                                            \
        if (!SKIP_CYCLE) {                                    \
            STACK_PEEK();                                     \
            CLK_INC();                                        \
        }                                                     \
        s = PULL();                                           \
        CLK_INC();                                            \
        if (!(s & P_INTERRUPT) && LOCAL_INTERRUPT()) {        \
            OPCODE_ENABLES_IRQ();                             \
        } else if ((s & P_INTERRUPT) && !LOCAL_INTERRUPT()) { \
            OPCODE_DISABLES_IRQ();                            \
        }                                                     \
        LOCAL_SET_STATUS(s);                                  \
        INC_PC(1);                                            \
    } while (0)

#define RLA(pc_inc, get_func, set_func)                   \
    do {                                                  \
        unsigned int old_value, new_value;                \
        get_func(old_value)                               \
        new_value = (old_value << 1) | (reg_p & P_CARRY); \
        LOCAL_SET_CARRY(new_value & 0x100);               \
        reg_a_write = reg_a_read & new_value;             \
        LOCAL_SET_NZ(reg_a_read);                         \
        INC_PC(pc_inc);                                   \
        set_func(old_value, new_value);                   \
    } while (0)

#define ROL(pc_inc, get_func, set_func)                   \
    do {                                                  \
        unsigned int old_value, new_value;                \
        get_func(old_value)                               \
        new_value = (old_value << 1) | (reg_p & P_CARRY); \
        LOCAL_SET_CARRY(new_value & 0x100);               \
        LOCAL_SET_NZ(new_value & 0xff);                   \
        INC_PC(pc_inc);                                   \
        set_func(old_value, new_value);                   \
    } while (0)

#define ROL_A()                                \
    do {                                       \
        unsigned int tmp = reg_a_read << 1;    \
                                               \
        reg_a_write = tmp | (reg_p & P_CARRY); \
        LOCAL_SET_CARRY(tmp & 0x100);          \
        LOCAL_SET_NZ(reg_a_read);              \
        INC_PC(1);                             \
    } while (0)

#define ROR(pc_inc, get_func, set_func)    \
    do {                                   \
        unsigned int old_value, new_value; \
        get_func(old_value)                \
        new_value = old_value;             \
        if (reg_p & P_CARRY) {             \
            new_value |= 0x100;            \
        }                                  \
        LOCAL_SET_CARRY(new_value & 0x01); \
        new_value >>= 1;                   \
        LOCAL_SET_NZ(new_value);           \
        INC_PC(pc_inc);                    \
        set_func(old_value, new_value)     \
    } while (0)

#define ROR_A()                                         \
    do {                                                \
        uint8_t tmp = reg_a_read;                          \
                                                        \
        reg_a_write = (reg_a_read >> 1) | (reg_p << 7); \
        LOCAL_SET_CARRY(tmp & 0x01);                    \
        LOCAL_SET_NZ(reg_a_read);                       \
        INC_PC(1);                                      \
    } while (0)

#define RRA(pc_inc, get_func, set_func)    \
    do {                                   \
        unsigned int old_value, new_value; \
        get_func(old_value)                \
        new_value = old_value;             \
        if (reg_p & P_CARRY) {             \
            new_value |= 0x100;            \
        }                                  \
        LOCAL_SET_CARRY(new_value & 0x01); \
        new_value >>= 1;                   \
        LOCAL_SET_NZ(new_value);           \
        INC_PC(pc_inc);                    \
        ADC(GET_TEMP, 0);                  \
        set_func(old_value, new_value)     \
    } while (0)

/* RTI does must not use `OPCODE_ENABLES_IRQ()' even if the I flag changes
   from 1 to 0 because the value of I is set 3 cycles before the end of the
   opcode, and thus the 6510 has enough time to call the interrupt routine as
   soon as the opcode ends, if necessary.  */
#define RTI()                           \
    do {                                \
        uint16_t tmp;                   \
        if (!SKIP_CYCLE) {              \
            STACK_PEEK();               \
            CLK_INC();                  \
        }                               \
        tmp = (uint16_t)PULL();         \
        CLK_INC();                      \
        LOCAL_SET_STATUS((uint8_t)tmp); \
        tmp = (uint16_t)PULL();         \
        CLK_INC();                      \
        tmp |= (uint16_t)PULL() << 8;   \
        CLK_INC();                      \
        JUMP(tmp);                      \
    } while (0)

#define RTS()                 \
    do {                      \
        uint16_t tmp;         \
        if (!SKIP_CYCLE) {    \
            STACK_PEEK();     \
            CLK_INC();        \
        }                     \
        tmp = PULL();         \
        CLK_INC();            \
        tmp |= (PULL() << 8); \
        CLK_INC();            \
        LOAD(tmp);            \
        CLK_INC();            \
        tmp++;                \
        JUMP(tmp);            \
    } while (0)

#define SAC()                       \
    do {                            \
        reg_a_write_idx = p1 >> 4;  \
        reg_a_read_idx = p1 & 0x0f; \
        INC_PC(2);                  \
    } while (0)

#define SBC(get_func, pc_inc)                                                               \
    do {                                                                                    \
        uint16_t src, tmp;                                                                      \
                                                                                            \
        get_func(src)                                                                       \
        tmp = reg_a_read - src - ((reg_p & P_CARRY) ? 0 : 1);                               \
        if (reg_p & P_DECIMAL) {                                                            \
            unsigned int tmp_a;                                                             \
            tmp_a = (reg_a_read & 0xf) - (src & 0xf) - ((reg_p & P_CARRY) ? 0 : 1);         \
            if (tmp_a & 0x10) {                                                             \
                tmp_a = ((tmp_a - 6) & 0xf) | ((reg_a_read & 0xf0) - (src & 0xf0) - 0x10);  \
            } else {                                                                        \
                tmp_a = (tmp_a & 0xf) | ((reg_a_read & 0xf0) - (src & 0xf0));               \
            }                                                                               \
            if (tmp_a & 0x100) {                                                            \
                tmp_a -= 0x60;                                                              \
            }                                                                               \
            LOCAL_SET_CARRY(tmp < 0x100);                                                   \
            LOCAL_SET_NZ(tmp & 0xff);                                                       \
            LOCAL_SET_OVERFLOW(((reg_a_read ^ tmp) & 0x80) && ((reg_a_read ^ src) & 0x80)); \
            reg_a_write = (uint8_t) tmp_a;                                                     \
        } else {                                                                            \
            LOCAL_SET_NZ(tmp & 0xff);                                                       \
            LOCAL_SET_CARRY(tmp < 0x100);                                                   \
            LOCAL_SET_OVERFLOW(((reg_a_read ^ tmp) & 0x80) && ((reg_a_read ^ src) & 0x80)); \
            reg_a_write = (uint8_t) tmp;                                                       \
        }                                                                                   \
        INC_PC(pc_inc);                                                                     \
    } while (0)

#define SBX()                            \
    do {                                 \
        unsigned int tmp;                \
        INC_PC(2);                       \
        tmp = (reg_a_read & reg_x) - p1; \
        LOCAL_SET_CARRY(tmp < 0x100);    \
        reg_x = tmp & 0xff;              \
        LOCAL_SET_NZ(reg_x);             \
    } while (0)

#undef SEC    /* defined in time.h on SunOS. */
#define SEC()               \
    do {                    \
        LOCAL_SET_CARRY(1); \
        INC_PC(1);          \
    } while (0)

#define SED()                 \
    do {                      \
        LOCAL_SET_DECIMAL(1); \
        INC_PC(1);            \
    } while (0)

#define SEI()                      \
    do {                           \
        if (!LOCAL_INTERRUPT()) {  \
            OPCODE_DISABLES_IRQ(); \
        }                          \
        LOCAL_SET_INTERRUPT(1);    \
        INC_PC(1);                 \
    } while (0)

#define SHA_IND_Y()                                    \
    do {                                               \
        INT_IND_Y_W_NOADDR();                          \
        SET_ABS_SH_I(tmpa, reg_a_read & reg_x, reg_y); \
        INC_PC(2);                                     \
    } while (0)

#define SH_ABS_I(reg_and, reg_i)                                              \
    do {                                                                      \
        if (!SKIP_CYCLE) {                                                    \
            LOAD_CHECK_BA_LOW_DUMMY(((p2 + reg_i) & 0xff) | ((p2) & 0xff00)); \
            CLK_INC();                                                        \
        }                                                                     \
        SET_ABS_SH_I(p2, reg_and, reg_i);                                     \
        INC_PC(3);                                                            \
    } while (0)

#define SHS_ABS_Y()                          \
    do {                                     \
        SH_ABS_I(reg_a_read & reg_x, reg_y); \
        reg_sp = reg_a_read & reg_x;         \
    } while (0)

#define SIR()                  \
    do {                       \
        reg_y_idx = p1 >> 4;   \
        reg_x_idx = p1 & 0x0f; \
        INC_PC(2);             \
    } while (0)

#define SLO(pc_inc, get_func, set_func)       \
    do {                                      \
        unsigned int old_value, new_value;    \
        get_func(old_value)                   \
        new_value = old_value;                \
        LOCAL_SET_CARRY(old_value & 0x80);    \
        new_value <<= 1;                      \
        reg_a_write = reg_a_read | new_value; \
        LOCAL_SET_NZ(reg_a_read);             \
        INC_PC(pc_inc);                       \
        set_func(old_value, new_value)        \
    } while (0)

#define SRE(pc_inc, get_func, set_func)       \
    do {                                      \
        unsigned int old_value, new_value;    \
        get_func(old_value)                   \
        new_value = old_value;                \
        LOCAL_SET_CARRY(old_value & 0x01);    \
        new_value >>= 1;                      \
        reg_a_write = reg_a_read ^ new_value; \
        LOCAL_SET_NZ(reg_a_read);             \
        INC_PC(pc_inc);                       \
        set_func(old_value, new_value)        \
    } while (0)

#define ST(value, set_func, pc_inc) \
    do {                            \
        INC_PC(pc_inc);             \
        set_func(value);            \
    } while (0)

#define TAX()                     \
    do {                          \
        reg_x = reg_a_read;       \
        LOCAL_SET_NZ(reg_a_read); \
        INC_PC(1);                \
    } while (0)

#define TAY()                     \
    do {                          \
        reg_y = reg_a_read;       \
        LOCAL_SET_NZ(reg_a_read); \
        INC_PC(1);                \
    } while (0)

#define TSX()                 \
    do {                      \
        reg_x = reg_sp;       \
        LOCAL_SET_NZ(reg_sp); \
        INC_PC(1);            \
    } while (0)

#define TXA()                     \
    do {                          \
        reg_a_write = reg_x;      \
        LOCAL_SET_NZ(reg_a_read); \
        INC_PC(1);                \
    } while (0)

#define TXS()           \
    do {                \
        reg_sp = reg_x; \
        INC_PC(1);      \
    } while (0)

#define TYA()                     \
    do {                          \
        reg_a_write = reg_y;      \
        LOCAL_SET_NZ(reg_a_read); \
        INC_PC(1);                \
    } while (0)


/* ------------------------------------------------------------------------- */

static const uint8_t fetch_tab[] = {
            /* 0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F */
    /* $00 */  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, /* $00 */
    /* $10 */  0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, /* $10 */
    /* $20 */  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, /* $20 */
    /* $30 */  0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, /* $30 */
    /* $40 */  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, /* $40 */
    /* $50 */  0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, /* $50 */
    /* $60 */  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, /* $60 */
    /* $70 */  0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, /* $70 */
    /* $80 */  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, /* $80 */
    /* $90 */  0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, /* $90 */
    /* $A0 */  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, /* $A0 */
    /* $B0 */  0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, /* $B0 */
    /* $C0 */  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, /* $C0 */
    /* $D0 */  0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, /* $D0 */
    /* $E0 */  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, /* $E0 */
    /* $F0 */  0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1  /* $F0 */
};


/* ------------------------------------------------------------------------ */

/* Here, the CPU is emulated. */

{
    static int cpu_is_jammed = 0;
    
#ifdef CHECK_AND_RUN_ALTERNATE_CPU
    CHECK_AND_RUN_ALTERNATE_CPU
#endif

    while (CLK >= alarm_context_next_pending_clk(ALARM_CONTEXT)) {
        alarm_context_dispatch(ALARM_CONTEXT, CLK);
    }
    
    /* HACK: when the CPU is jammed, no interrupts are served, the only way 
       to recover is reset. so we clear the interrupt flags and force 
       acknowledging them here in this case. */
    if (cpu_is_jammed) {
        interrupt_ack_irq(CPU_INT_STATUS);
        CPU_INT_STATUS->global_pending_int &= ~(IK_IRQ | IK_NMI);
        if (CPU_INT_STATUS->global_pending_int & IK_RESET) {
            cpu_is_jammed = 0;
        }
    }

    {
        enum cpu_int pending_interrupt;

        if (!(CPU_INT_STATUS->global_pending_int & IK_IRQ)
            && (CPU_INT_STATUS->global_pending_int & IK_IRQPEND)
            && CPU_INT_STATUS->irq_pending_clk <= CLK) {
            interrupt_ack_irq(CPU_INT_STATUS);
        }

        pending_interrupt = CPU_INT_STATUS->global_pending_int;
        if (pending_interrupt != IK_NONE) {
            DO_INTERRUPT(pending_interrupt);
            if (!(CPU_INT_STATUS->global_pending_int & IK_IRQ)
                && CPU_INT_STATUS->global_pending_int & IK_IRQPEND) {
                CPU_INT_STATUS->global_pending_int &= ~IK_IRQPEND;
            }
            while (CLK >= alarm_context_next_pending_clk(ALARM_CONTEXT)) {
                alarm_context_dispatch(ALARM_CONTEXT, CLK);
            }
        }
    }

    {
        opcode_t opcode;
#ifdef DEBUG
        debug_clk = maincpu_clk;
#endif

#ifdef FEATURE_CPUMEMHISTORY
        memmap_state |= (MEMMAP_STATE_INSTR | MEMMAP_STATE_OPCODE);
#endif

        SET_LAST_ADDR(reg_pc);

        FETCH_OPCODE(opcode);

#ifdef FEATURE_CPUMEMHISTORY
        /* If reg_pc >= bank_limit  then JSR (0x20) hasn't load p2 yet.
           The earlier LOAD(reg_pc+2) hack can break stealing badly on x64sc.
           The fixing is now handled in JSR(). */
        monitor_cpuhistory_store(maincpu_clk, reg_pc, p0, p1, p2 >> 8, reg_a_read, reg_x, reg_y, reg_sp, LOCAL_STATUS(), 0);
        memmap_state &= ~(MEMMAP_STATE_INSTR | MEMMAP_STATE_OPCODE);
#endif

#ifdef DEBUG
        if (TRACEFLG) {
            uint8_t op = (uint8_t)(p0);
            uint8_t lo = (uint8_t)(p1);
            uint8_t hi = (uint8_t)(p2 >> 8);

            debug_maincpu((uint32_t)(reg_pc), debug_clk,
                          mon_disassemble_to_string(e_comp_space,
                                                    (uint16_t) reg_pc, op,
                                                    lo, hi, 0, 1, "6502"),
                          reg_a_read, reg_x, reg_y, reg_sp);
        }
        if (debug.perform_break_into_monitor) {
            monitor_startup_trap();
            debug.perform_break_into_monitor = 0;
        }
#endif

trap_skipped:
#ifndef OPCODE_UPDATE_IN_FETCH
        SET_LAST_OPCODE(p0);
#endif

        switch (p0) {
            case 0x00:          /* BRK */
                BRK();
                break;

            case 0x01:          /* ORA ($nn,X) */
                ORA(GET_IND_X, 2);
                break;

            case 0x02:          /* JAM - also used for traps */
                STATIC_ASSERT(TRAP_OPCODE == 0x02);
                JAM_02();
                break;

            case 0x22:          /* JAM */
            case 0x52:          /* JAM */
            case 0x62:          /* JAM */
            case 0x72:          /* JAM */
            case 0x92:          /* JAM */
            case 0xb2:          /* JAM */
            case 0xd2:          /* JAM */
            case 0xf2:          /* JAM */
#ifndef C64DTV
            case 0x12:          /* JAM */
            case 0x32:          /* JAM */
            case 0x42:          /* JAM */
#endif
                cpu_is_jammed = 1;
                REWIND_FETCH_OPCODE(CLK);
                JAM();
                break;

#ifdef C64DTV
            case 0x12:          /* BRA $nnnn */
                BRANCH(1);
                break;

            case 0x32:          /* SAC #$nn */
                SAC();
                break;

            case 0x42:          /* SIR #$nn */
                SIR();
                break;
#endif

            case 0x03:          /* SLO ($nn,X) */
                SLO(2, GET_IND_X, SET_IND_RMW);
                break;

            case 0x04:          /* NOOP $nn */
            case 0x44:          /* NOOP $nn */
            case 0x64:          /* NOOP $nn */
                NOOP(GET_ZERO_DUMMY, 2);
                break;

            case 0x05:          /* ORA $nn */
                ORA(GET_ZERO, 2);
                break;

            case 0x06:          /* ASL $nn */
                ASL(2, GET_ZERO, SET_ZERO_RMW);
                break;

            case 0x07:          /* SLO $nn */
                SLO(2, GET_ZERO, SET_ZERO_RMW);
                break;

            case 0x08:          /* PHP */
                PHP();
                break;

            case 0x09:          /* ORA #$nn */
                ORA(GET_IMM, 2);
                break;

            case 0x0a:          /* ASL A */
                ASL_A();
                break;

            case 0x0b:          /* ANC #$nn */
            case 0x2b:          /* ANC #$nn */
                ANC();
                break;

            case 0x0c:          /* NOOP $nnnn */
                NOOP(GET_ABS_DUMMY, 3);
                break;

            case 0x0d:          /* ORA $nnnn */
                ORA(GET_ABS, 3);
                break;

            case 0x0e:          /* ASL $nnnn */
                ASL(3, GET_ABS, SET_ABS_RMW);
                break;

            case 0x0f:          /* SLO $nnnn */
                SLO(3, GET_ABS, SET_ABS_RMW);
                break;

            case 0x10:          /* BPL $nnnn */
                BRANCH(!LOCAL_SIGN());
                break;

            case 0x11:          /* ORA ($nn),Y */
                ORA(GET_IND_Y, 2);
                break;

            case 0x13:          /* SLO ($nn),Y */
                SLO(2, GET_IND_Y_RMW, SET_IND_RMW);
                break;

            case 0x14:          /* NOOP $nn,X */
            case 0x34:          /* NOOP $nn,X */
            case 0x54:          /* NOOP $nn,X */
            case 0x74:          /* NOOP $nn,X */
            case 0xd4:          /* NOOP $nn,X */
            case 0xf4:          /* NOOP $nn,X */
                NOOP(GET_ZERO_X_DUMMY, 2);
                break;

            case 0x15:          /* ORA $nn,X */
                ORA(GET_ZERO_X, 2);
                break;

            case 0x16:          /* ASL $nn,X */
                ASL(2, GET_ZERO_X, SET_ZERO_X_RMW);
                break;

            case 0x17:          /* SLO $nn,X */
                SLO(2, GET_ZERO_X, SET_ZERO_X_RMW);
                break;

            case 0x18:          /* CLC */
                CLC();
                break;

            case 0x19:          /* ORA $nnnn,Y */
                ORA(GET_ABS_Y, 3);
                break;

            case 0x1a:          /* NOOP */
            case 0x3a:          /* NOOP */
            case 0x5a:          /* NOOP */
            case 0x7a:          /* NOOP */
            case 0xda:          /* NOOP */
            case 0xfa:          /* NOOP */
            case 0xea:          /* NOP */
                NOOP(GET_IMM_DUMMY, 1);
                break;

            case 0x1b:          /* SLO $nnnn,Y */
                SLO(3, GET_ABS_Y_RMW, SET_ABS_Y_RMW);
                break;

            case 0x1c:          /* NOOP $nnnn,X */
            case 0x3c:          /* NOOP $nnnn,X */
            case 0x5c:          /* NOOP $nnnn,X */
            case 0x7c:          /* NOOP $nnnn,X */
            case 0xdc:          /* NOOP $nnnn,X */
            case 0xfc:          /* NOOP $nnnn,X */
                NOOP(GET_ABS_X_DUMMY, 3);
                break;

            case 0x1d:          /* ORA $nnnn,X */
                ORA(GET_ABS_X, 3);
                break;

            case 0x1e:          /* ASL $nnnn,X */
                ASL(3, GET_ABS_X_RMW, SET_ABS_X_RMW);
                break;

            case 0x1f:          /* SLO $nnnn,X */
                SLO(3, GET_ABS_X_RMW, SET_ABS_X_RMW);
                break;

            case 0x20:          /* JSR $nnnn */
                JSR();
                break;

            case 0x21:          /* AND ($nn,X) */
                AND(GET_IND_X, 2);
                break;

            case 0x23:          /* RLA ($nn,X) */
                RLA(2, GET_IND_X, SET_IND_RMW);
                break;

            case 0x24:          /* BIT $nn */
                BIT(GET_ZERO, 2);
                break;

            case 0x25:          /* AND $nn */
                AND(GET_ZERO, 2);
                break;

            case 0x26:          /* ROL $nn */
                ROL(2, GET_ZERO, SET_ZERO_RMW);
                break;

            case 0x27:          /* RLA $nn */
                RLA(2, GET_ZERO, SET_ZERO_RMW);
                break;

            case 0x28:          /* PLP */
                PLP();
                break;

            case 0x29:          /* AND #$nn */
                AND(GET_IMM, 2);
                break;

            case 0x2a:          /* ROL A */
                ROL_A();
                break;

            case 0x2c:          /* BIT $nnnn */
                BIT(GET_ABS, 3);
                break;

            case 0x2d:          /* AND $nnnn */
                AND(GET_ABS, 3);
                break;

            case 0x2e:          /* ROL $nnnn */
                ROL(3, GET_ABS, SET_ABS_RMW);
                break;

            case 0x2f:          /* RLA $nnnn */
                RLA(3, GET_ABS, SET_ABS_RMW);
                break;

            case 0x30:          /* BMI $nnnn */
                BRANCH(LOCAL_SIGN());
                break;

            case 0x31:          /* AND ($nn),Y */
                AND(GET_IND_Y, 2);
                break;

            case 0x33:          /* RLA ($nn),Y */
                RLA(2, GET_IND_Y_RMW, SET_IND_RMW);
                break;

            case 0x35:          /* AND $nn,X */
                AND(GET_ZERO_X, 2);
                break;

            case 0x36:          /* ROL $nn,X */
                ROL(2, GET_ZERO_X, SET_ZERO_X_RMW);
                break;

            case 0x37:          /* RLA $nn,X */
                RLA(2, GET_ZERO_X, SET_ZERO_X_RMW);
                break;

            case 0x38:          /* SEC */
                SEC();
                break;

            case 0x39:          /* AND $nnnn,Y */
                AND(GET_ABS_Y, 3);
                break;

            case 0x3b:          /* RLA $nnnn,Y */
                RLA(3, GET_ABS_Y_RMW, SET_ABS_Y_RMW);
                break;

            case 0x3d:          /* AND $nnnn,X */
                AND(GET_ABS_X, 3);
                break;

            case 0x3e:          /* ROL $nnnn,X */
                ROL(3, GET_ABS_X_RMW, SET_ABS_X_RMW);
                break;

            case 0x3f:          /* RLA $nnnn,X */
                RLA(3, GET_ABS_X_RMW, SET_ABS_X_RMW);
                break;

            case 0x40:          /* RTI */
                RTI();
                break;

            case 0x41:          /* EOR ($nn,X) */
                EOR(GET_IND_X, 2);
                break;

            case 0x43:          /* SRE ($nn,X) */
                SRE(2, GET_IND_X, SET_IND_RMW);
                break;

            case 0x45:          /* EOR $nn */
                EOR(GET_ZERO, 2);
                break;

            case 0x46:          /* LSR $nn */
                LSR(2, GET_ZERO, SET_ZERO_RMW);
                break;

            case 0x47:          /* SRE $nn */
                SRE(2, GET_ZERO, SET_ZERO_RMW);
                break;

            case 0x48:          /* PHA */
                PHA();
                break;

            case 0x49:          /* EOR #$nn */
                EOR(GET_IMM, 2);
                break;

            case 0x4a:          /* LSR A */
                LSR_A();
                break;

            case 0x4b:          /* ASR #$nn */
                ASR();
                break;

            case 0x4c:          /* JMP $nnnn */
                JMP(p2);
                break;

            case 0x4d:          /* EOR $nnnn */
                EOR(GET_ABS, 3);
                break;

            case 0x4e:          /* LSR $nnnn */
                LSR(3, GET_ABS, SET_ABS_RMW);
                break;

            case 0x4f:          /* SRE $nnnn */
                SRE(3, GET_ABS, SET_ABS_RMW);
                break;

            case 0x50:          /* BVC $nnnn */
                BRANCH(!LOCAL_OVERFLOW());
                break;

            case 0x51:          /* EOR ($nn),Y */
                EOR(GET_IND_Y, 2);
                break;

            case 0x53:          /* SRE ($nn),Y */
                SRE(2, GET_IND_Y_RMW, SET_IND_RMW);
                break;

            case 0x55:          /* EOR $nn,X */
                EOR(GET_ZERO_X, 2);
                break;

            case 0x56:          /* LSR $nn,X */
                LSR(2, GET_ZERO_X, SET_ZERO_X_RMW);
                break;

            case 0x57:          /* SRE $nn,X */
                SRE(2, GET_ZERO_X, SET_ZERO_X_RMW);
                break;

            case 0x58:          /* CLI */
                CLI();
                break;

            case 0x59:          /* EOR $nnnn,Y */
                EOR(GET_ABS_Y, 3);
                break;

            case 0x5b:          /* SRE $nnnn,Y */
                SRE(3, GET_ABS_Y_RMW, SET_ABS_Y_RMW);
                break;

            case 0x5d:          /* EOR $nnnn,X */
                EOR(GET_ABS_X, 3);
                break;

            case 0x5e:          /* LSR $nnnn,X */
                LSR(3, GET_ABS_X_RMW, SET_ABS_X_RMW);
                break;

            case 0x5f:          /* SRE $nnnn,X */
                SRE(3, GET_ABS_X_RMW, SET_ABS_X_RMW);
                break;

            case 0x60:          /* RTS */
                RTS();
                break;

            case 0x61:          /* ADC ($nn,X) */
                ADC(GET_IND_X, 2);
                break;

            case 0x63:          /* RRA ($nn,X) */
                RRA(2, GET_IND_X, SET_IND_RMW);
                break;

            case 0x65:          /* ADC $nn */
                ADC(GET_ZERO, 2);
                break;

            case 0x66:          /* ROR $nn */
                ROR(2, GET_ZERO, SET_ZERO_RMW);
                break;

            case 0x67:          /* RRA $nn */
                RRA(2, GET_ZERO, SET_ZERO_RMW);
                break;

            case 0x68:          /* PLA */
                PLA();
                break;

            case 0x69:          /* ADC #$nn */
                ADC(GET_IMM, 2);
                break;

            case 0x6a:          /* ROR A */
                ROR_A();
                break;

            case 0x6b:          /* ARR #$nn */
                ARR();
                break;

            case 0x6c:          /* JMP ($nnnn) */
                JMP_IND();
                break;

            case 0x6d:          /* ADC $nnnn */
                ADC(GET_ABS, 3);
                break;

            case 0x6e:          /* ROR $nnnn */
                ROR(3, GET_ABS, SET_ABS_RMW);
                break;

            case 0x6f:          /* RRA $nnnn */
                RRA(3, GET_ABS, SET_ABS_RMW);
                break;

            case 0x70:          /* BVS $nnnn */
                BRANCH(LOCAL_OVERFLOW());
                break;

            case 0x71:          /* ADC ($nn),Y */
                ADC(GET_IND_Y, 2);
                break;

            case 0x73:          /* RRA ($nn),Y */
                RRA(2, GET_IND_Y_RMW, SET_IND_RMW);
                break;

            case 0x75:          /* ADC $nn,X */
                ADC(GET_ZERO_X, 2);
                break;

            case 0x76:          /* ROR $nn,X */
                ROR(2, GET_ZERO_X, SET_ZERO_X_RMW);
                break;

            case 0x77:          /* RRA $nn,X */
                RRA(2, GET_ZERO_X, SET_ZERO_X_RMW);
                break;

            case 0x78:          /* SEI */
                SEI();
                break;

            case 0x79:          /* ADC $nnnn,Y */
                ADC(GET_ABS_Y, 3);
                break;

            case 0x7b:          /* RRA $nnnn,Y */
                RRA(3, GET_ABS_Y_RMW, SET_ABS_Y_RMW);
                break;

            case 0x7d:          /* ADC $nnnn,X */
                ADC(GET_ABS_X, 3);
                break;

            case 0x7e:          /* ROR $nnnn,X */
                ROR(3, GET_ABS_X_RMW, SET_ABS_X_RMW);
                break;

            case 0x7f:          /* RRA $nnnn,X */
                RRA(3, GET_ABS_X_RMW, SET_ABS_X_RMW);
                break;

            case 0x80:          /* NOOP #$nn */
            case 0x82:          /* NOOP #$nn */
            case 0x89:          /* NOOP #$nn */
            case 0xc2:          /* NOOP #$nn */
            case 0xe2:          /* NOOP #$nn */
                NOOP(GET_IMM_DUMMY, 2);
                break;

            case 0x81:          /* STA ($nn,X) */
                ST(reg_a_read, SET_IND_X, 2);
                break;

            case 0x83:          /* SAX ($nn,X) */
                ST(reg_a_read & reg_x, SET_IND_X, 2);
                break;

            case 0x84:          /* STY $nn */
                ST(reg_y, SET_ZERO, 2);
                break;

            case 0x85:          /* STA $nn */
                ST(reg_a_read, SET_ZERO, 2);
                break;

            case 0x86:          /* STX $nn */
                ST(reg_x, SET_ZERO, 2);
                break;

            case 0x87:          /* SAX $nn */
                ST(reg_a_read & reg_x, SET_ZERO, 2);
                break;

            case 0x88:          /* DEY */
                DEY();
                break;

            case 0x8a:          /* TXA */
                TXA();
                break;

            case 0x8b:          /* ANE #$nn */
                ANE();
                break;

            case 0x8c:          /* STY $nnnn */
                ST(reg_y, SET_ABS, 3);
                break;

            case 0x8d:          /* STA $nnnn */
                ST(reg_a_read, SET_ABS, 3);
                break;

            case 0x8e:          /* STX $nnnn */
                ST(reg_x, SET_ABS, 3);
                break;

            case 0x8f:          /* SAX $nnnn */
                ST(reg_a_read & reg_x, SET_ABS, 3);
                break;

            case 0x90:          /* BCC $nnnn */
                BRANCH(!LOCAL_CARRY());
                break;

            case 0x91:          /* STA ($nn),Y */
                ST(reg_a_read, SET_IND_Y, 2);
                break;

            case 0x93:          /* SHA ($nn),Y */
                SHA_IND_Y();
                break;

            case 0x94:          /* STY $nn,X */
                ST(reg_y, SET_ZERO_X, 2);
                break;

            case 0x95:          /* STA $nn,X */
                ST(reg_a_read, SET_ZERO_X, 2);
                break;

            case 0x96:          /* STX $nn,Y */
                ST(reg_x, SET_ZERO_Y, 2);
                break;

            case 0x97:          /* SAX $nn,Y */
                ST(reg_a_read & reg_x, SET_ZERO_Y, 2);
                break;

            case 0x98:          /* TYA */
                TYA();
                break;

            case 0x99:          /* STA $nnnn,Y */
                ST(reg_a_read, SET_ABS_Y, 3);
                break;

            case 0x9a:          /* TXS */
                TXS();
                break;

            case 0x9b:          /* NOP (SHS) $nnnn,Y */
#ifdef C64DTV
                NOOP(GET_ABS_Y_DUMMY, 3);
#else
                SHS_ABS_Y();
#endif
                break;

            case 0x9c:          /* SHY $nnnn,X */
                SH_ABS_I(reg_y, reg_x);
                break;

            case 0x9d:          /* STA $nnnn,X */
                ST(reg_a_read, SET_ABS_X, 3);
                break;

            case 0x9e:          /* SHX $nnnn,Y */
                SH_ABS_I(reg_x, reg_y);
                break;

            case 0x9f:          /* SHA $nnnn,Y */
                SH_ABS_I(reg_a_read & reg_x, reg_y);
                break;

            case 0xa0:          /* LDY #$nn */
                LD(reg_y, GET_IMM, 2);
                break;

            case 0xa1:          /* LDA ($nn,X) */
                LD(reg_a_write, GET_IND_X, 2);
                break;

            case 0xa2:          /* LDX #$nn */
                LD(reg_x, GET_IMM, 2);
                break;

            case 0xa3:          /* LAX ($nn,X) */
                LAX(GET_IND_X, 2);
                break;

            case 0xa4:          /* LDY $nn */
                LD(reg_y, GET_ZERO, 2);
                break;

            case 0xa5:          /* LDA $nn */
                LD(reg_a_write, GET_ZERO, 2);
                break;

            case 0xa6:          /* LDX $nn */
                LD(reg_x, GET_ZERO, 2);
                break;

            case 0xa7:          /* LAX $nn */
                LAX(GET_ZERO, 2);
                break;

            case 0xa8:          /* TAY */
                TAY();
                break;

            case 0xa9:          /* LDA #$nn */
                LD(reg_a_write, GET_IMM, 2);
                break;

            case 0xaa:          /* TAX */
                TAX();
                break;

            case 0xab:          /* LXA #$nn */
                LXA();
                break;

            case 0xac:          /* LDY $nnnn */
                LD(reg_y, GET_ABS, 3);
                break;

            case 0xad:          /* LDA $nnnn */
                LD(reg_a_write, GET_ABS, 3);
                break;

            case 0xae:          /* LDX $nnnn */
                LD(reg_x, GET_ABS, 3);
                break;

            case 0xaf:          /* LAX $nnnn */
                LAX(GET_ABS, 3);
                break;

            case 0xb0:          /* BCS $nnnn */
                BRANCH(LOCAL_CARRY());
                break;

            case 0xb1:          /* LDA ($nn),Y */
                LD(reg_a_write, GET_IND_Y, 2);
                break;

            case 0xb3:          /* LAX ($nn),Y */
                LAX(GET_IND_Y, 2);
                break;

            case 0xb4:          /* LDY $nn,X */
                LD(reg_y, GET_ZERO_X, 2);
                break;

            case 0xb5:          /* LDA $nn,X */
                LD(reg_a_write, GET_ZERO_X, 2);
                break;

            case 0xb6:          /* LDX $nn,Y */
                LD(reg_x, GET_ZERO_Y, 2);
                break;

            case 0xb7:          /* LAX $nn,Y */
                LAX(GET_ZERO_Y, 2);
                break;

            case 0xb8:          /* CLV */
                CLV();
                break;

            case 0xb9:          /* LDA $nnnn,Y */
                LD(reg_a_write, GET_ABS_Y, 3);
                break;

            case 0xba:          /* TSX */
                TSX();
                break;

            case 0xbb:          /* LAS $nnnn,Y */
                LAS();
                break;

            case 0xbc:          /* LDY $nnnn,X */
                LD(reg_y, GET_ABS_X, 3);
                break;

            case 0xbd:          /* LDA $nnnn,X */
                LD(reg_a_write, GET_ABS_X, 3);
                break;

            case 0xbe:          /* LDX $nnnn,Y */
                LD(reg_x, GET_ABS_Y, 3);
                break;

            case 0xbf:          /* LAX $nnnn,Y */
                LAX(GET_ABS_Y, 3);
                break;

            case 0xc0:          /* CPY #$nn */
                CP(reg_y, GET_IMM, 2);
                break;

            case 0xc1:          /* CMP ($nn,X) */
                CP(reg_a_read, GET_IND_X, 2);
                break;

            case 0xc3:          /* DCP ($nn,X) */
                DCP(2, GET_IND_X, SET_IND_RMW);
                break;

            case 0xc4:          /* CPY $nn */
                CP(reg_y, GET_ZERO, 2);
                break;

            case 0xc5:          /* CMP $nn */
                CP(reg_a_read, GET_ZERO, 2);
                break;

            case 0xc6:          /* DEC $nn */
                DEC(2, GET_ZERO, SET_ZERO_RMW);
                break;

            case 0xc7:          /* DCP $nn */
                DCP(2, GET_ZERO, SET_ZERO_RMW);
                break;

            case 0xc8:          /* INY */
                INY();
                break;

            case 0xc9:          /* CMP #$nn */
                CP(reg_a_read, GET_IMM, 2);
                break;

            case 0xca:          /* DEX */
                DEX();
                break;

            case 0xcb:          /* SBX #$nn */
                SBX();
                break;

            case 0xcc:          /* CPY $nnnn */
                CP(reg_y, GET_ABS, 3);
                break;

            case 0xcd:          /* CMP $nnnn */
                CP(reg_a_read, GET_ABS, 3);
                break;

            case 0xce:          /* DEC $nnnn */
                DEC(3, GET_ABS, SET_ABS_RMW);
                break;

            case 0xcf:          /* DCP $nnnn */
                DCP(3, GET_ABS, SET_ABS_RMW);
                break;

            case 0xd0:          /* BNE $nnnn */
                BRANCH(!LOCAL_ZERO());
                break;

            case 0xd1:          /* CMP ($nn),Y */
                CP(reg_a_read, GET_IND_Y, 2);
                break;

            case 0xd3:          /* DCP ($nn),Y */
                DCP(2, GET_IND_Y_RMW, SET_IND_RMW);
                break;

            case 0xd5:          /* CMP $nn,X */
                CP(reg_a_read, GET_ZERO_X, 2);
                break;

            case 0xd6:          /* DEC $nn,X */
                DEC(2, GET_ZERO_X, SET_ZERO_X_RMW);
                break;

            case 0xd7:          /* DCP $nn,X */
                DCP(2, GET_ZERO_X, SET_ZERO_X_RMW);
                break;

            case 0xd8:          /* CLD */
                CLD();
                break;

            case 0xd9:          /* CMP $nnnn,Y */
                CP(reg_a_read, GET_ABS_Y, 3);
                break;

            case 0xdb:          /* DCP $nnnn,Y */
                DCP(3, GET_ABS_Y_RMW, SET_ABS_Y_RMW);
                break;

            case 0xdd:          /* CMP $nnnn,X */
                CP(reg_a_read, GET_ABS_X, 3);
                break;

            case 0xde:          /* DEC $nnnn,X */
                DEC(3, GET_ABS_X_RMW, SET_ABS_X_RMW);
                break;

            case 0xdf:          /* DCP $nnnn,X */
                DCP(3, GET_ABS_X_RMW, SET_ABS_X_RMW);
                break;

            case 0xe0:          /* CPX #$nn */
                CP(reg_x, GET_IMM, 2);
                break;

            case 0xe1:          /* SBC ($nn,X) */
                SBC(GET_IND_X, 2);
                break;

            case 0xe3:          /* ISB ($nn,X) */
                ISB(2, GET_IND_X, SET_IND_RMW);
                break;

            case 0xe4:          /* CPX $nn */
                CP(reg_x, GET_ZERO, 2);
                break;

            case 0xe5:          /* SBC $nn */
                SBC(GET_ZERO, 2);
                break;

            case 0xe6:          /* INC $nn */
                INC(2, GET_ZERO, SET_ZERO_RMW);
                break;

            case 0xe7:          /* ISB $nn */
                ISB(2, GET_ZERO, SET_ZERO_RMW);
                break;

            case 0xe8:          /* INX */
                INX();
                break;

            case 0xe9:          /* SBC #$nn */
            case 0xeb:          /* USBC #$nn (same as SBC) */
                SBC(GET_IMM, 2);
                break;

            case 0xec:          /* CPX $nnnn */
                CP(reg_x, GET_ABS, 3);
                break;

            case 0xed:          /* SBC $nnnn */
                SBC(GET_ABS, 3);
                break;

            case 0xee:          /* INC $nnnn */
                INC(3, GET_ABS, SET_ABS_RMW);
                break;

            case 0xef:          /* ISB $nnnn */
                ISB(3, GET_ABS, SET_ABS_RMW);
                break;

            case 0xf0:          /* BEQ $nnnn */
                BRANCH(LOCAL_ZERO());
                break;

            case 0xf1:          /* SBC ($nn),Y */
                SBC(GET_IND_Y, 2);
                break;

            case 0xf3:          /* ISB ($nn),Y */
                ISB(2, GET_IND_Y_RMW, SET_IND_RMW);
                break;

            case 0xf5:          /* SBC $nn,X */
                SBC(GET_ZERO_X, 2);
                break;

            case 0xf6:          /* INC $nn,X */
                INC(2, GET_ZERO_X, SET_ZERO_X_RMW);
                break;

            case 0xf7:          /* ISB $nn,X */
                ISB(2, GET_ZERO_X, SET_ZERO_X_RMW);
                break;

            case 0xf8:          /* SED */
                SED();
                break;

            case 0xf9:          /* SBC $nnnn,Y */
                SBC(GET_ABS_Y, 3);
                break;

            case 0xfb:          /* ISB $nnnn,Y */
                ISB(3, GET_ABS_Y_RMW, SET_ABS_Y_RMW);
                break;

            case 0xfd:          /* SBC $nnnn,X */
                SBC(GET_ABS_X, 3);
                break;

            case 0xfe:          /* INC $nnnn,X */
                INC(3, GET_ABS_X_RMW, SET_ABS_X_RMW);
                break;

            case 0xff:          /* ISB $nnnn,X */
                ISB(3, GET_ABS_X_RMW, SET_ABS_X_RMW);
                break;
        }
    }
}
