tetanes 0.8.0

A NES Emulator written in Rust with SDL2 and WebAssembly support
Documentation
use super::{Cpu, Status, IRQ_VECTOR, NMI_VECTOR, SP_BASE};
use crate::memory::{MemRead, MemWrite};
use serde::{Deserialize, Serialize};
use std::fmt;

// 16x16 grid of 6502 opcodes. Matches datasheet matrix for easy lookup
#[rustfmt::skip]
pub const INSTRUCTIONS: [Instr; 256] = [
    Instr(0x00, IMM, BRK, 7), Instr(0x01, IDX, ORA, 6), Instr(0x02, IMP, XXX, 2), Instr(0x03, IDX, SLO, 8), Instr(0x04, ZP0, NOP, 3), Instr(0x05, ZP0, ORA, 3), Instr(0x06, ZP0, ASL, 5), Instr(0x07, ZP0, SLO, 5), Instr(0x08, IMP, PHP, 3), Instr(0x09, IMM, ORA, 2), Instr(0x0A, ACC, ASL, 2), Instr(0x0B, IMM, ANC, 2), Instr(0x0C, ABS, NOP, 4), Instr(0x0D, ABS, ORA, 4), Instr(0x0E, ABS, ASL, 6), Instr(0x0F, ABS, SLO, 6),
    Instr(0x10, REL, BPL, 2), Instr(0x11, IDY, ORA, 5), Instr(0x12, IMP, XXX, 2), Instr(0x13, IDY, SLO, 8), Instr(0x14, ZPX, NOP, 4), Instr(0x15, ZPX, ORA, 4), Instr(0x16, ZPX, ASL, 6), Instr(0x17, ZPX, SLO, 6), Instr(0x18, IMP, CLC, 2), Instr(0x19, ABY, ORA, 4), Instr(0x1A, IMP, NOP, 2), Instr(0x1B, ABY, SLO, 7), Instr(0x1C, ABX, IGN, 4), Instr(0x1D, ABX, ORA, 4), Instr(0x1E, ABX, ASL, 7), Instr(0x1F, ABX, SLO, 7),
    Instr(0x20, ABS, JSR, 6), Instr(0x21, IDX, AND, 6), Instr(0x22, IMP, XXX, 2), Instr(0x23, IDX, RLA, 8), Instr(0x24, ZP0, BIT, 3), Instr(0x25, ZP0, AND, 3), Instr(0x26, ZP0, ROL, 5), Instr(0x27, ZP0, RLA, 5), Instr(0x28, IMP, PLP, 4), Instr(0x29, IMM, AND, 2), Instr(0x2A, ACC, ROL, 2), Instr(0x2B, IMM, ANC, 2), Instr(0x2C, ABS, BIT, 4), Instr(0x2D, ABS, AND, 4), Instr(0x2E, ABS, ROL, 6), Instr(0x2F, ABS, RLA, 6),
    Instr(0x30, REL, BMI, 2), Instr(0x31, IDY, AND, 5), Instr(0x32, IMP, XXX, 2), Instr(0x33, IDY, RLA, 8), Instr(0x34, ZPX, NOP, 4), Instr(0x35, ZPX, AND, 4), Instr(0x36, ZPX, ROL, 6), Instr(0x37, ZPX, RLA, 6), Instr(0x38, IMP, SEC, 2), Instr(0x39, ABY, AND, 4), Instr(0x3A, IMP, NOP, 2), Instr(0x3B, ABY, RLA, 7), Instr(0x3C, ABX, IGN, 4), Instr(0x3D, ABX, AND, 4), Instr(0x3E, ABX, ROL, 7), Instr(0x3F, ABX, RLA, 7),
    Instr(0x40, IMP, RTI, 6), Instr(0x41, IDX, EOR, 6), Instr(0x42, IMP, XXX, 2), Instr(0x43, IDX, SRE, 8), Instr(0x44, ZP0, NOP, 3), Instr(0x45, ZP0, EOR, 3), Instr(0x46, ZP0, LSR, 5), Instr(0x47, ZP0, SRE, 5), Instr(0x48, IMP, PHA, 3), Instr(0x49, IMM, EOR, 2), Instr(0x4A, ACC, LSR, 2), Instr(0x4B, IMM, ALR, 2), Instr(0x4C, ABS, JMP, 3), Instr(0x4D, ABS, EOR, 4), Instr(0x4E, ABS, LSR, 6), Instr(0x4F, ABS, SRE, 6),
    Instr(0x50, REL, BVC, 2), Instr(0x51, IDY, EOR, 5), Instr(0x52, IMP, XXX, 2), Instr(0x53, IDY, SRE, 8), Instr(0x54, ZPX, NOP, 4), Instr(0x55, ZPX, EOR, 4), Instr(0x56, ZPX, LSR, 6), Instr(0x57, ZPX, SRE, 6), Instr(0x58, IMP, CLI, 2), Instr(0x59, ABY, EOR, 4), Instr(0x5A, IMP, NOP, 2), Instr(0x5B, ABY, SRE, 7), Instr(0x5C, ABX, IGN, 4), Instr(0x5D, ABX, EOR, 4), Instr(0x5E, ABX, LSR, 7), Instr(0x5F, ABX, SRE, 7),
    Instr(0x60, IMP, RTS, 6), Instr(0x61, IDX, ADC, 6), Instr(0x62, IMP, XXX, 2), Instr(0x63, IDX, RRA, 8), Instr(0x64, ZP0, NOP, 3), Instr(0x65, ZP0, ADC, 3), Instr(0x66, ZP0, ROR, 5), Instr(0x67, ZP0, RRA, 5), Instr(0x68, IMP, PLA, 4), Instr(0x69, IMM, ADC, 2), Instr(0x6A, ACC, ROR, 2), Instr(0x6B, IMM, ARR, 2), Instr(0x6C, IND, JMP, 5), Instr(0x6D, ABS, ADC, 4), Instr(0x6E, ABS, ROR, 6), Instr(0x6F, ABS, RRA, 6),
    Instr(0x70, REL, BVS, 2), Instr(0x71, IDY, ADC, 5), Instr(0x72, IMP, XXX, 2), Instr(0x73, IDY, RRA, 8), Instr(0x74, ZPX, NOP, 4), Instr(0x75, ZPX, ADC, 4), Instr(0x76, ZPX, ROR, 6), Instr(0x77, ZPX, RRA, 6), Instr(0x78, IMP, SEI, 2), Instr(0x79, ABY, ADC, 4), Instr(0x7A, IMP, NOP, 2), Instr(0x7B, ABY, RRA, 7), Instr(0x7C, ABX, IGN, 4), Instr(0x7D, ABX, ADC, 4), Instr(0x7E, ABX, ROR, 7), Instr(0x7F, ABX, RRA, 7),
    Instr(0x80, IMM, SKB, 2), Instr(0x81, IDX, STA, 6), Instr(0x82, IMM, SKB, 2), Instr(0x83, IDX, SAX, 6), Instr(0x84, ZP0, STY, 3), Instr(0x85, ZP0, STA, 3), Instr(0x86, ZP0, STX, 3), Instr(0x87, ZP0, SAX, 3), Instr(0x88, IMP, DEY, 2), Instr(0x89, IMM, SKB, 2), Instr(0x8A, IMP, TXA, 2), Instr(0x8B, IMM, XAA, 2), Instr(0x8C, ABS, STY, 4), Instr(0x8D, ABS, STA, 4), Instr(0x8E, ABS, STX, 4), Instr(0x8F, ABS, SAX, 4),
    Instr(0x90, REL, BCC, 2), Instr(0x91, IDY, STA, 6), Instr(0x92, IMP, XXX, 2), Instr(0x93, IDY, AHX, 6), Instr(0x94, ZPX, STY, 4), Instr(0x95, ZPX, STA, 4), Instr(0x96, ZPY, STX, 4), Instr(0x97, ZPY, SAX, 4), Instr(0x98, IMP, TYA, 2), Instr(0x99, ABY, STA, 5), Instr(0x9A, IMP, TXS, 2), Instr(0x9B, ABY, TAS, 5), Instr(0x9C, ABX, SYA, 5), Instr(0x9D, ABX, STA, 5), Instr(0x9E, ABY, SXA, 5), Instr(0x9F, ABY, AHX, 5),
    Instr(0xA0, IMM, LDY, 2), Instr(0xA1, IDX, LDA, 6), Instr(0xA2, IMM, LDX, 2), Instr(0xA3, IDX, LAX, 6), Instr(0xA4, ZP0, LDY, 3), Instr(0xA5, ZP0, LDA, 3), Instr(0xA6, ZP0, LDX, 3), Instr(0xA7, ZP0, LAX, 3), Instr(0xA8, IMP, TAY, 2), Instr(0xA9, IMM, LDA, 2), Instr(0xAA, IMP, TAX, 2), Instr(0xAB, IMM, LAX, 2), Instr(0xAC, ABS, LDY, 4), Instr(0xAD, ABS, LDA, 4), Instr(0xAE, ABS, LDX, 4), Instr(0xAF, ABS, LAX, 4),
    Instr(0xB0, REL, BCS, 2), Instr(0xB1, IDY, LDA, 5), Instr(0xB2, IMP, XXX, 2), Instr(0xB3, IDY, LAX, 5), Instr(0xB4, ZPX, LDY, 4), Instr(0xB5, ZPX, LDA, 4), Instr(0xB6, ZPY, LDX, 4), Instr(0xB7, ZPY, LAX, 4), Instr(0xB8, IMP, CLV, 2), Instr(0xB9, ABY, LDA, 4), Instr(0xBA, IMP, TSX, 2), Instr(0xBB, ABY, LAS, 4), Instr(0xBC, ABX, LDY, 4), Instr(0xBD, ABX, LDA, 4), Instr(0xBE, ABY, LDX, 4), Instr(0xBF, ABY, LAX, 4),
    Instr(0xC0, IMM, CPY, 2), Instr(0xC1, IDX, CMP, 6), Instr(0xC2, IMM, SKB, 2), Instr(0xC3, IDX, DCP, 8), Instr(0xC4, ZP0, CPY, 3), Instr(0xC5, ZP0, CMP, 3), Instr(0xC6, ZP0, DEC, 5), Instr(0xC7, ZP0, DCP, 5), Instr(0xC8, IMP, INY, 2), Instr(0xC9, IMM, CMP, 2), Instr(0xCA, IMP, DEX, 2), Instr(0xCB, IMM, AXS, 2), Instr(0xCC, ABS, CPY, 4), Instr(0xCD, ABS, CMP, 4), Instr(0xCE, ABS, DEC, 6), Instr(0xCF, ABS, DCP, 6),
    Instr(0xD0, REL, BNE, 2), Instr(0xD1, IDY, CMP, 5), Instr(0xD2, IMP, XXX, 2), Instr(0xD3, IDY, DCP, 8), Instr(0xD4, ZPX, NOP, 4), Instr(0xD5, ZPX, CMP, 4), Instr(0xD6, ZPX, DEC, 6), Instr(0xD7, ZPX, DCP, 6), Instr(0xD8, IMP, CLD, 2), Instr(0xD9, ABY, CMP, 4), Instr(0xDA, IMP, NOP, 2), Instr(0xDB, ABY, DCP, 7), Instr(0xDC, ABX, IGN, 4), Instr(0xDD, ABX, CMP, 4), Instr(0xDE, ABX, DEC, 7), Instr(0xDF, ABX, DCP, 7),
    Instr(0xE0, IMM, CPX, 2), Instr(0xE1, IDX, SBC, 6), Instr(0xE2, IMM, SKB, 2), Instr(0xE3, IDX, ISB, 8), Instr(0xE4, ZP0, CPX, 3), Instr(0xE5, ZP0, SBC, 3), Instr(0xE6, ZP0, INC, 5), Instr(0xE7, ZP0, ISB, 5), Instr(0xE8, IMP, INX, 2), Instr(0xE9, IMM, SBC, 2), Instr(0xEA, IMP, NOP, 2), Instr(0xEB, IMM, SBC, 2), Instr(0xEC, ABS, CPX, 4), Instr(0xED, ABS, SBC, 4), Instr(0xEE, ABS, INC, 6), Instr(0xEF, ABS, ISB, 6),
    Instr(0xF0, REL, BEQ, 2), Instr(0xF1, IDY, SBC, 5), Instr(0xF2, IMP, XXX, 2), Instr(0xF3, IDY, ISB, 8), Instr(0xF4, ZPX, NOP, 4), Instr(0xF5, ZPX, SBC, 4), Instr(0xF6, ZPX, INC, 6), Instr(0xF7, ZPX, ISB, 6), Instr(0xF8, IMP, SED, 2), Instr(0xF9, ABY, SBC, 4), Instr(0xFA, IMP, NOP, 2), Instr(0xFB, ABY, ISB, 7), Instr(0xFC, ABX, IGN, 4), Instr(0xFD, ABX, SBC, 4), Instr(0xFE, ABX, INC, 7), Instr(0xFF, ABX, ISB, 7),
];

