/***************************************************************************
 *   Copyright (C) 2022 by Santiago González                               *
 *                                                                         *
 ***( see copyright.txt file at root folder )*******************************/

#include <QDebug>

#include "mcs65core.h"
#include "e_mcu.h"
#include "simulator.h"
#include "ioport.h"
#include "watcher.h"

Mcs65Cpu::Mcs65Cpu( eMcu* mcu )
        : Mcs65Interface( mcu )
{
    // CPU registers to show in Monitor

    m_cpuRegs.insert("P" , &m_P  );
    m_cpuRegs.insert("S" , &m_SP );
    m_cpuRegs.insert("A" , &m_Ac );
    m_cpuRegs.insert("IR", &m_IR );
    m_cpuRegs.insert("X" , &m_rX );
    m_cpuRegs.insert("Y" , &m_rY );
    //m_rI = NULL;
    m_watcher->setRegisters( m_cpuRegs.keys() );

    // <register name="P"  addr="0x05" bits="C,Z,I,D,B,1,V,N" reset="00110100" mask="11011111" />
    m_STATUS = &m_P;
    mcu->setStatusBits({"C","Z","I","D","B","1","V","N"});

    // Buses
    m_dataBus = mcu->getIoPort("PORTD");
    m_addrBus = mcu->getIoPort("PORTA");
    m_rwPin   = mcu->getIoPin("RW");

    // Control Pins
    m_ctrlPort = mcu->getIoPort("CPORT0");
    m_phi0Pin = mcu->getIoPin("P0");
    m_phi1Pin = mcu->getIoPin("P1");   //m_phi1Pin->setPinMode( output );
    m_phi1Pin->setUnused( true );
    m_phi2Pin = mcu->getIoPin("P2");   m_phi2Pin->setPinMode( output );
    m_syncPin = mcu->getIoPin("SYNC"); m_syncPin->setPinMode( output );

    // Interrupt Pins
    m_irqPin = mcu->getIoPin("IRQ");
    m_nmiPin = mcu->getIoPin("NMI");

    // User Pins
    m_rdyPin = mcu->getIoPin("RDY");
    m_soPin  = mcu->getIoPin("SO");
    /// m_dbePin = McuPort::getPin("DBE"););

    /*mcu->component()->addPropGroup( {"Cpu", {
new BoolProp<Mcu>( "Ext_Osc", tr("External Clock"),"", this, &Mcu::extOscEnabled, &Mcu::enableExtOsc ),
     }} );*/
}
Mcs65Cpu::~Mcs65Cpu() {}

int Mcs65Cpu::getIntReg( QString reg )
{
    if( m_cpuRegs.contains( reg ) )
    {
        uint8_t* regPtr = m_cpuRegs.value( reg );
        return *regPtr;
    }
    return -1;
}

QString Mcs65Cpu::getStrReg( QString reg ) // Called by Mcu Monitor to get String values
{
    QString value = "";

    if( reg == "Instruction") value = getStrInst( m_IR );


    return value;
}

void Mcs65Cpu::reset()
{
    Cpu8bits::reset();

    *m_STATUS = 0b00110100; // Status

    m_state = cRESET;
    m_EXEC = NULL;
    m_debugPC = 0;
    m_cycle = 0;
    m_SP = 0;

    m_Isr = 0;
    m_isrSource = 0;

    m_dataMode = input;
    m_nextClock = true;
    m_halt = false;
    m_nmiState = false;

    stamp();
}

void Mcs65Cpu::stamp()
{
    m_dataBus->reset();
    m_addrBus->reset();
    m_ctrlPort->reset();

    m_addrBus->setPinMode( output );
    m_dataBus->setPinMode( input );

    m_rwPin->setPinMode( output );
    m_rwPin->setOutState( true );

    //m_phi1Pin->setPinMode( output );
    m_phi2Pin->setPinMode( output );
    m_syncPin->setPinMode( output );

    m_irqPin->setPinMode( input );
    m_nmiPin->setPinMode( input );

    // User Pins
    m_rdyPin->setPinMode( input );
    m_soPin->setPinMode( input );
}

