//============================================================================ // // MM MM 6666 555555 0000 2222 // MMMM MMMM 66 66 55 00 00 22 22 // MM MMM MM 66 55 00 00 22 // MM M MM 66666 55555 00 00 22222 -- "A 6502 Microprocessor Emulator" // MM MM 66 66 55 00 00 22 // MM MM 66 66 55 55 00 00 22 // MM MM 6666 5555 0000 222222 // // Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony // and the Stella Team // // See the file "License.txt" for information on usage and redistribution of // this file, and for a DISCLAIMER OF ALL WARRANTIES. //============================================================================ #ifdef DEBUGGER_SUPPORT #include "Debugger.hxx" #include "Expression.hxx" #include "CartDebug.hxx" #include "PackedBitArray.hxx" #include "Base.hxx" // Flags for disassembly types #define DISASM_CODE CartDebug::CODE // #define DISASM_GFX CartDebug::GFX // TODO - uncomment when needed // #define DISASM_PGFX CartDebug::PGFX // TODO - uncomment when needed #define DISASM_DATA CartDebug::DATA // #define DISASM_ROW CartDebug::ROW // TODO - uncomment when needed #define DISASM_WRITE CartDebug::WRITE #define DISASM_NONE 0 #else // Flags for disassembly types #define DISASM_CODE 0 // #define DISASM_GFX 0 // TODO - uncomment when needed // #define DISASM_PGFX 0 // TODO - uncomment when needed #define DISASM_DATA 0 // #define DISASM_ROW 0 // TODO - uncomment when needed #define DISASM_NONE 0 #define DISASM_WRITE 0 #endif #include "Settings.hxx" #include "Vec.hxx" #include "TIA.hxx" #include "M6532.hxx" #include "System.hxx" #include "M6502.hxx" #include "DispatchResult.hxx" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - M6502::M6502(const Settings& settings) : myExecutionStatus(0), mySystem(nullptr), mySettings(settings), A(0), X(0), Y(0), SP(0), IR(0), PC(0), N(false), V(false), B(false), D(false), I(false), notZ(false), C(false), icycles(0), myNumberOfDistinctAccesses(0), myLastAddress(0), myLastPeekAddress(0), myLastPokeAddress(0), myLastPeekBaseAddress(0), myLastPokeBaseAddress(0), myLastSrcAddressS(-1), myLastSrcAddressA(-1), myLastSrcAddressX(-1), myLastSrcAddressY(-1), myDataAddressForPoke(0), myOnHaltCallback(nullptr), myHaltRequested(false), myGhostReadsTrap(true), myStepStateByInstruction(false) { #ifdef DEBUGGER_SUPPORT myDebugger = nullptr; myJustHitReadTrapFlag = myJustHitWriteTrapFlag = false; #endif } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void M6502::install(System& system) { // Remember which system I'm installed in mySystem = &system; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void M6502::reset() { // Clear the execution status flags myExecutionStatus = 0; // Set registers to random or default values bool devSettings = mySettings.getBool("dev.settings"); const string& cpurandom = mySettings.getString(devSettings ? "dev.cpurandom" : "plr.cpurandom"); SP = BSPF::containsIgnoreCase(cpurandom, "S") ? mySystem->randGenerator().next() : 0xfd; A = BSPF::containsIgnoreCase(cpurandom, "A") ? mySystem->randGenerator().next() : 0x00; X = BSPF::containsIgnoreCase(cpurandom, "X") ? mySystem->randGenerator().next() : 0x00; Y = BSPF::containsIgnoreCase(cpurandom, "Y") ? mySystem->randGenerator().next() : 0x00; PS(BSPF::containsIgnoreCase(cpurandom, "P") ? mySystem->randGenerator().next() : 0x20); icycles = 0; // Load PC from the reset vector PC = uInt16(mySystem->peek(0xfffc)) | (uInt16(mySystem->peek(0xfffd)) << 8); myLastAddress = myLastPeekAddress = myLastPokeAddress = myLastPeekBaseAddress = myLastPokeBaseAddress; myLastSrcAddressS = myLastSrcAddressA = myLastSrcAddressX = myLastSrcAddressY = -1; myDataAddressForPoke = 0; myHaltRequested = false; myGhostReadsTrap = mySettings.getBool("dbg.ghostreadstrap"); myLastBreakCycle = -1; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - inline uInt8 M6502::peek(uInt16 address, uInt8 flags) { handleHalt(); //////////////////////////////////////////////// // TODO - move this logic directly into CartAR if(address != myLastAddress) { myNumberOfDistinctAccesses++; myLastAddress = address; } //////////////////////////////////////////////// mySystem->incrementCycles(SYSTEM_CYCLES_PER_CPU); icycles += SYSTEM_CYCLES_PER_CPU; uInt8 result = mySystem->peek(address, flags); myLastPeekAddress = address; #ifdef DEBUGGER_SUPPORT if(myReadTraps.isInitialized() && myReadTraps.isSet(address) && (myGhostReadsTrap || flags != DISASM_NONE)) { myLastPeekBaseAddress = myDebugger->getBaseAddress(myLastPeekAddress, true); // mirror handling int cond = evalCondTraps(); if(cond > -1) { myJustHitReadTrapFlag = true; stringstream msg; msg << "RTrap" << (flags == DISASM_NONE ? "G[" : "[") << Common::Base::HEX2 << cond << "]" << (myTrapCondNames[cond].empty() ? ": " : "If: {" + myTrapCondNames[cond] + "} "); myHitTrapInfo.message = msg.str(); myHitTrapInfo.address = address; } } #endif // DEBUGGER_SUPPORT return result; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - inline void M6502::poke(uInt16 address, uInt8 value, uInt8 flags) { //////////////////////////////////////////////// // TODO - move this logic directly into CartAR if(address != myLastAddress) { myNumberOfDistinctAccesses++; myLastAddress = address; } //////////////////////////////////////////////// mySystem->incrementCycles(SYSTEM_CYCLES_PER_CPU); icycles += SYSTEM_CYCLES_PER_CPU; mySystem->poke(address, value, flags); myLastPokeAddress = address; #ifdef DEBUGGER_SUPPORT if(myWriteTraps.isInitialized() && myWriteTraps.isSet(address)) { myLastPokeBaseAddress = myDebugger->getBaseAddress(myLastPokeAddress, false); // mirror handling int cond = evalCondTraps(); if(cond > -1) { myJustHitWriteTrapFlag = true; stringstream msg; msg << "WTrap[" << Common::Base::HEX2 << cond << "]" << (myTrapCondNames[cond].empty() ? ": " : "If: {" + myTrapCondNames[cond] + "} "); myHitTrapInfo.message = msg.str(); myHitTrapInfo.address = address; } } #endif // DEBUGGER_SUPPORT } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void M6502::requestHalt() { if (!myOnHaltCallback) throw runtime_error("onHaltCallback not configured"); myHaltRequested = true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - inline void M6502::handleHalt() { if (myHaltRequested) { myOnHaltCallback(); myHaltRequested = false; } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void M6502::execute(uInt64 number, DispatchResult& result) { _execute(number, result); #ifdef DEBUGGER_SUPPORT // Debugger hack: this ensures that stepping a "STA WSYNC" will actually end at the // beginning of the next line (otherwise, the next instruction would be stepped in order for // the halt to take effect). This is safe because as we know that the next cycle will be a read // cycle anyway. handleHalt(); #endif // Make sure that the hardware state matches the current system clock. This is necessary // to maintain a consistent state for the debugger after stepping and to make sure // that audio samples are generated for the whole timeslice. mySystem->tia().updateEmulation(); mySystem->m6532().updateEmulation(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool M6502::execute(uInt64 number) { DispatchResult result; execute(number, result); return result.isSuccess(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - inline void M6502::_execute(uInt64 cycles, DispatchResult& result) { // Clear all of the execution status bits except for the fatal error bit myExecutionStatus &= FatalErrorBit; #ifdef DEBUGGER_SUPPORT TIA& tia = mySystem->tia(); M6532& riot = mySystem->m6532(); #endif uInt64 previousCycles = mySystem->cycles(); uInt64 currentCycles = 0; // Loop until execution is stopped or a fatal error occurs for(;;) { while (!myExecutionStatus && currentCycles < cycles * SYSTEM_CYCLES_PER_CPU) { #ifdef DEBUGGER_SUPPORT // Don't break if we haven't actually executed anything yet if (myLastBreakCycle != mySystem->cycles()) { if(myJustHitReadTrapFlag || myJustHitWriteTrapFlag) { bool read = myJustHitReadTrapFlag; myJustHitReadTrapFlag = myJustHitWriteTrapFlag = false; myLastBreakCycle = mySystem->cycles(); result.setDebugger(currentCycles, myHitTrapInfo.message, myHitTrapInfo.address, read); return; } if(myBreakPoints.isInitialized() && myBreakPoints.isSet(PC)) { myLastBreakCycle = mySystem->cycles(); result.setDebugger(currentCycles, "BP: ", PC); return; } int cond = evalCondBreaks(); if(cond > -1) { stringstream msg; msg << "CBP[" << Common::Base::HEX2 << cond << "]: " << myCondBreakNames[cond]; myLastBreakCycle = mySystem->cycles(); result.setDebugger(currentCycles, msg.str()); return; } } int cond = evalCondSaveStates(); if(cond > -1) { stringstream msg; msg << "conditional savestate [" << Common::Base::HEX2 << cond << "]"; myDebugger->addState(msg.str()); } #endif // DEBUGGER_SUPPORT uInt16 operandAddress = 0, intermediateAddress = 0; uInt8 operand = 0; // Reset the peek/poke address pointers myLastPeekAddress = myLastPokeAddress = myDataAddressForPoke = 0; icycles = 0; // Fetch instruction at the program counter IR = peek(PC++, DISASM_CODE); // This address represents a code section // Call code to execute the instruction switch(IR) { // 6502 instruction emulation is generated by an M4 macro file #include "M6502.ins" default: // Oops, illegal instruction executed so set fatal error flag myExecutionStatus |= FatalErrorBit; } currentCycles = (mySystem->cycles() - previousCycles); #ifdef DEBUGGER_SUPPORT if(myStepStateByInstruction) { // Check out M6502::execute for an explanation. handleHalt(); tia.updateEmulation(); riot.updateEmulation(); } #endif } // See if we need to handle an interrupt if((myExecutionStatus & MaskableInterruptBit) || (myExecutionStatus & NonmaskableInterruptBit)) { // Yes, so handle the interrupt interruptHandler(); } // See if execution has been stopped if(myExecutionStatus & StopExecutionBit) { // Yes, so answer that everything finished fine result.setOk(currentCycles); return; } // See if a fatal error has occured if(myExecutionStatus & FatalErrorBit) { // Yes, so answer that something when wrong result.setFatal(currentCycles + icycles); return; } // See if we've executed the specified number of instructions if (currentCycles >= cycles * SYSTEM_CYCLES_PER_CPU) { // Yes, so answer that everything finished fine result.setOk(currentCycles); return; } } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void M6502::interruptHandler() { // Handle the interrupt if((myExecutionStatus & MaskableInterruptBit) && !I) { mySystem->incrementCycles(7 * SYSTEM_CYCLES_PER_CPU); mySystem->poke(0x0100 + SP--, (PC - 1) >> 8); mySystem->poke(0x0100 + SP--, (PC - 1) & 0x00ff); mySystem->poke(0x0100 + SP--, PS() & (~0x10)); D = false; I = true; PC = uInt16(mySystem->peek(0xFFFE)) | (uInt16(mySystem->peek(0xFFFF)) << 8); } else if(myExecutionStatus & NonmaskableInterruptBit) { mySystem->incrementCycles(7 * SYSTEM_CYCLES_PER_CPU); mySystem->poke(0x0100 + SP--, (PC - 1) >> 8); mySystem->poke(0x0100 + SP--, (PC - 1) & 0x00ff); mySystem->poke(0x0100 + SP--, PS() & (~0x10)); D = false; PC = uInt16(mySystem->peek(0xFFFA)) | (uInt16(mySystem->peek(0xFFFB)) << 8); } // Clear the interrupt bits in myExecutionStatus myExecutionStatus &= ~(MaskableInterruptBit | NonmaskableInterruptBit); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool M6502::save(Serializer& out) const { const string& CPU = name(); try { out.putString(CPU); out.putByte(A); // Accumulator out.putByte(X); // X index register out.putByte(Y); // Y index register out.putByte(SP); // Stack Pointer out.putByte(IR); // Instruction register out.putShort(PC); // Program Counter out.putBool(N); // N flag for processor status register out.putBool(V); // V flag for processor status register out.putBool(B); // B flag for processor status register out.putBool(D); // D flag for processor status register out.putBool(I); // I flag for processor status register out.putBool(notZ); // Z flag complement for processor status register out.putBool(C); // C flag for processor status register out.putByte(myExecutionStatus); // Indicates the number of distinct memory accesses out.putInt(myNumberOfDistinctAccesses); // Indicates the last address(es) which was accessed out.putShort(myLastAddress); out.putShort(myLastPeekAddress); out.putShort(myLastPokeAddress); out.putShort(myDataAddressForPoke); out.putInt(myLastSrcAddressS); out.putInt(myLastSrcAddressA); out.putInt(myLastSrcAddressX); out.putInt(myLastSrcAddressY); out.putBool(myHaltRequested); out.putBool(myStepStateByInstruction); out.putBool(myGhostReadsTrap); out.putLong(myLastBreakCycle); } catch(...) { cerr << "ERROR: M6502::save" << endl; return false; } return true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool M6502::load(Serializer& in) { const string& CPU = name(); try { if(in.getString() != CPU) return false; A = in.getByte(); // Accumulator X = in.getByte(); // X index register Y = in.getByte(); // Y index register SP = in.getByte(); // Stack Pointer IR = in.getByte(); // Instruction register PC = in.getShort(); // Program Counter N = in.getBool(); // N flag for processor status register V = in.getBool(); // V flag for processor status register B = in.getBool(); // B flag for processor status register D = in.getBool(); // D flag for processor status register I = in.getBool(); // I flag for processor status register notZ = in.getBool(); // Z flag complement for processor status register C = in.getBool(); // C flag for processor status register myExecutionStatus = in.getByte(); // Indicates the number of distinct memory accesses myNumberOfDistinctAccesses = in.getInt(); // Indicates the last address(es) which was accessed myLastAddress = in.getShort(); myLastPeekAddress = in.getShort(); myLastPokeAddress = in.getShort(); myDataAddressForPoke = in.getShort(); myLastSrcAddressS = in.getInt(); myLastSrcAddressA = in.getInt(); myLastSrcAddressX = in.getInt(); myLastSrcAddressY = in.getInt(); myHaltRequested = in.getBool(); myStepStateByInstruction = in.getBool(); myGhostReadsTrap = in.getBool(); myLastBreakCycle = in.getLong(); } catch(...) { cerr << "ERROR: M6502::load" << endl; return false; } return true; } #ifdef DEBUGGER_SUPPORT // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void M6502::attach(Debugger& debugger) { // Remember the debugger for this microprocessor myDebugger = &debugger; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt32 M6502::addCondBreak(Expression* e, const string& name) { myCondBreaks.emplace_back(e); myCondBreakNames.push_back(name); updateStepStateByInstruction(); return uInt32(myCondBreaks.size() - 1); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool M6502::delCondBreak(uInt32 idx) { if(idx < myCondBreaks.size()) { Vec::removeAt(myCondBreaks, idx); Vec::removeAt(myCondBreakNames, idx); updateStepStateByInstruction(); return true; } return false; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void M6502::clearCondBreaks() { myCondBreaks.clear(); myCondBreakNames.clear(); updateStepStateByInstruction(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - const StringList& M6502::getCondBreakNames() const { return myCondBreakNames; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt32 M6502::addCondSaveState(Expression* e, const string& name) { myCondSaveStates.emplace_back(e); myCondSaveStateNames.push_back(name); updateStepStateByInstruction(); return uInt32(myCondSaveStates.size() - 1); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool M6502::delCondSaveState(uInt32 idx) { if(idx < myCondSaveStates.size()) { Vec::removeAt(myCondSaveStates, idx); Vec::removeAt(myCondSaveStateNames, idx); updateStepStateByInstruction(); return true; } return false; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void M6502::clearCondSaveStates() { myCondSaveStates.clear(); myCondSaveStateNames.clear(); updateStepStateByInstruction(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - const StringList& M6502::getCondSaveStateNames() const { return myCondSaveStateNames; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt32 M6502::addCondTrap(Expression* e, const string& name) { myTrapConds.emplace_back(e); myTrapCondNames.push_back(name); updateStepStateByInstruction(); return uInt32(myTrapConds.size() - 1); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool M6502::delCondTrap(uInt32 brk) { if(brk < myTrapConds.size()) { Vec::removeAt(myTrapConds, brk); Vec::removeAt(myTrapCondNames, brk); updateStepStateByInstruction(); return true; } return false; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void M6502::clearCondTraps() { myTrapConds.clear(); myTrapCondNames.clear(); updateStepStateByInstruction(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - const StringList& M6502::getCondTrapNames() const { return myTrapCondNames; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void M6502::updateStepStateByInstruction() { myStepStateByInstruction = myCondBreaks.size() || myCondSaveStates.size() || myTrapConds.size(); } #endif // DEBUGGER_SUPPORT