#[rustfmt::skip]
#[allow(clippy::upper_case_acronyms)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
// List of all CPU official and unofficial operations
// http://wiki.nesdev.com/w/index.php/6502_instructions
// http://archive.6502.org/datasheets/rockwell_r650x_r651x.pdf
#[must_use]
pub enum Operation {
    ADC, AND, ASL, BCC, BCS, BEQ, BIT, BMI, BNE, BPL, BRK, BVC, BVS, CLC, CLD, CLI, CLV, CMP, CPX,
    CPY, DEC, DEX, DEY, EOR, INC, INX, INY, JMP, JSR, LDA, LDX, LDY, LSR, NOP, ORA, PHA, PHP, PLA,
    PLP, ROL, ROR, RTI, RTS, SBC, SEC, SED, SEI, STA, STX, STY, TAX, TAY, TSX, TXA, TXS, TYA,
    // "Unofficial" opcodes
    SKB, IGN, ISB, DCP, AXS, LAS, LAX, AHX, SAX, XAA, SXA, RRA, TAS, SYA, ARR, SRE, ALR, RLA, ANC,
    SLO, XXX
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[allow(clippy::upper_case_acronyms)]
#[rustfmt::skip]
#[must_use]
pub enum AddrMode {
    IMM,
    ZP0, ZPX, ZPY,
    ABS, ABX, ABY,
    IND, IDX, IDY,
    REL, ACC, IMP,
}

use AddrMode::{ABS, ABX, ABY, ACC, IDX, IDY, IMM, IMP, IND, REL, ZP0, ZPX, ZPY};
use Operation::{
    ADC, AHX, ALR, ANC, AND, ARR, ASL, AXS, BCC, BCS, BEQ, BIT, BMI, BNE, BPL, BRK, BVC, BVS, CLC,
    CLD, CLI, CLV, CMP, CPX, CPY, DCP, DEC, DEX, DEY, EOR, IGN, INC, INX, INY, ISB, JMP, JSR, LAS,
    LAX, LDA, LDX, LDY, LSR, NOP, ORA, PHA, PHP, PLA, PLP, RLA, ROL, ROR, RRA, RTI, RTS, SAX, SBC,
    SEC, SED, SEI, SKB, SLO, SRE, STA, STX, STY, SXA, SYA, TAS, TAX, TAY, TSX, TXA, TXS, TYA, XAA,
    XXX,
};

// (opcode, Addressing Mode, Operation, cycles taken)
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[must_use]
pub struct Instr(u8, AddrMode, Operation, usize);

impl Instr {
    #[inline]
    #[must_use]
    pub const fn opcode(&self) -> u8 {
        self.0
    }
    #[inline]
    pub const fn addr_mode(&self) -> AddrMode {
        self.1
    }
    #[inline]
    pub const fn op(&self) -> Operation {
        self.2
    }
    #[inline]
    #[must_use]
    pub const fn cycles(&self) -> usize {
        self.3
    }
}

/// CPU Addressing Modes
///
/// The 6502 can address 64KB from 0x0000 - 0xFFFF. The high byte is usually the page and the
/// low byte the offset into the page. There are 256 total pages of 256 bytes.
impl Cpu {
    /// Accumulator
    /// No additional data is required, but the default target will be the accumulator.
    //  ASL, ROL, LSR, ROR
    //  #  address R/W description
    // --- ------- --- -----------------------------------------------
    //  1    PC     R  fetch opcode, increment PC
    //  2    PC     R  read next instruction byte (and throw it away)
    #[inline]
    pub(super) fn acc(&mut self) {
        let _ = self.read(self.pc); // Cycle 2, Read and throw away
    }