void Mcs65Cpu::runEvent()
{
    if( m_nextClock ) // Clock Falling delayed: Set Address Bus
    {
        m_addrBus->setOutState( m_busAddr );

        if( m_dataMode == output ){
            m_dataMode = input;
            m_dataBus->setPinMode( input ); // Data Bus to input if last Write
        }
    }else            // Clock Rising delayed: Set Data Bus
    {
        if( m_dataMode == input ){
            m_dataMode = output;
            m_dataBus->setPinMode( output );
        }
        m_dataBus->setOutState( m_op0 );
    }
}

void Mcs65Cpu::extClock( bool clkState )
{
    if( clkState == m_nextClock ) runStep();
}

void Mcs65Cpu::runStep()
{
    if( m_nextClock ) clkRisingEdge();
    else              clkFallingEdge();

    m_phi2Pin->scheduleState( m_nextClock, 5000 );
    m_nextClock = !m_nextClock;
}

void Mcs65Cpu::clkRisingEdge()
{
    if( m_halt ) return;
    if( !m_soPin->getInpState() ) SET_OVERFLOW( 1 );

    if( m_state != cWRITE ) return;
    m_state = m_nextState;

    Simulator::self()->addEvent( m_tHW, this ); // Set Data Port
}

void Mcs65Cpu::clkFallingEdge()
{
     m_halt = !m_rdyPin->getInpState();
    if( m_halt ) return;

    m_mcu->cyclesDone = 1;
    m_cycle++;

    bool nmiState = m_nmiPin->getInpState();
    if( !m_Isr )                              // Check for interrupts
    {
        if( m_nmiState && !nmiState )
        {
            m_isrSource = 1;                 // NMI (falling edge)
        }
        else if( !m_isrSource && !STATUS(I) && !m_irqPin->getInpState() )
        {
            m_isrSource = 2;                 // IRQ
        }
    }
    m_nmiState = nmiState;

    if( m_state == cRESET )  // Reset Sequence: 8 cycles, then state changes to FETCH
    {
        resetSeq();
        if( m_state == cREAD ) { m_state = cRESET; return; } // Keep sequence until cFETCH
    }
    if( m_state == cDECODE )  // Decode Instruction, then state changes to READ
    {
        //m_debugPC = m_PC; // Don't update m_debugPC until last Instruction fully executed

        m_syncPin->scheduleState( false, m_tHA ); // Reset SYNC Signal
        m_IR = readDataBus();

        if( m_isrSource )
        {
            m_EXEC = &Mcs65Cpu::BRK;
            m_state = cEXEC;
            m_PC--;             // Fetch did increase PC
        }
        else decode();
    }
    if( m_state == cREAD ) // Read Operand: When all Operands ready state changes to EXEC
    {
        Read();
    }
    if( m_state == cEXEC )         // Execute Instruction: when finished state changes to FETCH
    {
        m_state = cFETCH;       // Default: Execute in 1 step, Instructions can add Execute cycles

        if( m_EXEC ) (this->*m_EXEC)();
        //else qDebug() << "ERROR: Instruction not implemented: 0x"+QString::number( m_IR, 16 ).toUpper(); //
    }
    if( m_state == cWRITE ) m_rwPin->scheduleState( false, m_tHA ); // Write result and fetch at next cycle //m_busAddr = m_opAddr Done in instruction
    else{
        m_rwPin->scheduleState( true, m_tHA );

        if( m_state == cFETCH )  // If no Write op. fetch next inst. at execute cycle
        {
            if( m_aMode <= aACCU ) m_PC--;           // If dummy Read then read the same PC
            m_debugPC = m_PC;
            readPGM();                               // Read opCode
            m_RET_ADDR = m_PC;
            m_cycle = 0;
            m_state = cDECODE;

            m_syncPin->scheduleState( true, m_tHA );  // Set SYNC Signal
        }
    }
}

