/* * This file is part of libsidplayfp, a SID player engine. * * Copyright 2011-2014 Leandro Nini * Copyright 2007-2010 Antti Lankila * Copyright 2000 Simon White * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mos6510.h" #include "sidplayfp/event.h" #include "sidendian.h" #include "opcodes.h" #ifdef DEBUG # include # include "mos6510debug.h" #endif namespace libsidplayfp { #ifdef PC64_TESTSUITE # include /** * CHR$ conversion table (0x01 = no output) */ static const char CHRtab[256] = { 0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x0d,0x01,0x01, 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01, 0x20,0x21,0x01,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f, 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f, 0x40,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f, 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x5b,0x24,0x5d,0x20,0x20, // alternative: CHR$(92=0x5c) => ISO Latin-1(0xa3) 0x2d,0x23,0x7c,0x2d,0x2d,0x2d,0x2d,0x7c,0x7c,0x5c,0x5c,0x2f,0x5c,0x5c,0x2f,0x2f, 0x5c,0x23,0x5f,0x23,0x7c,0x2f,0x58,0x4f,0x23,0x7c,0x23,0x2b,0x7c,0x7c,0x26,0x5c, // 0x80-0xFF 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01, 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01, 0x20,0x7c,0x23,0x2d,0x2d,0x7c,0x23,0x7c,0x23,0x2f,0x7c,0x7c,0x2f,0x5c,0x5c,0x2d, 0x2f,0x2d,0x2d,0x7c,0x7c,0x7c,0x7c,0x2d,0x2d,0x2d,0x2f,0x5c,0x5c,0x2f,0x2f,0x23, 0x20,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f, 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x2b,0x7c,0x7c,0x26,0x5c, 0x20,0x7c,0x23,0x2d,0x2d,0x7c,0x23,0x7c,0x23,0x2f,0x7c,0x7c,0x2f,0x5c,0x5c,0x2d, 0x2f,0x2d,0x2d,0x7c,0x7c,0x7c,0x7c,0x2d,0x2d,0x2d,0x2f,0x5c,0x5c,0x2f,0x2f,0x23 }; static char filetmp[0x100]; static int filepos = 0; #endif // PC64_TESTSUITE /** * Magic value for lxa and ane undocumented instructions. * Magic may be EE, EF, FE or FF, but most emulators seem to use EE. * Based on tests on a couple of chips at * http://visual6502.org/wiki/index.php?title=6502_Opcode_8B_(XAA,_ANE) * the value of magic for the MOS 6510 is FF. * However the Lorentz test suite assumes this to be EE. */ const uint8_t magic = #ifdef PC64_TESTSUITE 0xee #else 0xff #endif ; //-------------------------------------------------------------------------// /** * When AEC signal is high, no stealing is possible. */ void MOS6510::eventWithoutSteals() { const ProcessorCycle &instr = instrTable[cycleCount++]; (this->*(instr.func)) (); eventContext.schedule(m_nosteal, 1); } /** * When AEC signal is low, steals permitted. */ void MOS6510::eventWithSteals() { if (instrTable[cycleCount].nosteal) { const ProcessorCycle &instr = instrTable[cycleCount++]; (this->*(instr.func)) (); eventContext.schedule(m_steal, 1); } else { /* Even while stalled, the CPU can still process first clock of * interrupt delay, but only the first one. */ if (interruptCycle == cycleCount) { interruptCycle --; } } } /** * Handle bus access signals. When RDY line is asserted, the CPU * will pause when executing the next read operation. * * @param rdy new state for RDY signal */ void MOS6510::setRDY(bool newRDY) { rdy = newRDY; if (rdy) { eventContext.cancel(m_steal); eventContext.schedule(m_nosteal, 0, EVENT_CLOCK_PHI2); } else { eventContext.cancel(m_nosteal); eventContext.schedule(m_steal, 0, EVENT_CLOCK_PHI2); } } /** * Push P on stack, decrement S. */ void MOS6510::PushSR() { const uint_least16_t addr = endian_16(SP_PAGE, Register_StackPointer); cpuWrite(addr, flags.get()); Register_StackPointer--; } /** * increment S, Pop P off stack. */ void MOS6510::PopSR() { // Get status register off stack Register_StackPointer++; const uint_least16_t addr = endian_16(SP_PAGE, Register_StackPointer); flags.set(cpuRead(addr)); flags.B = true; calculateInterruptTriggerCycle(); } //-------------------------------------------------------------------------// //-------------------------------------------------------------------------// // Interrupt Routines // //-------------------------------------------------------------------------// //-------------------------------------------------------------------------// /** * This forces the CPU to abort whatever it is doing and immediately * enter the RST interrupt handling sequence. The implementation is * not compatible: instructions actually get aborted mid-execution. * However, there is no possible way to trigger this signal from * programs, so it's OK. */ void MOS6510::triggerRST() { Initialise(); cycleCount = BRKn << 3; rstFlag = true; calculateInterruptTriggerCycle(); } /** * Trigger NMI interrupt on the CPU. Calling this method * flags that CPU must enter the NMI routine at earliest * opportunity. There is no way to cancel NMI request once * given. */ void MOS6510::triggerNMI() { nmiFlag = true; calculateInterruptTriggerCycle(); /* maybe process 1 clock of interrupt delay. */ if (!rdy) { eventContext.cancel(m_steal); eventContext.schedule(m_steal, 0, EVENT_CLOCK_PHI2); } } /** * Pull IRQ line low on CPU. */ void MOS6510::triggerIRQ() { irqAssertedOnPin = true; calculateInterruptTriggerCycle(); /* maybe process 1 clock of interrupt delay. */ if (!rdy && interruptCycle == cycleCount) { eventContext.cancel(m_steal); eventContext.schedule(m_steal, 0, EVENT_CLOCK_PHI2); } } /** * Inform CPU that IRQ is no longer pulled low. */ void MOS6510::clearIRQ() { irqAssertedOnPin = false; calculateInterruptTriggerCycle(); } void MOS6510::interruptsAndNextOpcode() { if (cycleCount > interruptCycle + 2) { #ifdef DEBUG if (dodump) { const event_clock_t cycles = eventContext.getTime(EVENT_CLOCK_PHI2); fprintf(m_fdbg, "****************************************************\n"); fprintf(m_fdbg, " interrupt (%d)\n", static_castcycles); fprintf(m_fdbg, "****************************************************\n"); MOS6510Debug::DumpState(cycles, *this); } #endif cpuRead(Register_ProgramCounter); cycleCount = BRKn << 3; flags.B = false; interruptCycle = MAX; } else { fetchNextOpcode(); } } void MOS6510::fetchNextOpcode() { #ifdef DEBUG if (dodump) { MOS6510Debug::DumpState(eventContext.getTime(EVENT_CLOCK_PHI2), *this); } instrStartPC = Register_ProgramCounter; #endif cycleCount = cpuRead(Register_ProgramCounter) << 3; Register_ProgramCounter++; if (!rstFlag && !nmiFlag && !(!flags.I && irqAssertedOnPin)) { interruptCycle = MAX; } if (interruptCycle != MAX) { interruptCycle = -MAX; } } /** * Evaluate when to execute an interrupt. Calling this method can also * result in the decision that no interrupt at all needs to be scheduled. */ void MOS6510::calculateInterruptTriggerCycle() { /* Interrupt cycle not going to trigger? */ if (interruptCycle == MAX) { if (rstFlag || nmiFlag || (!flags.I && irqAssertedOnPin)) { interruptCycle = cycleCount; } } } void MOS6510::IRQLoRequest() { endian_16lo8(Register_ProgramCounter, cpuRead(Cycle_EffectiveAddress)); } void MOS6510::IRQHiRequest() { endian_16hi8(Register_ProgramCounter, cpuRead(Cycle_EffectiveAddress + 1)); } /** * Read the next opcode byte from memory (and throw it away) */ void MOS6510::throwAwayFetch() { cpuRead(Register_ProgramCounter); } /** * Issue throw-away read. Some people use these to ACK CIA IRQs. */ void MOS6510::throwAwayRead() { cpuRead(Cycle_HighByteWrongEffectiveAddress); } /** * Fetch value, increment PC. * * Addressing Modes: * - Immediate * - Relative */ void MOS6510::FetchDataByte() { Cycle_Data = cpuRead(Register_ProgramCounter); if (flags.B) { Register_ProgramCounter++; } #ifdef DEBUG instrOperand = Cycle_Data; #endif } /** * Fetch low address byte, increment PC. * * Addressing Modes: * - Stack Manipulation * - Absolute * - Zero Page * - Zero Page Indexed * - Absolute Indexed * - Absolute Indirect */ void MOS6510::FetchLowAddr() { Cycle_EffectiveAddress = cpuRead(Register_ProgramCounter); Register_ProgramCounter++; #ifdef DEBUG instrOperand = Cycle_EffectiveAddress; #endif } /** * Read from address, add index register X to it. * * Addressing Modes: * - Zero Page Indexed */ void MOS6510::FetchLowAddrX() { FetchLowAddr(); Cycle_EffectiveAddress = (Cycle_EffectiveAddress + Register_X) & 0xFF; } /** * Read from address, add index register Y to it. * * Addressing Modes: * - Zero Page Indexed */ void MOS6510::FetchLowAddrY() { FetchLowAddr(); Cycle_EffectiveAddress = (Cycle_EffectiveAddress + Register_Y) & 0xFF; } /** * Fetch high address byte, increment PC (Absolute Addressing). * * Low byte must have been obtained first! * * Addressing Modes: * - Absolute */ void MOS6510::FetchHighAddr() { // Get the high byte of an address from memory endian_16hi8(Cycle_EffectiveAddress, cpuRead(Register_ProgramCounter)); Register_ProgramCounter++; #ifdef DEBUG endian_16hi8(instrOperand, endian_16hi8(Cycle_EffectiveAddress)); #endif } /** * Fetch high byte of address, add index register X to low address byte, * increment PC. * * Addressing Modes: * - Absolute Indexed */ void MOS6510::FetchHighAddrX() { FetchHighAddr(); Cycle_HighByteWrongEffectiveAddress = (Cycle_EffectiveAddress & 0xff00) | ((Cycle_EffectiveAddress + Register_X) & 0xff); Cycle_EffectiveAddress += Register_X; } /** * Same as #FetchHighAddrX except dosen't worry about page crossing. */ void MOS6510::FetchHighAddrX2() { FetchHighAddrX(); if (Cycle_EffectiveAddress == Cycle_HighByteWrongEffectiveAddress) cycleCount++; } /** * Fetch high byte of address, add index register Y to low address byte, * increment PC. * * Addressing Modes: * - Absolute Indexed */ void MOS6510::FetchHighAddrY() { FetchHighAddr(); Cycle_HighByteWrongEffectiveAddress = (Cycle_EffectiveAddress & 0xff00) | ((Cycle_EffectiveAddress + Register_Y) & 0xff); Cycle_EffectiveAddress += Register_Y; } /** * Same as #FetchHighAddrY except dosen't worry about page crossing. */ void MOS6510::FetchHighAddrY2() { FetchHighAddrY(); if (Cycle_EffectiveAddress == Cycle_HighByteWrongEffectiveAddress) cycleCount++; } /** * Fetch pointer address low, increment PC. * * Addressing Modes: * - Absolute Indirect * - Indirect indexed (post Y) */ void MOS6510::FetchLowPointer() { Cycle_Pointer = cpuRead(Register_ProgramCounter); Register_ProgramCounter++; #ifdef DEBUG instrOperand = Cycle_Pointer; #endif } /** * Add X to it. * * Addressing Modes: * - Indexed Indirect (pre X) */ void MOS6510::FetchLowPointerX() { endian_16lo8(Cycle_Pointer, (Cycle_Pointer + Register_X) & 0xFF); } /** * Fetch pointer address high, increment PC. * * Addressing Modes: * - Absolute Indirect */ void MOS6510::FetchHighPointer() { endian_16hi8(Cycle_Pointer, cpuRead(Register_ProgramCounter)); Register_ProgramCounter++; #ifdef DEBUG endian_16hi8(instrOperand, endian_16hi8(Cycle_Pointer)); #endif } /** * Fetch effective address low. * * Addressing Modes: * - Indirect * - Indexed Indirect (pre X) * - Indirect indexed (post Y) */ void MOS6510::FetchLowEffAddr() { Cycle_EffectiveAddress = cpuRead(Cycle_Pointer); } /** * Fetch effective address high. * * Addressing Modes: * - Indirect * - Indexed Indirect (pre X) */ void MOS6510::FetchHighEffAddr() { endian_16lo8(Cycle_Pointer, (Cycle_Pointer + 1) & 0xff); endian_16hi8(Cycle_EffectiveAddress, cpuRead(Cycle_Pointer)); } /** * Fetch effective address high, add Y to low byte of effective address. * * Addressing Modes: * - Indirect indexed (post Y) */ void MOS6510::FetchHighEffAddrY() { FetchHighEffAddr(); Cycle_HighByteWrongEffectiveAddress = (Cycle_EffectiveAddress & 0xff00) | ((Cycle_EffectiveAddress + Register_Y) & 0xff); Cycle_EffectiveAddress += Register_Y; } /** Same as #FetchHighEffAddrY except dosen't worry about page crossing. */ void MOS6510::FetchHighEffAddrY2() { FetchHighEffAddrY(); if (Cycle_EffectiveAddress == Cycle_HighByteWrongEffectiveAddress) cycleCount++; } //-------------------------------------------------------------------------// //-------------------------------------------------------------------------// // Common Data Accessing Routines // // Data Accessing operations as described in 64doc by John West and // // Marko Makela // //-------------------------------------------------------------------------// //-------------------------------------------------------------------------// void MOS6510::FetchEffAddrDataByte() { Cycle_Data = cpuRead(Cycle_EffectiveAddress); } /** * Write Cycle_Data to effective address. */ void MOS6510::PutEffAddrDataByte() { cpuWrite(Cycle_EffectiveAddress, Cycle_Data); } /** * Push Program Counter Low Byte on stack, decrement S. */ void MOS6510::PushLowPC() { const uint_least16_t addr = endian_16(SP_PAGE, Register_StackPointer); cpuWrite(addr, endian_16lo8(Register_ProgramCounter)); Register_StackPointer--; } /** * Push Program Counter High Byte on stack, decrement S. */ void MOS6510::PushHighPC() { const uint_least16_t addr = endian_16(SP_PAGE, Register_StackPointer); cpuWrite(addr, endian_16hi8(Register_ProgramCounter)); Register_StackPointer--; } /** * Increment stack and pull program counter low byte from stack. */ void MOS6510::PopLowPC() { Register_StackPointer++; const uint_least16_t addr = endian_16(SP_PAGE, Register_StackPointer); endian_16lo8(Cycle_EffectiveAddress, cpuRead(addr)); } /** * Increment stack and pull program counter high byte from stack. */ void MOS6510::PopHighPC() { Register_StackPointer++; const uint_least16_t addr = endian_16(SP_PAGE, Register_StackPointer); endian_16hi8(Cycle_EffectiveAddress, cpuRead(addr)); } void MOS6510::WasteCycle() {} void MOS6510::brkPushLowPC() { PushLowPC(); if (rstFlag) { /* rst = %10x */ Cycle_EffectiveAddress = 0xfffc; } else if (nmiFlag) { /* nmi = %01x */ Cycle_EffectiveAddress = 0xfffa; } else { /* irq = %11x */ Cycle_EffectiveAddress = 0xfffe; } rstFlag = false; nmiFlag = false; calculateInterruptTriggerCycle(); } //-------------------------------------------------------------------------// //-------------------------------------------------------------------------// // Common Instruction Opcodes // // See and 6510 Assembly Book for more information on these instructions // //-------------------------------------------------------------------------// //-------------------------------------------------------------------------// void MOS6510::brk_instr() { PushSR(); flags.B = true; flags.I = true; } void MOS6510::cld_instr() { flags.D = false; interruptsAndNextOpcode(); } void MOS6510::cli_instr() { flags.I = false; calculateInterruptTriggerCycle(); interruptsAndNextOpcode(); } void MOS6510::jmp_instr() { doJSR(); interruptsAndNextOpcode(); } void MOS6510::doJSR() { Register_ProgramCounter = Cycle_EffectiveAddress; #ifdef PC64_TESTSUITE // trap handlers // Print character if (Register_ProgramCounter == 0xffd2) { const char ch = CHRtab[Register_Accumulator]; switch (ch) { case 0: break; case 1: fprintf(stderr, " "); break; case 0xd: fprintf(stderr, "\n"); filepos = 0; break; default: filetmp[filepos++] = ch;// - 'A' + 'a'; fprintf(stderr, "%c", ch); } } // Load else if (Register_ProgramCounter == 0xe16f) { filetmp[filepos] = '\0'; loadFile(filetmp); } // Stop else if (Register_ProgramCounter == 0x8000 || Register_ProgramCounter == 0xa474) { exit(0); } #endif // PC64_TESTSUITE } void MOS6510::pha_instr() { const uint_least16_t addr = endian_16(SP_PAGE, Register_StackPointer); cpuWrite(addr, Register_Accumulator); Register_StackPointer--; } /** * RTI does not delay the IRQ I flag change as it 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. */ void MOS6510::rti_instr() { #ifdef DEBUG if (dodump) fprintf (m_fdbg, "****************************************************\n\n"); #endif Register_ProgramCounter = Cycle_EffectiveAddress; interruptsAndNextOpcode(); } void MOS6510::rts_instr() { cpuRead(Cycle_EffectiveAddress); Register_ProgramCounter = Cycle_EffectiveAddress; Register_ProgramCounter++; } void MOS6510::sed_instr() { flags.D = true; interruptsAndNextOpcode(); } void MOS6510::sei_instr() { flags.I = true; interruptsAndNextOpcode(); if (!rstFlag && !nmiFlag && interruptCycle != MAX) interruptCycle = MAX; } void MOS6510::sta_instr() { Cycle_Data = Register_Accumulator; PutEffAddrDataByte(); } void MOS6510::stx_instr() { Cycle_Data = Register_X; PutEffAddrDataByte(); } void MOS6510::sty_instr() { Cycle_Data = Register_Y; PutEffAddrDataByte(); } //-------------------------------------------------------------------------// //-------------------------------------------------------------------------// // Common Instruction Undocumented Opcodes // // See documented 6502-nmo.opc by Adam Vardy for more details // //-------------------------------------------------------------------------// //-------------------------------------------------------------------------// /** * Undocumented - This opcode stores the result of A AND X AND the high * byte of the target address of the operand +1 in memory. */ void MOS6510::axa_instr() { Cycle_Data = Register_X & Register_Accumulator & (endian_16hi8(Cycle_EffectiveAddress) + 1); if (Cycle_HighByteWrongEffectiveAddress != Cycle_EffectiveAddress) Cycle_EffectiveAddress = endian_16(Cycle_Data, endian_16lo8(Cycle_EffectiveAddress)); PutEffAddrDataByte(); } /** * Undocumented - AXS ANDs the contents of the A and X registers (without changing the * contents of either register) and stores the result in memory. * AXS does not affect any flags in the processor status register. */ void MOS6510::axs_instr() { Cycle_Data = Register_Accumulator & Register_X; PutEffAddrDataByte(); } #if 0 // Not required - Operation performed By another method // Undocumented - HLT crashes the microprocessor. When this opcode is executed, program // execution ceases. No hardware interrupts will execute either. The author // has characterized this instruction as a halt instruction since this is the // most straightforward explanation for this opcode's behaviour. Only a reset // will restart execution. This opcode leaves no trace of any operation // performed! No registers affected. void MOS6510::hlt_instr() {} #endif /** * Undocumented - This opcode ANDs the contents of the Y register with and stores the * result in memory. */ void MOS6510::say_instr() { Cycle_Data = Register_Y & (endian_16hi8(Cycle_EffectiveAddress) + 1); if (Cycle_HighByteWrongEffectiveAddress != Cycle_EffectiveAddress) Cycle_EffectiveAddress = endian_16(Cycle_Data, endian_16lo8(Cycle_EffectiveAddress)); PutEffAddrDataByte(); } /** * Undocumented - This opcode ANDs the contents of the X register with and stores the * result in memory. */ void MOS6510::xas_instr() { Cycle_Data = Register_X & (endian_16hi8(Cycle_EffectiveAddress) + 1); if (Cycle_HighByteWrongEffectiveAddress != Cycle_EffectiveAddress) Cycle_EffectiveAddress = endian_16(Cycle_Data, endian_16lo8(Cycle_EffectiveAddress)); PutEffAddrDataByte(); } /** * BCD adding. */ void MOS6510::doADC() { const unsigned int C = flags.C ? 1 : 0; const unsigned int A = Register_Accumulator; const unsigned int s = Cycle_Data; const unsigned int regAC2 = A + s + C; if (flags.D) { // BCD mode unsigned int lo = (A & 0x0f) + (s & 0x0f) + C; unsigned int hi = (A & 0xf0) + (s & 0xf0); if (lo > 0x09) lo += 0x06; if (lo > 0x0f) hi += 0x10; flags.Z = !(regAC2 & 0xff); flags.N = hi & 0x80; flags.V = ((hi ^ A) & 0x80) && !((A ^ s) & 0x80); if (hi > 0x90) hi += 0x60; flags.C = hi > 0xff; Register_Accumulator = (hi | (lo & 0x0f)); } else { // Binary mode flags.C = regAC2 > 0xff; flags.V = ((regAC2 ^ A) & 0x80) && !((A ^ s) & 0x80); flags.setNZ(Register_Accumulator = regAC2 & 0xff); } } /** * BCD subtracting. */ void MOS6510::doSBC() { const unsigned int C = flags.C? 0 : 1; const unsigned int A = Register_Accumulator; const unsigned int s = Cycle_Data; const unsigned int regAC2 = A - s - C; flags.C = regAC2 < 0x100; flags.V = ((regAC2 ^ A) & 0x80) && ((A ^ s) & 0x80); flags.setNZ(regAC2); if (flags.D) { // BCD mode unsigned int lo = (A & 0x0f) - (s & 0x0f) - C; unsigned int hi = (A & 0xf0) - (s & 0xf0); if (lo & 0x10) { lo -= 0x06; hi -= 0x10; } if (hi & 0x100) hi -= 0x60; Register_Accumulator = (hi | (lo & 0x0f)); } else { // Binary mode Register_Accumulator = regAC2 & 0xff; } } //-------------------------------------------------------------------------// //-------------------------------------------------------------------------// // Generic Instruction Addressing Routines // //-------------------------------------------------------------------------/ //-------------------------------------------------------------------------// //-------------------------------------------------------------------------// // Generic Instruction Opcodes // // See and 6510 Assembly Book for more information on these instructions // //-------------------------------------------------------------------------// //-------------------------------------------------------------------------// void MOS6510::adc_instr() { doADC(); interruptsAndNextOpcode(); } void MOS6510::and_instr() { flags.setNZ(Register_Accumulator &= Cycle_Data); interruptsAndNextOpcode(); } /** * Undocumented - For a detailed explanation of this opcode look at: * http://visual6502.org/wiki/index.php?title=6502_Opcode_8B_(XAA,_ANE) */ void MOS6510::ane_instr() { flags.setNZ(Register_Accumulator = (Register_Accumulator | magic) & Register_X & Cycle_Data); interruptsAndNextOpcode(); } void MOS6510::asl_instr() { PutEffAddrDataByte(); flags.C = Cycle_Data & 0x80; flags.setNZ(Cycle_Data <<= 1); } void MOS6510::asla_instr() { flags.C = Register_Accumulator & 0x80; flags.setNZ(Register_Accumulator <<= 1); interruptsAndNextOpcode(); } void MOS6510::branch_instr(bool condition) { /* * 2 cycles spent before arriving here. spend 0 - 2 cycles here; * - condition false: Continue immediately to FetchNextInstr. * * Otherwise read the byte following the opcode (which is already scheduled to occur on this cycle). * This effort is wasted. Then calculate address of the branch target. If branch is on same page, * then continue at that insn on next cycle (this delays IRQs by 1 clock for some reason, allegedly). * * If the branch is on different memory page, issue a spurious read with wrong high byte before * continuing at the correct address. */ if (condition) { // issue the spurious read for next insn here. cpuRead(Register_ProgramCounter); Cycle_HighByteWrongEffectiveAddress = (Register_ProgramCounter & 0xff00) | ((Register_ProgramCounter + static_cast(Cycle_Data)) & 0xff); Cycle_EffectiveAddress = Register_ProgramCounter + static_cast(Cycle_Data); // Check for page boundary crossing if (Cycle_EffectiveAddress == Cycle_HighByteWrongEffectiveAddress) { cycleCount ++; // Hack: delay the interrupt past this instruction. if (interruptCycle >> 3 == cycleCount >> 3) interruptCycle += 2; } Register_ProgramCounter = Cycle_EffectiveAddress; } else { // branch not taken: skip the following spurious read insn and go to FetchNextInstr immediately. interruptsAndNextOpcode(); } } void MOS6510::bcc_instr() { branch_instr(!flags.C); } void MOS6510::bcs_instr() { branch_instr(flags.C); } void MOS6510::beq_instr() { branch_instr(flags.Z); } void MOS6510::bit_instr() { flags.Z = (Register_Accumulator & Cycle_Data) == 0; flags.N = Cycle_Data & 0x80; flags.V = Cycle_Data & 0x40; interruptsAndNextOpcode(); } void MOS6510::bmi_instr() { branch_instr(flags.N); } void MOS6510::bne_instr() { branch_instr(!flags.Z); } void MOS6510::bpl_instr() { branch_instr(!flags.N); } void MOS6510::bvc_instr() { branch_instr(!flags.V); } void MOS6510::bvs_instr() { branch_instr(flags.V); } void MOS6510::clc_instr() { flags.C = false; interruptsAndNextOpcode(); } void MOS6510::clv_instr() { flags.V = false; interruptsAndNextOpcode(); } void MOS6510::cmp_instr() { const uint_least16_t tmp = static_cast(Register_Accumulator) - Cycle_Data; flags.setNZ(tmp); flags.C = tmp < 0x100; interruptsAndNextOpcode(); } void MOS6510::cpx_instr() { const uint_least16_t tmp = static_cast(Register_X) - Cycle_Data; flags.setNZ(tmp); flags.C = tmp < 0x100; interruptsAndNextOpcode(); } void MOS6510::cpy_instr() { const uint_least16_t tmp = static_cast(Register_Y) - Cycle_Data; flags.setNZ(tmp); flags.C = tmp < 0x100; interruptsAndNextOpcode(); } void MOS6510::dec_instr() { PutEffAddrDataByte(); flags.setNZ(--Cycle_Data); } void MOS6510::dex_instr() { flags.setNZ(--Register_X); interruptsAndNextOpcode(); } void MOS6510::dey_instr() { flags.setNZ(--Register_Y); interruptsAndNextOpcode(); } void MOS6510::eor_instr() { flags.setNZ(Register_Accumulator ^= Cycle_Data); interruptsAndNextOpcode(); } void MOS6510::inc_instr() { PutEffAddrDataByte(); flags.setNZ(++Cycle_Data); } void MOS6510::inx_instr() { flags.setNZ(++Register_X); interruptsAndNextOpcode(); } void MOS6510::iny_instr() { flags.setNZ(++Register_Y); interruptsAndNextOpcode(); } void MOS6510::lda_instr() { flags.setNZ(Register_Accumulator = Cycle_Data); interruptsAndNextOpcode(); } void MOS6510::ldx_instr() { flags.setNZ(Register_X = Cycle_Data); interruptsAndNextOpcode(); } void MOS6510::ldy_instr() { flags.setNZ(Register_Y = Cycle_Data); interruptsAndNextOpcode(); } void MOS6510::lsr_instr() { PutEffAddrDataByte(); flags.C = Cycle_Data & 0x01; flags.setNZ(Cycle_Data >>= 1); } void MOS6510::lsra_instr() { flags.C = Register_Accumulator & 0x01; flags.setNZ(Register_Accumulator >>= 1); interruptsAndNextOpcode(); } void MOS6510::ora_instr() { flags.setNZ(Register_Accumulator |= Cycle_Data); interruptsAndNextOpcode(); } void MOS6510::pla_instr() { Register_StackPointer++; const uint_least16_t addr = endian_16(SP_PAGE, Register_StackPointer); flags.setNZ(Register_Accumulator = cpuRead (addr)); } void MOS6510::plp_instr() { interruptsAndNextOpcode(); } void MOS6510::rol_instr() { const uint8_t newC = Cycle_Data & 0x80; PutEffAddrDataByte(); Cycle_Data <<= 1; if (flags.C) Cycle_Data |= 0x01; flags.setNZ(Cycle_Data); flags.C = newC; } void MOS6510::rola_instr() { const uint8_t newC = Register_Accumulator & 0x80; Register_Accumulator <<= 1; if (flags.C) Register_Accumulator |= 0x01; flags.setNZ(Register_Accumulator); flags.C = newC; interruptsAndNextOpcode(); } void MOS6510::ror_instr() { const uint8_t newC = Cycle_Data & 0x01; PutEffAddrDataByte(); Cycle_Data >>= 1; if (flags.C) Cycle_Data |= 0x80; flags.setNZ(Cycle_Data); flags.C = newC; } void MOS6510::rora_instr() { const uint8_t newC = Register_Accumulator & 0x01; Register_Accumulator >>= 1; if (flags.C) Register_Accumulator |= 0x80; flags.setNZ(Register_Accumulator); flags.C = newC; interruptsAndNextOpcode(); } void MOS6510::sbx_instr() { const unsigned int tmp = (Register_X & Register_Accumulator) - Cycle_Data; flags.setNZ(Register_X = tmp & 0xff); flags.C = tmp < 0x100; interruptsAndNextOpcode(); } void MOS6510::sbc_instr() { doSBC(); interruptsAndNextOpcode(); } void MOS6510::sec_instr() { flags.C = true; interruptsAndNextOpcode(); } void MOS6510::shs_instr() { Register_StackPointer = Register_Accumulator & Register_X; Cycle_Data = (endian_16hi8(Cycle_EffectiveAddress) + 1) & Register_StackPointer; if (Cycle_HighByteWrongEffectiveAddress != Cycle_EffectiveAddress) Cycle_EffectiveAddress = endian_16(Cycle_Data, endian_16lo8(Cycle_EffectiveAddress)); PutEffAddrDataByte(); } void MOS6510::tax_instr() { flags.setNZ(Register_X = Register_Accumulator); interruptsAndNextOpcode(); } void MOS6510::tay_instr() { flags.setNZ(Register_Y = Register_Accumulator); interruptsAndNextOpcode(); } void MOS6510::tsx_instr() { flags.setNZ(Register_X = Register_StackPointer); interruptsAndNextOpcode(); } void MOS6510::txa_instr() { flags.setNZ(Register_Accumulator = Register_X); interruptsAndNextOpcode(); } void MOS6510::txs_instr() { Register_StackPointer = Register_X; interruptsAndNextOpcode(); } void MOS6510::tya_instr() { flags.setNZ(Register_Accumulator = Register_Y); interruptsAndNextOpcode(); } void MOS6510::illegal_instr() { cycleCount --; } //-------------------------------------------------------------------------// //-------------------------------------------------------------------------// // Generic Instruction Undocumented Opcodes // // See documented 6502-nmo.opc by Adam Vardy for more details // //-------------------------------------------------------------------------// //-------------------------------------------------------------------------// /** * Undocumented - This opcode ANDs the contents of the A register with an immediate value and * then LSRs the result. */ void MOS6510::alr_instr() { Register_Accumulator &= Cycle_Data; flags.C = Register_Accumulator & 0x01; flags.setNZ(Register_Accumulator >>= 1); interruptsAndNextOpcode(); } /** * Undocumented - ANC ANDs the contents of the A register with an immediate value and then * moves bit 7 of A into the Carry flag. This opcode works basically * identically to AND #immed. except that the Carry flag is set to the same * state that the Negative flag is set to. */ void MOS6510::anc_instr() { flags.setNZ(Register_Accumulator &= Cycle_Data); flags.C = flags.N; interruptsAndNextOpcode(); } /** * Undocumented - This opcode ANDs the contents of the A register with an immediate value and * then RORs the result. (Implementation based on that of Frodo C64 Emulator) */ void MOS6510::arr_instr() { const uint8_t data = Cycle_Data & Register_Accumulator; Register_Accumulator = data >> 1; if (flags.C) Register_Accumulator |= 0x80; if (flags.D) { flags.N = flags.C; flags.Z = Register_Accumulator == 0; flags.V = (data ^ Register_Accumulator) & 0x40; if ((data & 0x0f) + (data & 0x01) > 5) Register_Accumulator = (Register_Accumulator & 0xf0) | ((Register_Accumulator + 6) & 0x0f); flags.C = ((data + (data & 0x10)) & 0x1f0) > 0x50; if (flags.C) Register_Accumulator += 0x60; } else { flags.setNZ(Register_Accumulator); flags.C = Register_Accumulator & 0x40; flags.V = (Register_Accumulator & 0x40) ^ ((Register_Accumulator & 0x20) << 1); } interruptsAndNextOpcode(); } /** * Undocumented - This opcode ASLs the contents of a memory location and then ORs the result * with the accumulator. */ void MOS6510::aso_instr() { PutEffAddrDataByte(); flags.C = Cycle_Data & 0x80; Cycle_Data <<= 1; flags.setNZ(Register_Accumulator |= Cycle_Data); } /** * Undocumented - This opcode DECs the contents of a memory location and then CMPs the result * with the A register. */ void MOS6510::dcm_instr() { PutEffAddrDataByte(); Cycle_Data--; const uint_least16_t tmp = static_cast(Register_Accumulator) - Cycle_Data; flags.setNZ(tmp); flags.C = tmp < 0x100; } /** * Undocumented - This opcode INCs the contents of a memory location and then SBCs the result * from the A register. */ void MOS6510::ins_instr() { PutEffAddrDataByte(); Cycle_Data++; doSBC(); } /** * Undocumented - This opcode ANDs the contents of a memory location with the contents of the * stack pointer register and stores the result in the accumulator, the X * register, and the stack pointer. Affected flags: N Z. */ void MOS6510::las_instr() { flags.setNZ(Cycle_Data &= Register_StackPointer); Register_Accumulator = Cycle_Data; Register_X = Cycle_Data; Register_StackPointer = Cycle_Data; interruptsAndNextOpcode(); } /** * Undocumented - This opcode loads both the accumulator and the X register with the contents * of a memory location. */ void MOS6510::lax_instr() { flags.setNZ(Register_Accumulator = Register_X = Cycle_Data); interruptsAndNextOpcode(); } /** * Undocumented - LSE LSRs the contents of a memory location and then EORs the result with * the accumulator. */ void MOS6510::lse_instr() { PutEffAddrDataByte(); flags.C = Cycle_Data & 0x01; Cycle_Data >>= 1; flags.setNZ(Register_Accumulator ^= Cycle_Data); } /** * Undocumented - This opcode ORs the A register with #xx (the "magic" value), * ANDs the result with an immediate value, and then stores the result in both A and X. */ void MOS6510::oal_instr() { flags.setNZ(Register_X = (Register_Accumulator = (Cycle_Data & (Register_Accumulator | magic)))); interruptsAndNextOpcode(); } /** * Undocumented - RLA ROLs the contents of a memory location and then ANDs the result with * the accumulator. */ void MOS6510::rla_instr() { const uint8_t newC = Cycle_Data & 0x80; PutEffAddrDataByte(); Cycle_Data = Cycle_Data << 1; if (flags.C) Cycle_Data |= 0x01; flags.C = newC; flags.setNZ(Register_Accumulator &= Cycle_Data); } /** * Undocumented - RRA RORs the contents of a memory location and then ADCs the result with * the accumulator. */ void MOS6510::rra_instr() { const uint8_t newC = Cycle_Data & 0x01; PutEffAddrDataByte(); Cycle_Data >>= 1; if (flags.C) Cycle_Data |= 0x80; flags.C = newC; doADC(); } //-------------------------------------------------------------------------// /** * Create new CPU emu. * * @param context * The Event Context */ MOS6510::MOS6510(EventContext *context) : eventContext(*context), #ifdef DEBUG m_fdbg(stdout), #endif m_nosteal("CPU-nosteal", *this, &MOS6510::eventWithoutSteals), m_steal("CPU-steal", *this, &MOS6510::eventWithSteals) { //---------------------------------------------------------------------- // Build up the processor instruction table for (int i = 0; i < 0x100; i++) { #if DEBUG > 1 printf("Building Command %d[%02x]... ", i, i); #endif /* * So: what cycles are marked as stealable? Rules are: * * - CPU performs either read or write at every cycle. Reads are * always stealable. Writes are rare. * * - Every instruction begins with a sequence of reads. Writes, * if any, are at the end for most instructions. */ int buildCycle = i << 3; typedef enum { WRITE, READ } AccessMode; AccessMode access = WRITE; bool legalMode = true; bool legalInstr = true; switch (i) { // Accumulator or Implied addressing case ASLn: case CLCn: case CLDn: case CLIn: case CLVn: case DEXn: case DEYn: case INXn: case INYn: case LSRn: case NOPn_: case PHAn: case PHPn: case PLAn: case PLPn: case ROLn: case RORn: case SECn: case SEDn: case SEIn: case TAXn: case TAYn: case TSXn: case TXAn: case TXSn: case TYAn: instrTable[buildCycle++].func = &MOS6510::throwAwayFetch; break; // Immediate and Relative Addressing Mode Handler case ADCb: case ANDb: case ANCb_: case ANEb: case ASRb: case ARRb: case BCCr: case BCSr: case BEQr: case BMIr: case BNEr: case BPLr: case BRKn: case BVCr: case BVSr: case CMPb: case CPXb: case CPYb: case EORb: case LDAb: case LDXb: case LDYb: case LXAb: case NOPb_: case ORAb: case SBCb_: case SBXb: case RTIn: case RTSn: instrTable[buildCycle++].func = &MOS6510::FetchDataByte; break; // Zero Page Addressing Mode Handler - Read & RMW case ADCz: case ANDz: case BITz: case CMPz: case CPXz: case CPYz: case EORz: case LAXz: case LDAz: case LDXz: case LDYz: case ORAz: case NOPz_: case SBCz: case ASLz: case DCPz: case DECz: case INCz: case ISBz: case LSRz: case ROLz: case RORz: case SREz: case SLOz: case RLAz: case RRAz: access = READ; case SAXz: case STAz: case STXz: case STYz: instrTable[buildCycle++].func = &MOS6510::FetchLowAddr; break; // Zero Page with X Offset Addressing Mode Handler // these issue extra reads on the 0 page, but we don't care about it // because there are no detectable effects from them. These reads // occur during the "wasted" cycle. case ADCzx: case ANDzx: case CMPzx: case EORzx: case LDAzx: case LDYzx: case NOPzx_: case ORAzx: case SBCzx: case ASLzx: case DCPzx: case DECzx: case INCzx: case ISBzx: case LSRzx: case RLAzx: case ROLzx: case RORzx: case RRAzx: case SLOzx: case SREzx: access = READ; case STAzx: case STYzx: instrTable[buildCycle++].func = &MOS6510::FetchLowAddrX; // operates on 0 page in read mode. Truly side-effect free. instrTable[buildCycle++].func = &MOS6510::WasteCycle; break; // Zero Page with Y Offset Addressing Mode Handler case LDXzy: case LAXzy: access = READ; case STXzy: case SAXzy: instrTable[buildCycle++].func = &MOS6510::FetchLowAddrY; // operates on 0 page in read mode. Truly side-effect free. instrTable[buildCycle++].func = &MOS6510::WasteCycle; break; // Absolute Addressing Mode Handler case ADCa: case ANDa: case BITa: case CMPa: case CPXa: case CPYa: case EORa: case LAXa: case LDAa: case LDXa: case LDYa: case NOPa: case ORAa: case SBCa: case ASLa: case DCPa: case DECa: case INCa: case ISBa: case LSRa: case ROLa: case RORa: case SLOa: case SREa: case RLAa: case RRAa: access = READ; case JMPw: case SAXa: case STAa: case STXa: case STYa: instrTable[buildCycle++].func = &MOS6510::FetchLowAddr; instrTable[buildCycle++].func = &MOS6510::FetchHighAddr; break; case JSRw: instrTable[buildCycle++].func = &MOS6510::FetchLowAddr; break; // Absolute With X Offset Addressing Mode Handler (Read) case ADCax: case ANDax: case CMPax: case EORax: case LDAax: case LDYax: case NOPax_: case ORAax: case SBCax: access = READ; instrTable[buildCycle++].func = &MOS6510::FetchLowAddr; instrTable[buildCycle++].func = &MOS6510::FetchHighAddrX2; // this cycle is skipped if the address is already correct. // otherwise, it will be read and ignored. instrTable[buildCycle++].func = &MOS6510::throwAwayRead; break; // Absolute X (RMW; no page crossing handled, always reads before writing) case ASLax: case DCPax: case DECax: case INCax: case ISBax: case LSRax: case RLAax: case ROLax: case RORax: case RRAax: case SLOax: case SREax: access = READ; case SHYax: case STAax: instrTable[buildCycle++].func = &MOS6510::FetchLowAddr; instrTable[buildCycle++].func = &MOS6510::FetchHighAddrX; instrTable[buildCycle++].func = &MOS6510::throwAwayRead; break; // Absolute With Y Offset Addresing Mode Handler (Read) case ADCay: case ANDay: case CMPay: case EORay: case LASay: case LAXay: case LDAay: case LDXay: case ORAay: case SBCay: access = READ; instrTable[buildCycle++].func = &MOS6510::FetchLowAddr; instrTable[buildCycle++].func = &MOS6510::FetchHighAddrY2; instrTable[buildCycle++].func = &MOS6510::throwAwayRead; break; // Absolute Y (No page crossing handled) case DCPay: case ISBay: case RLAay: case RRAay: case SLOay: case SREay: access = READ; case SHAay: case SHSay: case SHXay: case STAay: instrTable[buildCycle++].func = &MOS6510::FetchLowAddr; instrTable[buildCycle++].func = &MOS6510::FetchHighAddrY; instrTable[buildCycle++].func = &MOS6510::throwAwayRead; break; // Absolute Indirect Addressing Mode Handler case JMPi: instrTable[buildCycle++].func = &MOS6510::FetchLowPointer; instrTable[buildCycle++].func = &MOS6510::FetchHighPointer; instrTable[buildCycle++].func = &MOS6510::FetchLowEffAddr; instrTable[buildCycle++].func = &MOS6510::FetchHighEffAddr; break; // Indexed with X Preinc Addressing Mode Handler case ADCix: case ANDix: case CMPix: case EORix: case LAXix: case LDAix: case ORAix: case SBCix: case DCPix: case ISBix: case SLOix: case SREix: case RLAix: case RRAix: access = READ; case SAXix: case STAix: instrTable[buildCycle++].func = &MOS6510::FetchLowPointer; instrTable[buildCycle++].func = &MOS6510::FetchLowPointerX; instrTable[buildCycle++].func = &MOS6510::FetchLowEffAddr; instrTable[buildCycle++].func = &MOS6510::FetchHighEffAddr; break; // Indexed with Y Postinc Addressing Mode Handler (Read) case ADCiy: case ANDiy: case CMPiy: case EORiy: case LAXiy: case LDAiy: case ORAiy: case SBCiy: access = READ; instrTable[buildCycle++].func = &MOS6510::FetchLowPointer; instrTable[buildCycle++].func = &MOS6510::FetchLowEffAddr; instrTable[buildCycle++].func = &MOS6510::FetchHighEffAddrY2; instrTable[buildCycle++].func = &MOS6510::throwAwayRead; break; // Indexed Y (No page crossing handled) case DCPiy: case ISBiy: case RLAiy: case RRAiy: case SLOiy: case SREiy: access = READ; case SHAiy: case STAiy: instrTable[buildCycle++].func = &MOS6510::FetchLowPointer; instrTable[buildCycle++].func = &MOS6510::FetchLowEffAddr; instrTable[buildCycle++].func = &MOS6510::FetchHighEffAddrY; instrTable[buildCycle++].func = &MOS6510::throwAwayRead; break; default: legalMode = false; break; } if (access == READ) { instrTable[buildCycle++].func = &MOS6510::FetchEffAddrDataByte; } //--------------------------------------------------------------------------------------- // Addressing Modes Finished, other cycles are instruction dependent switch(i) { case ADCz: case ADCzx: case ADCa: case ADCax: case ADCay: case ADCix: case ADCiy: case ADCb: instrTable[buildCycle++].func = &MOS6510::adc_instr; break; case ANCb_: instrTable[buildCycle++].func = &MOS6510::anc_instr; break; case ANDz: case ANDzx: case ANDa: case ANDax: case ANDay: case ANDix: case ANDiy: case ANDb: instrTable[buildCycle++].func = &MOS6510::and_instr; break; case ANEb: // Also known as XAA instrTable[buildCycle++].func = &MOS6510::ane_instr; break; case ARRb: instrTable[buildCycle++].func = &MOS6510::arr_instr; break; case ASLn: instrTable[buildCycle++].func = &MOS6510::asla_instr; break; case ASLz: case ASLzx: case ASLa: case ASLax: instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::asl_instr; instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::PutEffAddrDataByte; break; case ASRb: // Also known as ALR instrTable[buildCycle++].func = &MOS6510::alr_instr; break; case BCCr: instrTable[buildCycle++].func = &MOS6510::bcc_instr; instrTable[buildCycle++].func = &MOS6510::throwAwayRead; break; case BCSr: instrTable[buildCycle++].func = &MOS6510::bcs_instr; instrTable[buildCycle++].func = &MOS6510::throwAwayRead; break; case BEQr: instrTable[buildCycle++].func = &MOS6510::beq_instr; instrTable[buildCycle++].func = &MOS6510::throwAwayRead; break; case BITz: case BITa: instrTable[buildCycle++].func = &MOS6510::bit_instr; break; case BMIr: instrTable[buildCycle++].func = &MOS6510::bmi_instr; instrTable[buildCycle++].func = &MOS6510::throwAwayRead; break; case BNEr: instrTable[buildCycle++].func = &MOS6510::bne_instr; instrTable[buildCycle++].func = &MOS6510::throwAwayRead; break; case BPLr: instrTable[buildCycle++].func = &MOS6510::bpl_instr; instrTable[buildCycle++].func = &MOS6510::throwAwayRead; break; case BRKn: instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::PushHighPC; instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::brkPushLowPC; instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::brk_instr; instrTable[buildCycle++].func = &MOS6510::IRQLoRequest; instrTable[buildCycle++].func = &MOS6510::IRQHiRequest; instrTable[buildCycle++].func = &MOS6510::fetchNextOpcode; break; case BVCr: instrTable[buildCycle++].func = &MOS6510::bvc_instr; instrTable[buildCycle++].func = &MOS6510::throwAwayRead; break; case BVSr: instrTable[buildCycle++].func = &MOS6510::bvs_instr; instrTable[buildCycle++].func = &MOS6510::throwAwayRead; break; case CLCn: instrTable[buildCycle++].func = &MOS6510::clc_instr; break; case CLDn: instrTable[buildCycle++].func = &MOS6510::cld_instr; break; case CLIn: instrTable[buildCycle++].func = &MOS6510::cli_instr; break; case CLVn: instrTable[buildCycle++].func = &MOS6510::clv_instr; break; case CMPz: case CMPzx: case CMPa: case CMPax: case CMPay: case CMPix: case CMPiy: case CMPb: instrTable[buildCycle++].func = &MOS6510::cmp_instr; break; case CPXz: case CPXa: case CPXb: instrTable[buildCycle++].func = &MOS6510::cpx_instr; break; case CPYz: case CPYa: case CPYb: instrTable[buildCycle++].func = &MOS6510::cpy_instr; break; case DCPz: case DCPzx: case DCPa: case DCPax: case DCPay: case DCPix: case DCPiy: // Also known as DCM instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::dcm_instr; instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::PutEffAddrDataByte; break; case DECz: case DECzx: case DECa: case DECax: instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::dec_instr; instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::PutEffAddrDataByte; break; case DEXn: instrTable[buildCycle++].func = &MOS6510::dex_instr; break; case DEYn: instrTable[buildCycle++].func = &MOS6510::dey_instr; break; case EORz: case EORzx: case EORa: case EORax: case EORay: case EORix: case EORiy: case EORb: instrTable[buildCycle++].func = &MOS6510::eor_instr; break; #if 0 // HLT, also known as JAM case 0x02: case 0x12: case 0x22: case 0x32: case 0x42: case 0x52: case 0x62: case 0x72: case 0x92: case 0xb2: case 0xd2: case 0xf2: case 0x02: case 0x12: case 0x22: case 0x32: case 0x42: case 0x52: case 0x62: case 0x72: case 0x92: case 0xb2: case 0xd2: case 0xf2: instrTable[buildCycle++].func = hlt_instr; break; #endif case INCz: case INCzx: case INCa: case INCax: instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::inc_instr; instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::PutEffAddrDataByte; break; case INXn: instrTable[buildCycle++].func = &MOS6510::inx_instr; break; case INYn: instrTable[buildCycle++].func = &MOS6510::iny_instr; break; case ISBz: case ISBzx: case ISBa: case ISBax: case ISBay: case ISBix: case ISBiy: // Also known as INS instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::ins_instr; instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::PutEffAddrDataByte; break; case JSRw: instrTable[buildCycle++].func = &MOS6510::WasteCycle; instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::PushHighPC; instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::PushLowPC; instrTable[buildCycle++].func = &MOS6510::FetchHighAddr; case JMPw: case JMPi: instrTable[buildCycle++].func = &MOS6510::jmp_instr; break; case LASay: instrTable[buildCycle++].func = &MOS6510::las_instr; break; case LAXz: case LAXzy: case LAXa: case LAXay: case LAXix: case LAXiy: instrTable[buildCycle++].func = &MOS6510::lax_instr; break; case LDAz: case LDAzx: case LDAa: case LDAax: case LDAay: case LDAix: case LDAiy: case LDAb: instrTable[buildCycle++].func = &MOS6510::lda_instr; break; case LDXz: case LDXzy: case LDXa: case LDXay: case LDXb: instrTable[buildCycle++].func = &MOS6510::ldx_instr; break; case LDYz: case LDYzx: case LDYa: case LDYax: case LDYb: instrTable[buildCycle++].func = &MOS6510::ldy_instr; break; case LSRn: instrTable[buildCycle++].func = &MOS6510::lsra_instr; break; case LSRz: case LSRzx: case LSRa: case LSRax: instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::lsr_instr; instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::PutEffAddrDataByte; break; case NOPn_: case NOPb_: case NOPz_: case NOPzx_: case NOPa: case NOPax_: // NOPb NOPz NOPzx - Also known as SKBn // NOPa NOPax - Also known as SKWn break; case LXAb: // Also known as OAL instrTable[buildCycle++].func = &MOS6510::oal_instr; break; case ORAz: case ORAzx: case ORAa: case ORAax: case ORAay: case ORAix: case ORAiy: case ORAb: instrTable[buildCycle++].func = &MOS6510::ora_instr; break; case PHAn: instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::pha_instr; break; case PHPn: instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::PushSR; break; case PLAn: // should read the value at current stack register. // Truly side-effect free. instrTable[buildCycle++].func = &MOS6510::WasteCycle; instrTable[buildCycle++].func = &MOS6510::pla_instr; break; case PLPn: // should read the value at current stack register. // Truly side-effect free. instrTable[buildCycle++].func = &MOS6510::WasteCycle; instrTable[buildCycle++].func = &MOS6510::PopSR; instrTable[buildCycle++].func = &MOS6510::plp_instr; break; case RLAz: case RLAzx: case RLAix: case RLAa: case RLAax: case RLAay: case RLAiy: instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::rla_instr; instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::PutEffAddrDataByte; break; case ROLn: instrTable[buildCycle++].func = &MOS6510::rola_instr; break; case ROLz: case ROLzx: case ROLa: case ROLax: instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::rol_instr; instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::PutEffAddrDataByte; break; case RORn: instrTable[buildCycle++].func = &MOS6510::rora_instr; break; case RORz: case RORzx: case RORa: case RORax: instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::ror_instr; instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::PutEffAddrDataByte; break; case RRAa: case RRAax: case RRAay: case RRAz: case RRAzx: case RRAix: case RRAiy: instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::rra_instr; instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::PutEffAddrDataByte; break; case RTIn: // should read the value at current stack register. // Truly side-effect free. instrTable[buildCycle++].func = &MOS6510::WasteCycle; instrTable[buildCycle++].func = &MOS6510::PopSR; instrTable[buildCycle++].func = &MOS6510::PopLowPC; instrTable[buildCycle++].func = &MOS6510::PopHighPC; instrTable[buildCycle++].func = &MOS6510::rti_instr; break; case RTSn: // should read the value at current stack register. // Truly side-effect free. instrTable[buildCycle++].func = &MOS6510::WasteCycle; instrTable[buildCycle++].func = &MOS6510::PopLowPC; instrTable[buildCycle++].func = &MOS6510::PopHighPC; instrTable[buildCycle++].func = &MOS6510::rts_instr; break; case SAXz: case SAXzy: case SAXa: case SAXix: // Also known as AXS instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::axs_instr; break; case SBCz: case SBCzx: case SBCa: case SBCax: case SBCay: case SBCix: case SBCiy: case SBCb_: instrTable[buildCycle++].func = &MOS6510::sbc_instr; break; case SBXb: instrTable[buildCycle++].func = &MOS6510::sbx_instr; break; case SECn: instrTable[buildCycle++].func = &MOS6510::sec_instr; break; case SEDn: instrTable[buildCycle++].func = &MOS6510::sed_instr; break; case SEIn: instrTable[buildCycle++].func = &MOS6510::sei_instr; break; case SHAay: case SHAiy: // Also known as AXA instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::axa_instr; break; case SHSay: // Also known as TAS instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::shs_instr; break; case SHXay: // Also known as XAS instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::xas_instr; break; case SHYax: // Also known as SAY instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::say_instr; break; case SLOz: case SLOzx: case SLOa: case SLOax: case SLOay: case SLOix: case SLOiy: // Also known as ASO instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::aso_instr; instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::PutEffAddrDataByte; break; case SREz: case SREzx: case SREa: case SREax: case SREay: case SREix: case SREiy: // Also known as LSE instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::lse_instr; instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::PutEffAddrDataByte; break; case STAz: case STAzx: case STAa: case STAax: case STAay: case STAix: case STAiy: instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::sta_instr; break; case STXz: case STXzy: case STXa: instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::stx_instr; break; case STYz: case STYzx: case STYa: instrTable[buildCycle].nosteal = true; instrTable[buildCycle++].func = &MOS6510::sty_instr; break; case TAXn: instrTable[buildCycle++].func = &MOS6510::tax_instr; break; case TAYn: instrTable[buildCycle++].func = &MOS6510::tay_instr; break; case TSXn: instrTable[buildCycle++].func = &MOS6510::tsx_instr; break; case TXAn: instrTable[buildCycle++].func = &MOS6510::txa_instr; break; case TXSn: instrTable[buildCycle++].func = &MOS6510::txs_instr; break; case TYAn: instrTable[buildCycle++].func = &MOS6510::tya_instr; break; default: legalInstr = false; break; } /* Missing an addressing mode or implementation makes opcode invalid. * These are normally called HLT instructions. In the hardware, the * CPU state machine locks up and will never recover. */ if (!(legalMode && legalInstr)) { instrTable[buildCycle++].func = &MOS6510::illegal_instr; } // check for IRQ triggers or fetch next opcode... instrTable[buildCycle].func = &MOS6510::interruptsAndNextOpcode; #if DEBUG > 1 printf("Done [%d Cycles]\n", buildCycle - (i << 3)); #endif } // Intialise Processor Registers Register_Accumulator = 0; Register_X = 0; Register_Y = 0; Cycle_EffectiveAddress = 0; Cycle_Data = 0; #ifdef DEBUG dodump = false; #endif Initialise(); } /** * Initialise CPU Emulation (Registers). */ void MOS6510::Initialise() { // Reset stack Register_StackPointer = 0xFF; // Reset Cycle Count cycleCount = (BRKn << 3) + 6; // fetchNextOpcode // Reset Status Register flags.reset(); // Set PC to some value Register_ProgramCounter = 0; // IRQs pending check irqAssertedOnPin = false; nmiFlag = false; rstFlag = false; interruptCycle = MAX; // Signals rdy = true; eventContext.schedule(m_nosteal, 0, EVENT_CLOCK_PHI2); } /** * Reset CPU Emulation. */ void MOS6510::reset() { // Internal Stuff Initialise(); // Set processor port to the default values cpuWrite(0, 0x2F); cpuWrite(1, 0x37); // Requires External Bits // Read from reset vector for program entry point endian_16lo8(Cycle_EffectiveAddress, cpuRead(0xFFFC)); endian_16hi8(Cycle_EffectiveAddress, cpuRead(0xFFFD)); Register_ProgramCounter = Cycle_EffectiveAddress; } /** * Module Credits. */ const char *MOS6510::credit = { "MOS6510 Cycle Exact Emulation\n" "\t(C) 2000 Simon A. White\n" "\t(C) 2008-2010 Antti S. Lankila\n" "\t(C) 2011-2012 Leandro Nini\n" }; void MOS6510::debug(bool enable, FILE *out) { #ifdef DEBUG dodump = enable; if (!(out && enable)) m_fdbg = stdout; else m_fdbg = out; #endif } }