    /// Implied
    /// No additional data is required, but the default target will be the accumulator.
    // #  address R/W description
    //   --- ------- --- -----------------------------------------------
    //    1    PC     R  fetch opcode, increment PC
    //    2    PC     R  read next instruction byte (and throw it away)
    #[inline]
    pub(super) fn imp(&mut self) {
        let _ = self.read(self.pc); // Cycle 2, Read and throw away
    }

    /// Immediate
    /// Uses the next byte as the value, so we'll update the `abs_addr` to the next byte.
    // #  address R/W description
    //   --- ------- --- ------------------------------------------
    //    1    PC     R  fetch opcode, increment PC
    //    2    PC     R  fetch value, increment PC
    #[inline]
    pub(super) fn imm(&mut self) {
        self.abs_addr = self.pc;
        self.pc = self.pc.wrapping_add(1);
    }

    /// Zero Page
    /// Accesses the first 0xFF bytes of the address range, so this only requires one extra byte
    /// instead of the usual two.
    //  Read instructions (LDA, LDX, LDY, EOR, AND, ORA, ADC, SBC, CMP, BIT,
    //                    LAX, NOP)

    //     #  address R/W description
    //    --- ------- --- ------------------------------------------
    //     1    PC     R  fetch opcode, increment PC
    //     2    PC     R  fetch address, increment PC
    //     3  address  R  read from effective address

    //  Read-Modify-Write instructions (ASL, LSR, ROL, ROR, INC, DEC,
    //                                  SLO, SRE, RLA, RRA, ISB, DCP)

    //     #  address R/W description
    //    --- ------- --- ------------------------------------------
    //     1    PC     R  fetch opcode, increment PC
    //     2    PC     R  fetch address, increment PC
    //     3  address  R  read from effective address
    //     4  address  W  write the value back to effective address,
    //                    and do the operation on it
    //     5  address  W  write the new value to effective address

    //  Write instructions (STA, STX, STY, SAX)

    //     #  address R/W description
    //    --- ------- --- ------------------------------------------
    //     1    PC     R  fetch opcode, increment PC
    //     2    PC     R  fetch address, increment PC
    //     3  address  W  write register to effective address
    #[inline]
    pub(super) fn zp0(&mut self) {
        self.abs_addr = u16::from(self.read_instr_byte()); // Cycle 2
    }

    /// Zero Page w/ X offset
    /// Same as Zero Page, but is offset by adding the x register.
    //  Read instructions (LDA, LDX, LDY, EOR, AND, ORA, ADC, SBC, CMP, BIT,
    //                     LAX, NOP)

    //     #   address  R/W description
    //    --- --------- --- ------------------------------------------
    //     1     PC      R  fetch opcode, increment PC
    //     2     PC      R  fetch address, increment PC
    //     3   address   R  read from address, add index register to it
    //     4  address+X* R  read from effective address

    //           * The high byte of the effective address is always zero,
    //             i.e. page boundary crossings are not handled.

    //  Read-Modify-Write instructions (ASL, LSR, ROL, ROR, INC, DEC,
    //                                  SLO, SRE, RLA, RRA, ISB, DCP)

    //     #   address  R/W description
    //    --- --------- --- ---------------------------------------------
    //     1     PC      R  fetch opcode, increment PC
    //     2     PC      R  fetch address, increment PC
    //     3   address   R  read from address, add index register X to it
    //     4  address+X* R  read from effective address
    //     5  address+X* W  write the value back to effective address,
    //                      and do the operation on it
    //     6  address+X* W  write the new value to effective address

    //    Note: * The high byte of the effective address is always zero,
    //            i.e. page boundary crossings are not handled.

    //  Write instructions (STA, STX, STY, SAX)

    //     #   address  R/W description
    //    --- --------- --- -------------------------------------------
    //     1     PC      R  fetch opcode, increment PC
    //     2     PC      R  fetch address, increment PC
    //     3   address   R  read from address, add index register to it
    //     4  address+X* W  write to effective address

    //           * The high byte of the effective address is always zero,
    //             i.e. page boundary crossings are not handled.
    #[inline]
    pub(super) fn zpx(&mut self) {
        let addr = u16::from(self.read_instr_byte()); // Cycle 2
        let _ = self.read(addr); // Cycle 3
        self.abs_addr = addr.wrapping_add(self.x.into()) & 0x00FF;
    }

    /// Zero Page w/ Y offset
    /// Same as Zero Page, but is offset by adding the y register.
    //  Read instructions (LDX, LAX)

    //     #   address  R/W description
    //    --- --------- --- ------------------------------------------
    //     1     PC      R  fetch opcode, increment PC
    //     2     PC      R  fetch address, increment PC
    //     3   address   R  read from address, add index register to it
    //     4  address+Y* R  read from effective address

    //           * The high byte of the effective address is always zero,
    //             i.e. page boundary crossings are not handled.

    //  Write instructions (STX, SAX)

    //     #   address  R/W description
    //    --- --------- --- -------------------------------------------
    //     1     PC      R  fetch opcode, increment PC
    //     2     PC      R  fetch address, increment PC
    //     3   address   R  read from address, add index register to it
    //     4  address+Y* W  write to effective address

    //           * The high byte of the effective address is always zero,
    //             i.e. page boundary crossings are not handled.
    #[inline]
    pub(super) fn zpy(&mut self) {
        let addr = u16::from(self.read_instr_byte()); // Cycle 2
        let _ = self.read(addr); // Cycle 3
        self.abs_addr = addr.wrapping_add(self.y.into()) & 0x00FF;
    }

    /// Relative
    /// This mode is only used by branching instructions. The address must be between -128 and +127,
    /// allowing the branching instruction to move backward or forward relative to the current
    /// program counter.
    //    #   address  R/W description
    //   --- --------- --- ---------------------------------------------
    //    1     PC      R  fetch opcode, increment PC
    //    2     PC      R  fetch fetched_data, increment PC
    //    3     PC      R  Fetch opcode of next instruction,
    //                     If branch is taken, add fetched_data to PCL.
    //                     Otherwise increment PC.
    //    4+    PC*     R  Fetch opcode of next instruction.
    //                     Fix PCH. If it did not change, increment PC.
    //    5!    PC      R  Fetch opcode of next instruction,
    //                     increment PC.

    //   Notes: The opcode fetch of the next instruction is included to
    //          this diagram for illustration purposes. When determining
    //          real execution times, remember to subtract the last
    //          cycle.

    //          * The high byte of Program Counter (PCH) may be invalid
    //            at this time, i.e. it may be smaller or bigger by $100.

    //          + If branch is taken, this cycle will be executed.

    //          ! If branch occurs to different page, this cycle will be
    //            executed.
    #[inline]
    pub(super) fn rel(&mut self) {
        self.rel_addr = u16::from(self.read_instr_byte()); // Cycle 2
    }

    /// Absolute
    /// Uses a full 16-bit address as the next value.
    //  Read instructions (LDA, LDX, LDY, EOR, AND, ORA, ADC, SBC, CMP, BIT,
    //                     LAX, NOP)
    //
    //     #  address R/W description
    //    --- ------- --- ------------------------------------------
    //     1    PC     R  fetch opcode, increment PC
    //     2    PC     R  fetch low byte of address, increment PC
    //     3    PC     R  fetch high byte of address, increment PC
    //     4  address  R  read from effective address

    //  Read-Modify-Write instructions (ASL, LSR, ROL, ROR, INC, DEC,
    //                                  SLO, SRE, RLA, RRA, ISB, DCP)
    //
    //     #  address R/W description
    //    --- ------- --- ------------------------------------------
    //     1    PC     R  fetch opcode, increment PC
    //     2    PC     R  fetch low byte of address, increment PC
    //     3    PC     R  fetch high byte of address, increment PC
    //     4  address  R  read from effective address
    //     5  address  W  write the value back to effective address,
    //                    and do the operation on it
    //     6  address  W  write the new value to effective address