void Mcs65Cpu::resetSeq() // Reset Sequence: 8 cycles (including fetch first instruction)
{
    switch( m_cycle ){
        case 1:                    return;
        case 2: readMem( 0x0001 ); return;
        case 3: readMem( 0x0100 ); return;
        case 4:                    return;
        case 5:                    return;
        case 6: readMem( 0XFFFC ); return;
        case 7: readMem( 0XFFFD ); m_op0 = readDataBus(); return;
        case 8:{
            uint32_t data = readDataBus();
            m_PC = m_op0 | (data << 8);
            m_state = cFETCH;         // Reset sequence finished, Fetch first instruction.
            m_aMode = aIMME;          // Avoid PC decrement at cFETCH case
}   }   }

void Mcs65Cpu::decode()
{
    m_state = cREAD;    // Read next operand in any case
    m_aMode = aNONE;
    m_aFlags = 0;
    m_EXEC = NULL;

    switch( m_IR )                     // Irregular Instructions
    {
        case 0x00: m_EXEC = &Mcs65Cpu::BRK; m_isrSource = 3; return; // BRK
        case 0x08: m_EXEC = &Mcs65Cpu::PHP;                  return; // PHP
        case 0x18: m_EXEC = &Mcs65Cpu::CLC; return; // CLC
        case 0x20: m_EXEC = &Mcs65Cpu::JSR; m_aMode = aABSO; return; // JSR abs Execution controls PC
        case 0x28: m_EXEC = &Mcs65Cpu::PLP;                  return; // PLP
        case 0x38: m_EXEC = &Mcs65Cpu::SEC; return; // SEC
        case 0x40: m_EXEC = &Mcs65Cpu::RTI;                  return; // RTI
        case 0x48: m_EXEC = &Mcs65Cpu::PHA; return; // PHA
        case 0x58: m_EXEC = &Mcs65Cpu::CLI; return; // CLI
        case 0x60: m_EXEC = &Mcs65Cpu::RTS;                  return; // RTS
        case 0x68: m_EXEC = &Mcs65Cpu::PLA;                  return; // PLA
        case 0x78: m_EXEC = &Mcs65Cpu::SEI; return; // SEI
        case 0x88: m_EXEC = &Mcs65Cpu::DEY; return; // DEY
        case 0x8A: m_EXEC = &Mcs65Cpu::TXA; return; // TXA
        case 0x98: m_EXEC = &Mcs65Cpu::TYA; return; // TYA
        case 0x9A: m_EXEC = &Mcs65Cpu::TXS; return; // TXS
        case 0xA8: m_EXEC = &Mcs65Cpu::TAY; return; // TAY
        case 0xAA: m_EXEC = &Mcs65Cpu::TAX; return; // TAX
        case 0xB8: m_EXEC = &Mcs65Cpu::CLV; return; // CLV
        case 0xBA: m_EXEC = &Mcs65Cpu::TSX; return; // TSX
        case 0xC8: m_EXEC = &Mcs65Cpu::INY; return; // INY
        case 0xCA: m_EXEC = &Mcs65Cpu::DEX; return; // DEX
        case 0xD8: m_EXEC = &Mcs65Cpu::CLD; return; // CLD
        case 0xE8: m_EXEC = &Mcs65Cpu::INX; return; // INX
        case 0xEA: m_EXEC = &Mcs65Cpu::NOP; return; // NOP
        case 0xF8: m_EXEC = &Mcs65Cpu::SED; return; // SED
    }
    /// A big switch case would be faster (with setters for Addressing Mode)
    // Regular Instructions:             OOOAAAGG , O = Opcode, A = Addr mode, G = Group
    uint8_t group =  m_IR & 0b00000011;
    uint8_t Ocode = (m_IR & 0b11100000) >> 5;
    uint8_t Atype = (m_IR & 0b00011100) >> 2;

    switch( Atype ){                // Set Addressing Mode
    case 0: {
        if( group == 1 ){ m_aMode = aINDI; m_aFlags = iX; break; } // Group 1
        else{             m_aMode = aIMME;                break; } // Groups 2, 0
    }
    case 1:               m_aMode = aZERO;                break;   // All
    case 2: {
        if( group == 1 ){ m_aMode = aIMME;                break; } // Group 1
        if( group == 2 ){ m_aMode = aACCU;                break; } // Group 2
    }
    case 3:               m_aMode = aABSO;                break;   // All
    case 4:
        if( group == 0 ){ m_aMode = aIMME;                break; } // Group 0 Conditional Branch
        if( group == 1 ){ m_aMode = aINDI; m_aFlags = iY; break; } // Group 1

    case 5: {             m_aMode = aZEDX;
        if( group == 2
         && ( Ocode == 4 || Ocode == 5 ) ) m_aFlags = iY;          // Group 2: STX zpg,Y (0x96) & LDX zpg,Y (0x86)
        else                               m_aFlags = iX;
    }break;
    case 6:
        if( group == 1 ){ m_aMode = aABSO; m_aFlags = iY; break; } // Group 1

    case 7:               m_aMode = aABSO; m_aFlags = iX; break;   // All
    }
    switch( group ){               // Set Executable
        case 0:{
        if( Atype == 4 ) // xxy 100 00 Conditional Branch
        {
            m_u8Tmp0 = (m_IR & 0b11000000) >> 6; // flag: 0=N, 1=O, 2=C, 3=Z
            m_u8Tmp1 = (m_IR & 0b00100000) >> 5; // y
            m_EXEC = &Mcs65Cpu::BXX; // BPL BMI BVC BVS BCC BCS BNE BEQ
        }else{
            switch( Ocode ){
                case 0: break;
                case 1: m_EXEC = &Mcs65Cpu::BIT; break;
                case 2:                   //JMP;
                case 3: m_EXEC = &Mcs65Cpu::JMP; break;
                case 4: m_EXEC = &Mcs65Cpu::STY; break;
                case 5: m_EXEC = &Mcs65Cpu::LDY; break;
                case 6: m_EXEC = &Mcs65Cpu::CPY; break;
                case 7: m_EXEC = &Mcs65Cpu::CPX; break;
        } } } break;
        case 1:{ switch( Ocode ){
                case 0: m_EXEC = &Mcs65Cpu::ORA; break;
                case 1: m_EXEC = &Mcs65Cpu::AND; break;
                case 2: m_EXEC = &Mcs65Cpu::EOR; break;
                case 3: m_EXEC = &Mcs65Cpu::ADC; break;
                case 4: m_EXEC = &Mcs65Cpu::STA; break;
                case 5: m_EXEC = &Mcs65Cpu::LDA; break;
                case 6: m_EXEC = &Mcs65Cpu::CMP; break;
                case 7: m_EXEC = &Mcs65Cpu::SBC; break;
        } }break;
        case 2:{ switch( Ocode ){
                case 0: m_EXEC = &Mcs65Cpu::ASL; break;
                case 1: m_EXEC = &Mcs65Cpu::ROL; break;
                case 2: m_EXEC = &Mcs65Cpu::LSR; break;
                case 3: m_EXEC = &Mcs65Cpu::ROR; break;
                case 4: m_EXEC = &Mcs65Cpu::STX; break;
                case 5: m_EXEC = &Mcs65Cpu::LDX; break;
                case 6: m_EXEC = &Mcs65Cpu::DEC; break;
                case 7: m_EXEC = &Mcs65Cpu::INC; break;
        } }break;
        case 3: { break; }  /// Not valid (by now)
    }
    if( !m_EXEC )
        qDebug() << "ERROR: Instruction not implemented: 0x"
                    +QString::number( m_IR, 16 ).toUpper()
                 << "PC:0x"+QString::number( m_PC, 16 ).toUpper();
}

void Mcs65Cpu::Read()
{
    if( m_cycle == 1 ){ readPGM(); return; } // Always Read 1º Operand
    m_state = cEXEC; // Default

    switch( m_aMode ){
        case aNONE:                        break;/// Dummy Read
        case aACCU: m_op0 = m_Ac;          break;/// Value = m_Acumulator
        case aIMME: m_op0 = readDataBus(); break;/// Immediate: From Mem 1 byte
        case aABSO:{                             /// Absolute: address From Mem 2 bytes: LLHH (Little Endian)
            switch( m_cycle ){
                case 2:{
                    m_opAddr = readDataBus();     // Read addrL
                    readPGM();                    // Read 2º Operand
                }break;
                case 3:{
                    if( m_aFlags )               /// Absolute Indexed
                    {
                        if     ( m_aFlags & iX ) m_opAddr += m_rX;
                        else if( m_aFlags & iY ) m_opAddr += m_rY;
                        if( m_opAddr > 255 ){
                            m_opAddr &= 0xFF;     // clear High byte
                            m_aFlags |= iC;       // carry
                        }
                    }
                    m_opAddr |= (readDataBus() << 8); // Read addrH
                    readMem( m_opAddr );
                }break;
                case 4:{
                    if( m_aFlags & iC ){          // Add carry and read
                        m_opAddr += 256;
                        readMem( m_opAddr );
                    }
                    else m_op0 = readDataBus();   // Read operation finished
                }break;
                case 5: m_op0 = readDataBus();
            }
        }break;
        case aZERO:{                             /// Zero Page
            switch( m_cycle ){
                case 2:{
                    m_opAddr = readDataBus();     // Read addrL
                    readMem( m_opAddr );          // Read 2º Operand
                }break;
                case 3: m_op0 = readDataBus();    // Finish read operation
            }
        }break;
        case aZEDX:{                             /// Zero Page indexed
            switch( m_cycle ){
                case 2:{
                    m_u8Tmp0 = readDataBus();
                    readMem( m_u8Tmp0 );          // Dummy read
                }break;
                case 3:{                          // Discard reading
                    if     ( m_aFlags & iX ) m_u8Tmp0 += m_rX;
                    else if( m_aFlags & iY ) m_u8Tmp0 += m_rY;
                    m_opAddr = m_u8Tmp0;
                    readMem( m_opAddr );
                }break;
                case 4: m_op0 = readDataBus();    // Finish read operation
            }
        }break;
        case aINDI:{                             /// Indirect: 1 byte from Mem + RX(ZP wrap) or RY
            if( m_aFlags & iX ){                 /// Indirect Pre-indexed
                switch( m_cycle ){
                    case 2:{
                        m_u8Tmp0 = readDataBus();
                        readMem( m_u8Tmp0 );          // Read addrL
                    }break;
                    case 3:{
                        m_u8Tmp0 += m_rX;             // Read addrL+X
                        readMem( m_u8Tmp0 );
                    }break;
                    case 4:{
                        m_u8Tmp0 = readDataBus()+1;
                        readMem( m_u8Tmp0 );          // Read addrH
                    }break;
                    case 5:{
                        m_opAddr = m_u8Tmp0 | readDataBus()<<8;
                        readMem( m_opAddr );
                    }break;
                    case 6: m_op0 = readDataBus();    // Finish read operation
                }
            }else{                                   /// Indirect Post-indexed
                switch( m_cycle ){
                    case 2:{
                        m_u8Tmp0 = readDataBus();
                        readMem( m_u8Tmp0 );          // Read addrL
                    }break;
                    case 3:{
                        m_u8Tmp1 = readDataBus();     // addrL
                        readMem( m_u8Tmp0+1 );        // Read addrH
                    }break;
                    case 4:{
                        m_opAddr = m_u8Tmp1 + m_rY;   // addrL+Y
                        if( m_opAddr > 255 ){
                            m_opAddr &= 0xFF;         // clear High byte
                            m_aFlags |= iC;           // carry
                        }
                        m_opAddr |= readDataBus()<<8; // Read addrH
                        readMem( m_opAddr );
                    }break;
                    case 5:{
                        if( m_aFlags & iC ){          // Add carry and read
                            m_opAddr += 256;
                            readMem( m_opAddr );
                        }
                        else m_op0 = readDataBus();   // Read operation finished
                    }break;
                    case 6: m_op0 = readDataBus();    // Finish read operation
                }
            }
        }
    }
}