    //  Write instructions (STA, STX, STY, SAX)
    //
    //     #  address R/W description
    //    --- ------- --- ------------------------------------------
    //     1    PC     R  fetch opcode, increment PC
    //     2    PC     R  fetch low byte of address, increment PC
    //     3    PC     R  fetch high byte of address, increment PC
    //     4  address  W  write register to effective address
    #[inline]
    pub(super) fn abs(&mut self) {
        self.abs_addr = self.read_instr_word(); // Cycle 2 & 3
    }

    /// Absolute w/ X offset
    /// Same as Absolute, but is offset by adding the x register. If a page boundary is crossed, an
    /// additional clock is required.
    // Read instructions (LDA, LDX, LDY, EOR, AND, ORA, ADC, SBC, CMP, BIT,
    //                    LAX, LAE, SHS, NOP)

    //    #   address  R/W description
    //   --- --------- --- ------------------------------------------
    //    1     PC      R  fetch opcode, increment PC
    //    2     PC      R  fetch low byte of address, increment PC
    //    3     PC      R  fetch high byte of address,
    //                     add index register to low address byte,
    //                     increment PC
    //    4  address+X* R  read from effective address,
    //                     fix the high byte of effective address
    //    5+ address+X  R  re-read from effective address

    //          * The high byte of the effective address may be invalid
    //            at this time, i.e. it may be smaller by $100.

    //          + This cycle will be executed only if the effective address
    //            was invalid during cycle #4, i.e. page boundary was crossed.

    // Read-Modify-Write instructions (ASL, LSR, ROL, ROR, INC, DEC,
    //                                 SLO, SRE, RLA, RRA, ISB, DCP)

    //    #   address  R/W description
    //   --- --------- --- ------------------------------------------
    //    1    PC       R  fetch opcode, increment PC
    //    2    PC       R  fetch low byte of address, increment PC
    //    3    PC       R  fetch high byte of address,
    //                     add index register X to low address byte,
    //                     increment PC
    //    4  address+X* R  read from effective address,
    //                     fix the high byte of effective address
    //    5  address+X  R  re-read from effective address
    //    6  address+X  W  write the value back to effective address,
    //                     and do the operation on it
    //    7  address+X  W  write the new value to effective address

    //   Notes: * The high byte of the effective address may be invalid
    //            at this time, i.e. it may be smaller by $100.

    // Write instructions (STA, STX, STY, SHA, SHX, SHY)

    //    #   address  R/W description
    //   --- --------- --- ------------------------------------------
    //    1     PC      R  fetch opcode, increment PC
    //    2     PC      R  fetch low byte of address, increment PC
    //    3     PC      R  fetch high byte of address,
    //                     add index register to low address byte,
    //                     increment PC
    //    4  address+X* R  read from effective address,
    //                     fix the high byte of effective address
    //    5  address+X  W  write to effective address

    //          * The high byte of the effective address may be invalid
    //            at this time, i.e. it may be smaller by $100. Because
    //            the processor cannot undo a write to an invalid
    //            address, it always reads from the address first.
    #[inline]
    pub(super) fn abx(&mut self) {
        let addr = self.read_instr_word(); // Cycle 2 & 3
        self.abs_addr = addr.wrapping_add(self.x.into());
        // Cycle 4 Read with fixed high byte
        self.fetched_data = self.read((addr & 0xFF00) | (self.abs_addr & 0x00FF));
    }

    /// Absolute w/ Y offset
    /// Same as Absolute, but is offset by adding the y register. If a page boundary is crossed, an
    /// additional clock is required.
    // Read instructions (LDA, LDX, LDY, EOR, AND, ORA, ADC, SBC, CMP, BIT,
    //                    LAX, LAE, SHS, NOP)

    //    #   address  R/W description
    //   --- --------- --- ------------------------------------------
    //    1     PC      R  fetch opcode, increment PC
    //    2     PC      R  fetch low byte of address, increment PC
    //    3     PC      R  fetch high byte of address,
    //                     add index register to low address byte,
    //                     increment PC
    //    4  address+Y* R  read from effective address,
    //                     fix the high byte of effective address
    //    5+ address+Y  R  re-read from effective address

    //          * The high byte of the effective address may be invalid
    //            at this time, i.e. it may be smaller by $100.

    //          + This cycle will be executed only if the effective address
    //            was invalid during cycle #4, i.e. page boundary was crossed.

    // Read-Modify-Write instructions (ASL, LSR, ROL, ROR, INC, DEC,
    //                                 SLO, SRE, RLA, RRA, ISB, DCP)

    //    #   address  R/W description
    //   --- --------- --- ------------------------------------------
    //    1    PC       R  fetch opcode, increment PC
    //    2    PC       R  fetch low byte of address, increment PC
    //    3    PC       R  fetch high byte of address,
    //                     add index register Y to low address byte,
    //                     increment PC
    //    4  address+Y* R  read from effective address,
    //                     fix the high byte of effective address
    //    5  address+Y  R  re-read from effective address
    //    6  address+Y  W  write the value back to effective address,
    //                     and do the operation on it
    //    7  address+Y  W  write the new value to effective address

    //   Notes: * The high byte of the effective address may be invalid
    //            at this time, i.e. it may be smaller by $100.

    // Write instructions (STA, STX, STY, SHA, SHX, SHY)

    //    #   address  R/W description
    //   --- --------- --- ------------------------------------------
    //    1     PC      R  fetch opcode, increment PC
    //    2     PC      R  fetch low byte of address, increment PC
    //    3     PC      R  fetch high byte of address,
    //                     add index register to low address byte,
    //                     increment PC
    //    4  address+Y* R  read from effective address,
    //                     fix the high byte of effective address
    //    5  address+Y  W  write to effective address

    //          * The high byte of the effective address may be invalid
    //            at this time, i.e. it may be smaller by $100. Because
    //            the processor cannot undo a write to an invalid
    //            address, it always reads from the address first.
    #[inline]
    pub(super) fn aby(&mut self) {
        let addr = self.read_instr_word(); // Cycles 2 & 3
        self.abs_addr = addr.wrapping_add(self.y.into());
        // Cycle 4 Read with fixed high byte
        self.fetched_data = self.read((addr & 0xFF00) | (self.abs_addr & 0x00FF));
    }

    /// Indirect (JMP)
    /// The next 16-bit address is used to get the actual 16-bit address. This instruction has
    /// a bug in the original hardware. If the lo byte is 0xFF, the hi byte would cross a page
    /// boundary. However, this doesn't work correctly on the original hardware and instead
    /// wraps back around to 0.
    //    #   address  R/W description
    //   --- --------- --- ------------------------------------------
    //    1     PC      R  fetch opcode, increment PC
    //    2     PC      R  fetch pointer address low, increment PC
    //    3     PC      R  fetch pointer address high, increment PC
    //    4   pointer   R  fetch low address to latch
    //    5  pointer+1* R  fetch PCH, copy latch to PCL

    //   Note: * The PCH will always be fetched from the same page
    //           than PCL, i.e. page boundary crossing is not handled.

    //            How Real Programmers Acknowledge Interrupts
    #[inline]
    pub(super) fn ind(&mut self) {
        let addr = self.read_instr_word();
        if addr & 0xFF == 0xFF {
            // Simulate bug
            self.abs_addr = (u16::from(self.read(addr & 0xFF00)) << 8) | u16::from(self.read(addr));
        } else {
            // Normal behavior
            self.abs_addr = self.read_word(addr);
        }
    }

    /// Indirect X
    /// The next 8-bit address is offset by the X register to get the actual 16-bit address from
    /// page 0x00.
    // Read instructions (LDA, ORA, EOR, AND, ADC, CMP, SBC, LAX)

    //    #    address   R/W description
    //   --- ----------- --- ------------------------------------------
    //    1      PC       R  fetch opcode, increment PC
    //    2      PC       R  fetch pointer address, increment PC
    //    3    pointer    R  read from the address, add X to it
    //    4   pointer+X   R  fetch effective address low
    //    5  pointer+X+1  R  fetch effective address high
    //    6    address    R  read from effective address