void Mcs65Cpu::readPGM(){ readMem( m_PC++ ); }

void Mcs65Cpu::readMem( uint16_t addr )
{
    m_busAddr = addr;
    m_state = cREAD;
    Simulator::self()->addEvent( m_tHA, this ); // Buses managed at runEvent()
}

uint8_t Mcs65Cpu::readDataBus() { return m_dataBus->getInpState(); }

void Mcs65Cpu::writeMem( uint16_t addr ) {
    m_busAddr = addr; m_state = cWRITE; m_nextState = cFETCH;
    Simulator::self()->addEvent( m_tHA, this ); // Buses managed at runEvent()
}

void Mcs65Cpu::pushStack8( uint8_t byte ) { m_op0 = byte; writeMem( 0x0100 + m_SP-- ); }

void Mcs65Cpu::popStack8() { readMem( 0x0100 + (++m_SP) ); /*m_state = cEXEC;*/ }

void Mcs65Cpu::setNZ( uint8_t val ) { SET_NEGATIVE( val & 0x80 ); SET_ZERO( val == 0 ); }

void Mcs65Cpu::writeWflags( bool c )
{
    SET_CARRY( c );
    setNZ( m_op0 );
    if( m_aMode == aACCU ) m_Ac = m_op0;         // Write to Acc
    else                   writeMem( m_opAddr ); // Write to MEM
}

void Mcs65Cpu::ADC()
{
    uint8_t m = m_op0;
    uint tmp = m + m_Ac + (STATUS(C) ? 1 : 0);
    SET_ZERO( (tmp & 0xFF) == 0 );

    if( STATUS(D) ){
        if( ((m_Ac & 0xF) + (m & 0xF) + (STATUS(C) ? 1 : 0)) > 9) tmp += 6;

        SET_NEGATIVE( tmp & 0x80 );
        SET_OVERFLOW( !((m_Ac ^ m) & 0x80) && ((m_Ac ^ tmp) & 0x80) );

        if( tmp > 0x99) tmp += 96;
        SET_CARRY(tmp > 0x99);
    }else{
        SET_NEGATIVE( tmp & 0x80 );
        SET_OVERFLOW(!((m_Ac ^ m) & 0x80) && ((m_Ac ^ tmp) & 0x80));
        SET_CARRY(tmp > 0xFF);
    }
    m_Ac = tmp & 0xFF;
}

void Mcs65Cpu::AND() { m_Ac &= m_op0; setNZ( m_Ac ); }

void Mcs65Cpu::ASL()
{
    bool carry = m_op0 & 0x80;
    m_op0 <<= 1;
    writeWflags( carry );
}

void Mcs65Cpu::BIT()
{
    uint8_t res = m_op0 & m_Ac;
    SET_NEGATIVE( res );
    *m_STATUS = (*m_STATUS & 0x3F) | (m_op0 & 0xC0) | CONSTANT | BREAK;
    SET_ZERO( res == 0 );
}