    //   Note: The effective address is always fetched from zero page,
    //         i.e. the zero page boundary crossing is not handled.

    // Read-Modify-Write instructions (SLO, SRE, RLA, RRA, ISB, DCP)

    //    #    address   R/W description
    //   --- ----------- --- ------------------------------------------
    //    1      PC       R  fetch opcode, increment PC
    //    2      PC       R  fetch pointer address, increment PC
    //    3    pointer    R  read from the address, add X to it
    //    4   pointer+X   R  fetch effective address low
    //    5  pointer+X+1  R  fetch effective address high
    //    6    address    R  read from effective address
    //    7    address    W  write the value back to effective address,
    //                       and do the operation on it
    //    8    address    W  write the new value to effective address

    //   Note: The effective address is always fetched from zero page,
    //         i.e. the zero page boundary crossing is not handled.

    // Write instructions (STA, SAX)

    //    #    address   R/W description
    //   --- ----------- --- ------------------------------------------
    //    1      PC       R  fetch opcode, increment PC
    //    2      PC       R  fetch pointer address, increment PC
    //    3    pointer    R  read from the address, add X to it
    //    4   pointer+X   R  fetch effective address low
    //    5  pointer+X+1  R  fetch effective address high
    //    6    address    W  write to effective address

    //   Note: The effective address is always fetched from zero page,
    //         i.e. the zero page boundary crossing is not handled.
    #[inline]
    pub(super) fn idx(&mut self) {
        let addr = self.read_instr_byte(); // Cycle 2
        let _ = self.read(u16::from(addr)); // Cycle 3
        let addr = addr.wrapping_add(self.x);
        self.abs_addr = self.read_word_zp(addr); // Cycles 4 & 5
    }

    /// Indirect Y
    /// The next 8-bit address is read to get a 16-bit address from page 0x00, which is then offset
    /// by the Y register. If a page boundary is crossed, add a clock cycle.
    // Read instructions (LDA, EOR, AND, ORA, ADC, SBC, CMP)

    //    #    address   R/W description
    //   --- ----------- --- ------------------------------------------
    //    1      PC       R  fetch opcode, increment PC
    //    2      PC       R  fetch pointer address, increment PC
    //    3    pointer    R  fetch effective address low
    //    4   pointer+1   R  fetch effective address high,
    //                       add Y to low byte of effective address
    //    5   address+Y*  R  read from effective address,
    //                       fix high byte of effective address
    //    6+  address+Y   R  read from effective address

    //   Notes: The effective address is always fetched from zero page,
    //          i.e. the zero page boundary crossing is not handled.

    //          * The high byte of the effective address may be invalid
    //            at this time, i.e. it may be smaller by $100.

    //          + This cycle will be executed only if the effective address
    //            was invalid during cycle #5, i.e. page boundary was crossed.

    // Read-Modify-Write instructions (SLO, SRE, RLA, RRA, ISB, DCP)

    //    #    address   R/W description
    //   --- ----------- --- ------------------------------------------
    //    1      PC       R  fetch opcode, increment PC
    //    2      PC       R  fetch pointer address, increment PC
    //    3    pointer    R  fetch effective address low
    //    4   pointer+1   R  fetch effective address high,
    //                       add Y to low byte of effective address
    //    5   address+Y*  R  read from effective address,
    //                       fix high byte of effective address
    //    6   address+Y   R  re-read from effective address
    //    7   address+Y   W  write the value back to effective address,
    //                       and do the operation on it
    //    8   address+Y   W  write the new value to effective address

    //   Notes: The effective address is always fetched from zero page,
    //          i.e. the zero page boundary crossing is not handled.

    //          * The high byte of the effective address may be invalid
    //            at this time, i.e. it may be smaller by $100.

    // Write instructions (STA, SHA)

    //    #    address   R/W description
    //   --- ----------- --- ------------------------------------------
    //    1      PC       R  fetch opcode, increment PC
    //    2      PC       R  fetch pointer address, increment PC
    //    3    pointer    R  fetch effective address low
    //    4   pointer+1   R  fetch effective address high,
    //                       add Y to low byte of effective address
    //    5   address+Y*  R  read from effective address,
    //                       fix high byte of effective address
    //    6   address+Y   W  write to effective address

    //   Notes: The effective address is always fetched from zero page,
    //          i.e. the zero page boundary crossing is not handled.

    //          * The high byte of the effective address may be invalid
    //            at this time, i.e. it may be smaller by $100.
    #[inline]
    pub(super) fn idy(&mut self) {
        let addr = self.read_instr_byte(); // Cycle 2
        let addr = self.read_word_zp(addr); // Cycles 3 & 4
        self.abs_addr = addr.wrapping_add(self.y.into());
        // Cycle 4 Read with fixed high byte
        self.fetched_data = self.read((addr & 0xFF00) | (self.abs_addr & 0x00FF));
    }
}

/// CPU instructions
impl Cpu {
    /// Storage opcodes

    /// LDA: Load A with M
    #[inline]
    pub(super) fn lda(&mut self) {
        self.fetch_data();
        self.acc = self.fetched_data;
        self.set_zn_status(self.acc);
    }
    /// LDX: Load X with M
    #[inline]
    pub(super) fn ldx(&mut self) {
        self.fetch_data();
        self.x = self.fetched_data;
        self.set_zn_status(self.x);
    }
    /// LDY: Load Y with M
    #[inline]
    pub(super) fn ldy(&mut self) {
        self.fetch_data();
        self.y = self.fetched_data;
        self.set_zn_status(self.y);
    }
    /// STA: Store A into M
    #[inline]
    pub(super) fn sta(&mut self) {
        self.write(self.abs_addr, self.acc);
    }
    /// STX: Store X into M
    #[inline]
    pub(super) fn stx(&mut self) {
        self.write(self.abs_addr, self.x);
    }
    /// STY: Store Y into M
    #[inline]
    pub(super) fn sty(&mut self) {
        self.write(self.abs_addr, self.y);
    }
    /// TAX: Transfer A to X
    #[inline]
    pub(super) fn tax(&mut self) {
        self.x = self.acc;
        self.set_zn_status(self.x);
    }
    /// TAY: Transfer A to Y
    #[inline]
    pub(super) fn tay(&mut self) {
        self.y = self.acc;
        self.set_zn_status(self.y);
    }
    /// TSX: Transfer Stack Pointer to X
    #[inline]
    pub(super) fn tsx(&mut self) {
        self.x = self.sp;
        self.set_zn_status(self.x);
    }
    /// TXA: Transfer X to A
    #[inline]
    pub(super) fn txa(&mut self) {
        self.acc = self.x;
        self.set_zn_status(self.acc);
    }
    /// TXS: Transfer X to Stack Pointer
    #[inline]
    pub(super) fn txs(&mut self) {
        self.sp = self.x;
    }
    /// TYA: Transfer Y to A
    #[inline]
    pub(super) fn tya(&mut self) {
        self.acc = self.y;
        self.set_zn_status(self.acc);
    }

    /// Arithmetic opcodes