void Mcs65Cpu::BRK() // Break/Interrupt : 1 byte, 7 cycles
{
    switch( m_cycle ){
        case 1: m_state = cEXEC; break;
        case 2:                              // PCH    -> Stack
            pushStack8( m_PC >> 8 );
            m_nextState = cEXEC;
            break;
        case 3:                              // PCL    -> Stack
            pushStack8( m_PC /*& 0xFF*/ );
            m_nextState = cEXEC;
            break;
        case 4:{                             // Status -> Stack
            uint8_t status = *m_STATUS;

            switch( m_isrSource)             // Determine ISR source
            {
                case 1:  m_Isr = 0xFFFA; status &= ~BREAK; break; // NMI (falling edge) - B flag cleared
                case 2:  m_Isr = 0xFFFE; status &= ~BREAK; break; // IRQ                 - B flag cleared
                default: m_Isr = 0xFFFE; status |=  BREAK;        // Software BRK
            }
            pushStack8( status );
            m_nextState = cEXEC;
        } break;
        case 5:                              // Read Isr L
            readMem( m_Isr );
            m_aMode = aIMME;
            break;
        case 6:                              // Read Isr H
            readMem( m_Isr+1 );
            m_u8Tmp0 = m_op0;
            m_aMode = aIMME;
            SET_INTERRUPT( 1 );
            break;
        case 7:                              // Jump to Isr
            m_PC = (m_op0 << 8) | m_u8Tmp0;
            m_isrSource = 0;
            m_Isr = 0;
}   }

void Mcs65Cpu::CLC() { SET_CARRY(0);     }
void Mcs65Cpu::CLD() { SET_DECIMAL(0);   }
void Mcs65Cpu::CLI() { SET_INTERRUPT(0); }
void Mcs65Cpu::CLV() { SET_OVERFLOW(0);  }

void Mcs65Cpu::CMP() { SET_CARRY( m_Ac >= m_op0 ); setNZ( m_Ac - m_op0 ); }
void Mcs65Cpu::CPX() { SET_CARRY( m_rX >= m_op0 ); setNZ( m_rX - m_op0 ); }
void Mcs65Cpu::CPY() { SET_CARRY( m_rY >= m_op0 ); setNZ( m_rY - m_op0 ); }

void Mcs65Cpu::DEC() { m_op0 -= 1; setNZ( m_op0 ); writeMem( m_opAddr ); }
void Mcs65Cpu::DEX() { m_rX  -= 1; setNZ( m_rX ); }
void Mcs65Cpu::DEY() { m_rY  -= 1; setNZ( m_rY ); }

void Mcs65Cpu::EOR() { m_Ac = m_Ac ^ m_op0; setNZ( m_Ac ); }

void Mcs65Cpu::INC() { m_op0 += 1; setNZ( m_op0 ); writeMem( m_opAddr ); }
void Mcs65Cpu::INX() { m_rX  += 1; setNZ( m_rX ); }
void Mcs65Cpu::INY() { m_rY  += 1; setNZ( m_rY ); }

void Mcs65Cpu::JMP() { m_PC = m_opAddr; }
void Mcs65Cpu::JSR()
{
    switch( m_cycle ){
        case 4: pushStack8( m_PC >> 8 );   m_nextState = cEXEC; break;  // PCH -> Stack
        case 5: pushStack8( m_PC & 0xFF ); m_nextState = cEXEC; break;  // PCL -> Stack
        case 6: m_PC = m_opAddr;                                        // Jump to Subroutine
}   }

void Mcs65Cpu::LDA() { m_Ac = m_op0; setNZ( m_Ac ); }
void Mcs65Cpu::LDX() { m_rX = m_op0; setNZ( m_rX ); }
void Mcs65Cpu::LDY() { m_rY = m_op0; setNZ( m_rY ); }

void Mcs65Cpu::LSR()
{
    bool carry = m_op0 & 1;
    m_op0 >>= 1;
    writeWflags( carry );
}

void Mcs65Cpu::NOP() { return; }

void Mcs65Cpu::ORA() { m_Ac |= m_op0; setNZ( m_Ac ); } // OR ACCU

void Mcs65Cpu::PHA() { pushStack8( m_Ac ); }
void Mcs65Cpu::PHP() { pushStack8( STATUS(C) | STATUS(B) ); }

void Mcs65Cpu::PLA()
{
    if( m_cycle == 2 ) popStack8();
    else             { m_Ac = readDataBus(); setNZ( m_Ac ); }
}