    /// ADC: Add M to A with Carry
    #[inline]
    pub(super) fn adc(&mut self) {
        self.fetch_data();
        let a = self.acc;
        let (x1, o1) = self.fetched_data.overflowing_add(a);
        let (x2, o2) = x1.overflowing_add(self.status_bit(Status::C));
        self.acc = x2;
        self.status.set(Status::C, o1 | o2);
        self.status.set(
            Status::V,
            (a ^ self.fetched_data) & 0x80 == 0 && (a ^ self.acc) & 0x80 != 0,
        );
        self.set_zn_status(self.acc);
    }
    /// SBC: Subtract M from A with Carry
    #[inline]
    pub(super) fn sbc(&mut self) {
        self.fetch_data();
        let a = self.acc;
        let (x1, o1) = a.overflowing_sub(self.fetched_data);
        let (x2, o2) = x1.overflowing_sub(1 - self.status_bit(Status::C));
        self.acc = x2;
        self.status.set(Status::C, !(o1 | o2));
        self.status.set(
            Status::V,
            (a ^ self.fetched_data) & 0x80 != 0 && (a ^ self.acc) & 0x80 != 0,
        );
        self.set_zn_status(self.acc);
    }
    /// DEC: Decrement M by One
    #[inline]
    pub(super) fn dec(&mut self) {
        self.fetch_data();
        self.write_fetched(self.fetched_data); // dummy write
        let val = self.fetched_data.wrapping_sub(1);
        self.write_fetched(val);
        self.set_zn_status(val);
    }
    /// DEX: Decrement X by One
    #[inline]
    pub(super) fn dex(&mut self) {
        self.x = self.x.wrapping_sub(1);
        self.set_zn_status(self.x);
    }
    /// DEY: Decrement Y by One
    #[inline]
    pub(super) fn dey(&mut self) {
        self.y = self.y.wrapping_sub(1);
        self.set_zn_status(self.y);
    }
    /// INC: Increment M by One
    #[inline]
    pub(super) fn inc(&mut self) {
        self.fetch_data();
        self.write_fetched(self.fetched_data); // dummy write
        let val = self.fetched_data.wrapping_add(1);
        self.set_zn_status(val);
        self.write_fetched(val);
    }
    /// INX: Increment X by One
    #[inline]
    pub(super) fn inx(&mut self) {
        self.x = self.x.wrapping_add(1);
        self.set_zn_status(self.x);
    }
    /// INY: Increment Y by One
    #[inline]
    pub(super) fn iny(&mut self) {
        self.y = self.y.wrapping_add(1);
        self.set_zn_status(self.y);
    }

    /// Bitwise opcodes

    /// AND: "And" M with A
    #[inline]
    pub(super) fn and(&mut self) {
        self.fetch_data();
        self.acc &= self.fetched_data;
        self.set_zn_status(self.acc);
    }
    /// ASL: Shift Left One Bit (M or A)
    #[inline]
    pub(super) fn asl(&mut self) {
        self.fetch_data(); // Cycle 4 & 5
        self.write_fetched(self.fetched_data); // Cycle 6
        self.status.set(Status::C, (self.fetched_data >> 7) & 1 > 0);
        let val = self.fetched_data.wrapping_shl(1);
        self.set_zn_status(val);
        self.write_fetched(val); // Cycle 7
    }
    /// BIT: Test Bits in M with A (Affects N, V, and Z)
    #[inline]
    pub(super) fn bit(&mut self) {
        self.fetch_data();
        let val = self.acc & self.fetched_data;
        self.status.set(Status::Z, val == 0);
        self.status.set(Status::N, self.fetched_data & (1 << 7) > 0);
        self.status.set(Status::V, self.fetched_data & (1 << 6) > 0);
    }
    /// EOR: "Exclusive-Or" M with A
    #[inline]
    pub(super) fn eor(&mut self) {
        self.fetch_data();
        self.acc ^= self.fetched_data;
        self.set_zn_status(self.acc);
    }
    /// LSR: Shift Right One Bit (M or A)
    #[inline]
    pub(super) fn lsr(&mut self) {
        self.fetch_data(); // Cycle 4 & 5
        self.write_fetched(self.fetched_data); // Cycle 6
        self.status.set(Status::C, self.fetched_data & 1 > 0);
        let val = self.fetched_data.wrapping_shr(1);
        self.set_zn_status(val);
        self.write_fetched(val); // Cycle 7
    }
    /// ORA: "OR" M with A
    #[inline]
    pub(super) fn ora(&mut self) {
        self.fetch_data();
        self.acc |= self.fetched_data;
        self.set_zn_status(self.acc);
    }
    /// ROL: Rotate One Bit Left (M or A)
    #[inline]
    pub(super) fn rol(&mut self) {
        self.fetch_data();
        self.write_fetched(self.fetched_data); // dummy write
        let old_c = self.status_bit(Status::C);
        self.status.set(Status::C, (self.fetched_data >> 7) & 1 > 0);
        let val = (self.fetched_data << 1) | old_c;
        self.set_zn_status(val);
        self.write_fetched(val);
    }
    /// ROR: Rotate One Bit Right (M or A)
    #[inline]
    pub(super) fn ror(&mut self) {
        self.fetch_data();
        self.write_fetched(self.fetched_data); // dummy write
        let mut ret = self.fetched_data.rotate_right(1);
        if self.status.intersects(Status::C) {
            ret |= 1 << 7;
        } else {
            ret &= !(1 << 7);
        }
        self.status.set(Status::C, self.fetched_data & 1 > 0);
        self.set_zn_status(ret);
        self.write_fetched(ret);
    }

    /// Branch opcodes

    /// Utility function used by all branch instructions
    #[inline]
    pub(super) fn branch(&mut self) {
        // If an interrupt occurs during the final cycle of a non-pagecrossing branch
        // then it will be ignored until the next instruction completes
        if self.run_irq && !self.prev_run_irq {
            self.run_irq = false;
        }

        self.read(self.pc); // Dummy read

        self.abs_addr = if self.rel_addr & 0x80 == 0x80 {
            self.pc.wrapping_add(self.rel_addr | 0xFF00)
        } else {
            self.pc.wrapping_add(self.rel_addr)
        };
        if Self::pages_differ(self.abs_addr, self.pc) {
            self.read(self.pc); // Dummy read
        }
        self.pc = self.abs_addr;
    }
    /// BCC: Branch on Carry Clear
    #[inline]
    pub(super) fn bcc(&mut self) {
        if !self.status.intersects(Status::C) {
            self.branch();
        }
    }
    /// BCS: Branch on Carry Set
    #[inline]
    pub(super) fn bcs(&mut self) {
        if self.status.intersects(Status::C) {
            self.branch();
        }
    }
    /// BEQ: Branch on Result Zero
    #[inline]
    pub(super) fn beq(&mut self) {
        if self.status.intersects(Status::Z) {
            self.branch();
        }
    }
    /// BMI: Branch on Result Negative
    #[inline]
    pub(super) fn bmi(&mut self) {
        if self.status.intersects(Status::N) {
            self.branch();
        }
    }
    /// BNE: Branch on Result Not Zero
    #[inline]
    pub(super) fn bne(&mut self) {
        if !self.status.intersects(Status::Z) {
            self.branch();
        }
    }
    /// BPL: Branch on Result Positive
    #[inline]
    pub(super) fn bpl(&mut self) {
        if !self.status.intersects(Status::N) {
            self.branch();
        }
    }
    /// BVC: Branch on Overflow Clear
    #[inline]
    pub(super) fn bvc(&mut self) {
        if !self.status.intersects(Status::V) {
            self.branch();
        }
    }
    /// BVS: Branch on Overflow Set
    #[inline]
    pub(super) fn bvs(&mut self) {
        if self.status.intersects(Status::V) {
            self.branch();
        }
    }

    /// Jump opcodes

    /// JMP: Jump to Location
    // #  address R/W description
    //   --- ------- --- -------------------------------------------------
    //    1    PC     R  fetch opcode, increment PC
    //    2    PC     R  fetch low address byte, increment PC
    //    3    PC     R  copy low address byte to PCL, fetch high address
    //                   byte to PCH
    #[inline]
    pub(super) fn jmp(&mut self) {
        self.pc = self.abs_addr;
    }
    /// JSR: Jump to Location Save Return addr
    //  #  address R/W description
    // --- ------- --- -------------------------------------------------
    //  1    PC     R  fetch opcode, increment PC
    //  2    PC     R  fetch low address byte, increment PC
    //  3  $0100,S  R  internal operation (predecrement S?)
    //  4  $0100,S  W  push PCH on stack, decrement S
    //  5  $0100,S  W  push PCL on stack, decrement S
    //  6    PC     R  copy low address byte to PCL, fetch high address
    //                 byte to PCH
    #[inline]
    pub(super) fn jsr(&mut self) {
        let _ = self.read(SP_BASE | u16::from(self.sp)); // Cycle 3
        self.push_word(self.pc.wrapping_sub(1));
        self.pc = self.abs_addr;
    }
    /// RTI: Return from Interrupt
    //  #  address R/W description
    // --- ------- --- -----------------------------------------------
    //  1    PC     R  fetch opcode, increment PC
    //  2    PC     R  read next instruction byte (and throw it away)
    //  3  $0100,S  R  increment S
    //  4  $0100,S  R  pull P from stack, increment S
    //  5  $0100,S  R  pull PCL from stack, increment S
    //  6  $0100,S  R  pull PCH from stack
    #[inline]
    pub(super) fn rti(&mut self) {
        let _ = self.read(SP_BASE | u16::from(self.sp)); // Cycle 3
        self.status = Status::from_bits_truncate(self.pop()); // Cycle 4
        self.status &= !Status::U;
        self.status &= !Status::B;
        self.pc = self.pop_word(); // Cycles 5 & 6
    }
    /// RTS: Return from Subroutine
    //  #  address R/W description
    // --- ------- --- -----------------------------------------------
    //  1    PC     R  fetch opcode, increment PC
    //  2    PC     R  read next instruction byte (and throw it away)
    //  3  $0100,S  R  increment S
    //  4  $0100,S  R  pull PCL from stack, increment S
    //  5  $0100,S  R  pull PCH from stack
    //  6    PC     R  increment PC
    #[inline]
    pub(super) fn rts(&mut self) {
        let _ = self.read(SP_BASE | u16::from(self.sp)); // Cycle 3
        self.pc = self.pop_word().wrapping_add(1); // Cycles 4 & 5
        let _ = self.read(self.pc); // Cycle 6
    }

    ///  Register opcodes

    /// CLC: Clear Carry Flag
    #[inline]
    pub(super) fn clc(&mut self) {
        self.status.set(Status::C, false);
    }
    /// SEC: Set Carry Flag
    #[inline]
    pub(super) fn sec(&mut self) {
        self.status.set(Status::C, true);
    }
    /// CLD: Clear Decimal Mode
    #[inline]
    pub(super) fn cld(&mut self) {
        self.status.set(Status::D, false);
    }
    /// SED: Set Decimal Mode
    #[inline]
    pub(super) fn sed(&mut self) {
        self.status.set(Status::D, true);
    }
    /// CLI: Clear Interrupt Disable Bit
    #[inline]
    pub(super) fn cli(&mut self) {
        self.status.set(Status::I, false);
    }
    /// SEI: Set Interrupt Disable Status
    #[inline]
    pub(super) fn sei(&mut self) {
        self.status.set(Status::I, true);
    }
    /// CLV: Clear Overflow Flag
    #[inline]
    pub(super) fn clv(&mut self) {
        self.status.set(Status::V, false);
    }

    /// Compare opcodes

    /// Utility function used by all compare instructions
    #[inline]
    pub(super) fn compare(&mut self, a: u8, b: u8) {
        let result = a.wrapping_sub(b);
        self.set_zn_status(result);
        self.status.set(Status::C, a >= b);
    }
    /// CMP: Compare M and A
    #[inline]
    pub(super) fn cmp(&mut self) {
        self.fetch_data();
        self.compare(self.acc, self.fetched_data);
    }
    /// CPX: Compare M and X
    #[inline]
    pub(super) fn cpx(&mut self) {
        self.fetch_data();
        self.compare(self.x, self.fetched_data);
    }
    /// CPY: Compare M and Y
    #[inline]
    pub(super) fn cpy(&mut self) {
        self.fetch_data();
        self.compare(self.y, self.fetched_data);
    }

    /// Stack opcodes

    /// PHP: Push Processor Status on Stack
    //  #  address R/W description
    // --- ------- --- -----------------------------------------------
    //  1    PC     R  fetch opcode, increment PC
    //  2    PC     R  read next instruction byte (and throw it away)
    //  3  $0100,S  W  push register on stack, decrement S
    #[inline]
    pub(super) fn php(&mut self) {
        // Set U and B when pushing during PHP and BRK
        self.push((self.status | Status::U | Status::B).bits());
    }
    /// PLP: Pull Processor Status from Stack
    //  #  address R/W description
    // --- ------- --- -----------------------------------------------
    //  1    PC     R  fetch opcode, increment PC
    //  2    PC     R  read next instruction byte (and throw it away)
    //  3  $0100,S  R  increment S
    //  4  $0100,S  R  pull register from stack
    #[inline]
    pub(super) fn plp(&mut self) {
        let _ = self.read(SP_BASE | u16::from(self.sp)); // Cycle 3
        self.status = Status::from_bits_truncate(self.pop());
    }
    /// PHA: Push A on Stack
    //  #  address R/W description
    // --- ------- --- -----------------------------------------------
    //  1    PC     R  fetch opcode, increment PC
    //  2    PC     R  read next instruction byte (and throw it away)
    //  3  $0100,S  W  push register on stack, decrement S
    #[inline]
    pub(super) fn pha(&mut self) {
        self.push(self.acc);
    }
    /// PLA: Pull A from Stack
    //  #  address R/W description
    // --- ------- --- -----------------------------------------------
    //  1    PC     R  fetch opcode, increment PC
    //  2    PC     R  read next instruction byte (and throw it away)
    //  3  $0100,S  R  increment S
    //  4  $0100,S  R  pull register from stack
    #[inline]
    pub(super) fn pla(&mut self) {
        let _ = self.read(SP_BASE | u16::from(self.sp)); // Cycle 3
        self.acc = self.pop();
        self.set_zn_status(self.acc);
    }

    /// System opcodes

    /// BRK: Force Break Interrupt
    //  #  address R/W description
    // --- ------- --- -----------------------------------------------
    //  1    PC     R  fetch opcode, increment PC
    //  2    PC     R  read next instruction byte (and throw it away),
    //                 increment PC
    //  3  $0100,S  W  push PCH on stack (with B flag set), decrement S
    //  4  $0100,S  W  push PCL on stack, decrement S
    //  5  $0100,S  W  push P on stack, decrement S
    //  6   $FFFE   R  fetch PCL
    //  7   $FFFF   R  fetch PCH
    #[inline]
    pub(super) fn brk(&mut self) {
        self.fetch_data(); // throw away
        self.push_word(self.pc);

        // Pushing status to the stack has to happen after checking NMI since it can hijack the BRK
        // IRQ when it occurs between cycles 4 and 5.
        // https://www.nesdev.org/wiki/CPU_interrupts#Interrupt_hijacking
        //
        // Set U and B when pushing during PHP and BRK
        let status = (self.status | Status::U | Status::B).bits();

        if self.nmi {
            self.nmi = false;
            self.push(status);
            self.status.set(Status::I, true);

            self.pc = self.read_word(NMI_VECTOR);
            if self.debugging {
                log::trace!("NMI: {}", self.cycle);
            }
        } else {
            self.push(status);
            self.status.set(Status::I, true);

            self.pc = self.read_word(IRQ_VECTOR);
            if self.debugging {
                log::trace!("IRQ: {}", self.cycle);
            }
        }
        // Prevent NMI from triggering immediately after BRK
        log::trace!(
            "Suppress NMI after BRK: {}, {} -> false",
            self.cycle,
            self.prev_nmi,
        );
        self.prev_nmi = false;
    }
    /// NOP: No Operation
    #[inline]
    pub(super) fn nop(&mut self) {
        self.fetch_data(); // throw away
    }

    /// Unofficial opcodes

    /// SKB: Like NOP
    #[inline]
    pub(super) fn skb(&mut self) {
        self.fetch_data();
    }

    /// IGN: Like NOP, but can cross page boundary
    #[inline]
    pub(super) fn ign(&mut self) {
        self.fetch_data();
    }