void Mcs65Cpu::PLP()
{
    if( m_cycle == 2 ) popStack8();
    else              { *m_STATUS = readDataBus() | CONSTANT | BREAK;  }
}

void Mcs65Cpu::ROL()
{
    bool carry = m_op0 & 0x80;
    m_op0 <<= 1;
    if( STATUS(C) ) m_op0 |= 0x01;
    writeWflags( carry );
}

void Mcs65Cpu::ROR()
{
    bool carry = m_op0 & 1;
    m_op0 >>= 1;
    if( STATUS(C) ) m_op0 |= 0x80;
    writeWflags( carry );
}

void Mcs65Cpu::RTI() // Return from Interrupt ////
{
    switch( m_cycle ){
        case 2: popStack8();                                               break;
        case 3: popStack8(); *m_STATUS = readDataBus() | CONSTANT | BREAK; break;
        case 4: popStack8(); m_op0 = readDataBus();                        break;
        case 5: m_PC = (readDataBus() << 8) | m_op0; m_aMode = aIMME; // Avoid PC decrement at cFETCH case
            SET_INTERRUPT( 0 );
            m_mcu->interrupts()->retI();
}   }

void Mcs65Cpu::RTS() // Return from Subroutine
{
    switch( m_cycle ){
        case 2: popStack8();                        break;
        case 3: popStack8(); m_op0 = readDataBus(); break;
        case 4: m_PC = (readDataBus() << 8) | m_op0; m_aMode = aIMME; // Avoid PC decrement at cFETCH case
}   }

void Mcs65Cpu::SBC()
{
    uint8_t m = m_op0;
    uint tmp = m_Ac - m - (STATUS(C) ? 0 : 1);
    SET_NEGATIVE( tmp & 0x80 );
    SET_ZERO( (tmp & 0xFF) == 0 );
    SET_OVERFLOW( ((m_Ac ^ tmp) & 0x80) && ((m_Ac ^ m) & 0x80) );

    if( STATUS(D) ){
        if( ((m_Ac & 0x0F) - (STATUS(C) ? 0 : 1)) < (m & 0x0F)) tmp -= 6;
        if( tmp > 0x99) tmp -= 0x60;
    }
    SET_CARRY( tmp < 0x100 );
    m_Ac = ( tmp /*& 0xFF*/ );
}

void Mcs65Cpu::SEC() { SET_CARRY(1); }
void Mcs65Cpu::SED() { SET_DECIMAL(1); }
void Mcs65Cpu::SEI() { SET_INTERRUPT(1); }

void Mcs65Cpu::STA() { m_op0 = m_Ac; writeMem( m_opAddr ); }
void Mcs65Cpu::STX() { m_op0 = m_rX; writeMem( m_opAddr ); }
void Mcs65Cpu::STY() { m_op0 = m_rY; writeMem( m_opAddr ); }

void Mcs65Cpu::TAX() { m_rX = m_Ac; setNZ( m_rX ); }
void Mcs65Cpu::TAY() { m_rY = m_Ac; setNZ( m_rY ); }
void Mcs65Cpu::TXA() { m_Ac = m_rX; setNZ( m_Ac ); }
void Mcs65Cpu::TYA() { m_Ac = m_rY; setNZ( m_Ac ); }
void Mcs65Cpu::TSX() { m_rX = m_SP; setNZ( m_rX ); }
void Mcs65Cpu::TXS() { m_SP = m_rX; }

void  Mcs65Cpu::BXX() /// +1 cycle same page +2 different page
{                         // m_u8Tmp0 = Flag , m_u8Tmp1 = y
    uint8_t bit = 0;
    switch( m_u8Tmp0 ) {
        case 0: bit = N; break;
        case 1: bit = O; break;
        case 2: bit = C; break;
        case 3: bit = Z; break;
    }
    uint8_t flagVal = STATUS( bit ) ? 1 : 0;
    if( flagVal == m_u8Tmp1 ) m_PC += (int8_t)m_op0;
}