    /// XXX: Captures all unimplemented opcodes
    #[inline]
    pub(super) fn xxx(&mut self) {
        self.corrupted = true;
        log::error!(
            "Invalid opcode ${:02X} {:?} #{:?} encountered!",
            self.instr.opcode(),
            self.instr.op(),
            self.instr.addr_mode(),
        );
    }
    /// ISC/ISB: Shortcut for INC then SBC
    #[inline]
    pub(super) fn isb(&mut self) {
        // INC
        self.fetch_data();
        self.write_fetched(self.fetched_data); // dummy write
        let val = self.fetched_data.wrapping_add(1);
        // SBC
        let a = self.acc;
        let (x1, o1) = a.overflowing_sub(val);
        let (x2, o2) = x1.overflowing_sub(1 - self.status_bit(Status::C));
        self.acc = x2;
        self.status.set(Status::C, !(o1 | o2));
        self.status.set(
            Status::V,
            (a ^ val) & 0x80 != 0 && (a ^ self.acc) & 0x80 != 0,
        );
        self.set_zn_status(self.acc);
        self.write_fetched(val);
    }
    /// DCP: Shortcut for DEC then CMP
    #[inline]
    pub(super) fn dcp(&mut self) {
        // DEC
        self.fetch_data();
        self.write_fetched(self.fetched_data); // dummy write
        let val = self.fetched_data.wrapping_sub(1);
        // CMP
        self.compare(self.acc, val);
        self.write_fetched(val);
    }
    /// AXS: A & X into X
    #[inline]
    pub(super) fn axs(&mut self) {
        self.fetch_data();
        let t = u32::from(self.acc & self.x).wrapping_sub(u32::from(self.fetched_data));
        self.set_zn_status((t & 0xFF) as u8);
        self.status
            .set(Status::C, (((t >> 8) & 0x01) ^ 0x01) == 0x01);
        self.x = (t & 0xFF) as u8;
    }
    /// LAS: Shortcut for LDA then TSX
    #[inline]
    pub(super) fn las(&mut self) {
        self.lda();
        self.tsx();
    }
    /// LAX: Shortcut for LDA then TAX
    #[inline]
    pub(super) fn lax(&mut self) {
        self.lda();
        self.tax();
    }
    /// AHX/SHA/AXA: AND X with A then AND with 7, then store in memory
    #[inline]
    pub(super) fn ahx(&mut self) {
        let val = self.acc
            & self.x
            & self
                .fetched_data
                .wrapping_sub(self.y)
                .wrapping_shr(8)
                .wrapping_add(1);
        self.write_fetched(val);
    }
    /// SAX: AND A with X
    #[inline]
    pub(super) fn sax(&mut self) {
        if self.instr.addr_mode() == IDY {
            self.fetch_data();
        }
        let val = self.acc & self.x;
        self.write_fetched(val);
    }
    /// XAA: Unknown
    #[inline]
    pub(super) fn xaa(&mut self) {
        self.fetch_data();
        self.acc |= 0xEE;
        self.acc &= self.x;
        // AND
        self.acc &= self.fetched_data;
        self.set_zn_status(self.acc);
    }
    /// SXA/SHX/XAS: AND X with the high byte of the target address + 1
    #[inline]
    pub(super) fn sxa(&mut self) {
        let hi = (self.abs_addr >> 8) as u8;
        let lo = (self.abs_addr & 0xFF) as u8;
        let val = self.x & hi.wrapping_add(1);
        self.abs_addr = ((u16::from(self.x) & u16::from(hi.wrapping_add(1))) << 8) | u16::from(lo);
        self.write_fetched(val);
    }
    /// SYA/SHY/SAY: AND Y with the high byte of the target address + 1
    #[inline]
    pub(super) fn sya(&mut self) {
        let hi = (self.abs_addr >> 8) as u8;
        let lo = (self.abs_addr & 0xFF) as u8;
        let val = self.y & hi.wrapping_add(1);
        self.abs_addr = ((u16::from(self.y) & u16::from(hi.wrapping_add(1))) << 8) | u16::from(lo);
        self.write_fetched(val);
    }
    /// RRA: Shortcut for ROR then ADC
    #[inline]
    pub(super) fn rra(&mut self) {
        self.fetch_data();
        // ROR
        self.write_fetched(self.fetched_data); // dummy write
        let mut ret = self.fetched_data.rotate_right(1);
        if self.status.intersects(Status::C) {
            ret |= 1 << 7;
        } else {
            ret &= !(1 << 7);
        }
        self.status.set(Status::C, self.fetched_data & 1 > 0);
        // ADC
        let a = self.acc;
        let (x1, o1) = ret.overflowing_add(a);
        let (x2, o2) = x1.overflowing_add(self.status_bit(Status::C));
        self.acc = x2;
        self.status.set(Status::C, o1 | o2);
        self.status.set(
            Status::V,
            (a ^ ret) & 0x80 == 0 && (a ^ self.acc) & 0x80 != 0,
        );
        self.set_zn_status(self.acc);
        self.write_fetched(ret);
    }
    /// TAS: Shortcut for STA then TXS
    #[inline]
    pub(super) fn tas(&mut self) {
        // STA
        self.write(self.abs_addr, self.acc);
        // TXS
        self.sp = self.x;
    }
    /// ARR: Shortcut for AND #imm then ROR, but sets flags differently
    /// C is bit 6 and V is bit 6 xor bit 5
    #[inline]
    pub(super) fn arr(&mut self) {
        // AND
        self.fetch_data();
        self.acc &= self.fetched_data;
        // ROR
        self.status
            .set(Status::V, (self.acc ^ (self.acc >> 1)) & 0x40 == 0x40);
        let t = self.acc >> 7;
        self.acc >>= 1;
        self.acc |= self.status_bit(Status::C) << 7;
        self.status.set(Status::C, t & 0x01 == 0x01);
        self.set_zn_status(self.acc);
    }
    /// SRA: Shortcut for LSR then EOR
    #[inline]
    pub(super) fn sre(&mut self) {
        self.fetch_data();
        // LSR
        self.write_fetched(self.fetched_data); // dummy write
        self.status.set(Status::C, self.fetched_data & 1 > 0);
        let val = self.fetched_data.wrapping_shr(1);
        // EOR
        self.acc ^= val;
        self.set_zn_status(self.acc);
        self.write_fetched(val);
    }
    /// ALR/ASR: Shortcut for AND #imm then LSR
    #[inline]
    pub(super) fn alr(&mut self) {
        // AND
        self.fetch_data();
        self.acc &= self.fetched_data;
        // LSR
        self.status.set(Status::C, self.acc & 0x01 == 0x01);
        self.acc >>= 1;
        self.set_zn_status(self.acc);
    }
    /// RLA: Shortcut for ROL then AND
    #[inline]
    pub(super) fn rla(&mut self) {
        self.fetch_data();
        // ROL
        self.write_fetched(self.fetched_data); // dummy write
        let old_c = self.status_bit(Status::C);
        self.status.set(Status::C, (self.fetched_data >> 7) & 1 > 0);
        let val = (self.fetched_data << 1) | old_c;
        // AND
        self.acc &= val;
        self.set_zn_status(self.acc);
        self.write_fetched(val);
    }
    /// ANC/AAC: AND #imm but puts bit 7 into carry as if ASL was executed
    #[inline]
    pub(super) fn anc(&mut self) {
        // AND
        self.fetch_data();
        self.acc &= self.fetched_data;
        self.set_zn_status(self.acc);
        // Put bit 7 into carry
        self.status.set(Status::C, (self.acc >> 7) & 1 > 0);
    }
    /// SLO: Shortcut for ASL then ORA
    #[inline]
    pub(super) fn slo(&mut self) {
        self.fetch_data();
        // ASL
        self.write_fetched(self.fetched_data); // dummy write
        self.status.set(Status::C, (self.fetched_data >> 7) & 1 > 0);
        let val = self.fetched_data.wrapping_shl(1);
        self.write_fetched(val);
        // ORA
        self.acc |= val;
        self.set_zn_status(self.acc);
    }
}

impl fmt::Debug for Instr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> {
        let mut op = self.op();
        let unofficial = match self.op() {
            XXX | ISB | DCP | AXS | LAS | LAX | AHX | SAX | XAA | SXA | RRA | TAS | SYA | ARR
            | SRE | ALR | RLA | ANC | SLO => "*",
            NOP if self.opcode() != 0xEA => "*", // 0xEA is the only official NOP
            SKB | IGN => {
                op = NOP;
                "*"
            }
            SBC if self.opcode() == 0xEB => "*",
            _ => "",
        };
        write!(f, "{:1}{:?}", unofficial, op)
    }
}