/* * (C) 2006 Dirk W. Hoffmann. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "C64.h" int debugcnt = 0; int debugirq = 0; // -------------------------------------------------------------------------------- // Execution thread // -------------------------------------------------------------------------------- // Exit function of the main execution loop void threadCleanup(void* thisC64) { assert(thisC64 != NULL); C64 *c64 = (C64 *)thisC64; c64->threadCleanup(); c64->debug(1, "Execution thread terminated\n"); c64->putMessage(MSG_HALT); } // Main execution loop void *runThread(void *thisC64) { assert(thisC64 != NULL); C64 *c64 = (C64 *)thisC64; c64->debug(1, "Execution thread started\n"); c64->putMessage(MSG_RUN); // Configure thread properties... pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); pthread_cleanup_push(threadCleanup, thisC64); // Prepare to run... c64->cpu->clearErrorState(); c64->floppy->cpu->clearErrorState(); c64->restartTimer(); while (1) { if (!c64->executeOneLine()) break; if (c64->getRasterline() == 0 && c64->getFrame() % 8 == 0) { // Check if thread was requested to terminate pthread_testcancel(); } } pthread_cleanup_pop(1); pthread_exit(NULL); } // -------------------------------------------------------------------------------- // Class methods // -------------------------------------------------------------------------------- C64::C64() { name = "C64"; debug(1, "Creating virtual C64\n"); p = NULL; warp = false; alwaysWarp = false; warpLoad = false; // Create components cpu = new CPU(); mem = new C64Memory(); vic = new VIC(); sid = new SIDWrapper(); cia1 = new CIA1(); cia2 = new CIA2(); iec = new IEC(); expansionport = new ExpansionPort(); floppy = new VC1541(); keyboard = new Keyboard(); joystick1 = new Joystick(); joystick2 = new Joystick(); // Register sub components VirtualComponent *subcomponents[] = { cpu, mem, vic, sid, cia1, cia2, iec, expansionport, floppy, &datasette, keyboard, joystick1, joystick2, NULL }; registerSubComponents(subcomponents, sizeof(subcomponents)); setC64(this); // Register snapshot items SnapshotItem items[] = { { &warpLoad, sizeof(warpLoad), KEEP_ON_RESET }, { &alwaysWarp, sizeof(alwaysWarp), CLEAR_ON_RESET }, { &warp, sizeof(warp), CLEAR_ON_RESET }, { &cycles, sizeof(cycles), CLEAR_ON_RESET }, { &frame, sizeof(frame), CLEAR_ON_RESET }, { &rasterline, sizeof(rasterline), CLEAR_ON_RESET }, { &rasterlineCycle, sizeof(rasterlineCycle), CLEAR_ON_RESET }, { NULL, 0, 0 }}; registerSnapshotItems(items, sizeof(items)); // Configure machine type and reset setPAL(); reset(); // Initialie mach timer info mach_timebase_info(&timebase); // Initialize snapshot ringbuffer (BackInTime feature) for (unsigned i = 0; i < BACK_IN_TIME_BUFFER_SIZE; i++) backInTimeHistory[i] = new Snapshot(); backInTimeWritePtr = 0; } // Construction and destruction C64::~C64() { // Halt emulator halt(); // Release all components delete joystick2; delete joystick1; delete floppy; delete expansionport; delete iec; delete keyboard; delete cia2; delete cia1; delete sid; delete vic; delete cpu; delete mem; debug(1, "Cleaned up virtual C64\n", this); } void C64::reset() { suspend(); VirtualComponent::reset(); cpu->mem = mem; cpu->setPC(0xFCE2); rasterlineCycle = 1; nanoTargetTime = 0UL; ping(); resume(); } void C64::ping() { debug (1, "Pinging virtual C64\n"); VirtualComponent::ping(); putMessage(MSG_WARP, warp); putMessage(MSG_ALWAYS_WARP, alwaysWarp); } void C64::dumpState() { msg("C64:\n"); msg("----\n\n"); msg(" Machine type : %s\n", isPAL() ? "PAL" : "NTSC"); msg(" Frames per second : %d\n", vic->getFramesPerSecond()); msg(" Rasterlines per frame : %d\n", vic->getRasterlinesPerFrame()); msg(" Cycles per rasterline : %d\n", vic->getCyclesPerRasterline()); msg(" Current cycle : %llu\n", cycles); msg(" Current frame : %d\n", frame); msg(" Current rasterline : %d\n", rasterline); msg("Current rasterline cycle : %d\n", rasterlineCycle); msg("\n"); } Message *C64::getMessage() { return queue.getMessage(); } void C64::putMessage(int id, int i, void *p, const char *c) { queue.putMessage(id, i, p, c); } // ----------------------------------------------------------------------------------------------- // Configure // ----------------------------------------------------------------------------------------------- void C64::setPAL() { suspend(); vic->setChipModel(VIC::MOS6569_PAL); sid->setPAL(); debug(2, "Switching VIC chip model to MOS6569 (PAL)\n"); resume(); } void C64::setNTSC() { suspend(); vic->setChipModel(VIC::MOS6567_NTSC); sid->setNTSC(); debug(2, "Switching VIC chip model to MOS6567 (NTSC)\n"); resume(); } void C64::setWarp(bool b) { if (warp == b) return; warp = b; // Warping has the unavoidable drawback that audio playback gets out of sync. // Therefore, we silence SID during warp mode and smoothly bring back sound when // warping ends. if (warp) { // Quickly fade out SID sid->rampDown(); } else { // Smoothly fade in SID sid->rampUp(); restartTimer(); } putMessage(MSG_WARP, b); } void C64::setAlwaysWarp(bool b) { if (alwaysWarp == b) return; if (alwaysWarp != b) { alwaysWarp = b; setWarp(b); putMessage(MSG_ALWAYS_WARP, b); } } void C64::setWarpLoad(bool b) { warpLoad = b; } // ----------------------------------------------------------------------------------------------- // Loading and saving // ----------------------------------------------------------------------------------------------- void C64::loadFromSnapshot(Snapshot *snapshot) { if (snapshot == NULL) return; uint8_t *ptr = snapshot->getData(); loadFromBuffer(&ptr); ping(); } void C64::saveToSnapshot(Snapshot *snapshot) { if (snapshot == NULL) return; snapshot->setTimestamp(time(NULL)); // snapshot->setPAL(isPAL()); snapshot->takeScreenshot((uint32_t *)vic->screenBuffer(), isPAL()); snapshot->alloc(stateSize()); uint8_t *ptr = snapshot->getData(); saveToBuffer(&ptr); } // ----------------------------------------------------------------------------------------------- // Control // ----------------------------------------------------------------------------------------------- void C64::runstopRestore() { // Note: The restore key is directly connected to the NMI line of the CPU // Thus, the runstop/restore key combination triggers an interrupts that causes a soft reset keyboard->pressRunstopKey(); cpu->setNMILineReset(); // Hold runstop key down for a while... sleepMicrosec((uint64_t)100000); keyboard->releaseRunstopKey(); } bool C64::isRunnable() { return mem->basicRomIsLoaded() && mem->charRomIsLoaded() && mem->kernelRomIsLoaded() && floppy->mem->romIsLoaded(); } void C64::run() { if (isHalted()) { // Check for ROM images if (getMissingRoms()) { putMessage(MSG_ROM_MISSING, getMissingRoms()); return; } // Power on sub components sid->run(); // Start execution thread pthread_create(&p, NULL, runThread, (void *)this); } } bool C64::isRunning() { return p != NULL; } void C64::halt() { if (isRunning()) { // Cancel execution thread pthread_cancel(p); // Wait until thread terminates pthread_join(p, NULL); // Finish the current command (to reach a clean state) step(); // Shut down sub components sid->halt(); } } bool C64::isHalted() { return p == NULL; } void C64::step() { // Clear error states cpu->clearErrorState(); floppy->cpu->clearErrorState(); // Execute next command do { executeOneCycle(); } while (!cpu->atBeginningOfNewCommand()); // We are now at cycle 0 of the next command // Execute one more cycle (and stop in cycle 1) executeOneCycle(); } // From Wolfgang Lorenz: Clock.txt // // o2 high phase | o2 low phase // .-----. | .----------------. .------. .------. // | | | | 1. Next Clock | | | | | // .-> | CPU | ----> | 2. Fire Timers | --> | CIA1 | --> | CIA2 | -. // | | | | | 3. VIC | | | | | | // | '-----' | '----------------' '------' '------' | // '---------------------------------------------------------------' #define EXECUTE(x) \ cia1->executeOneCycle(); \ cia2->executeOneCycle(); \ if (!cpu->executeOneCycle()) result = false; \ if (!floppy->executeOneCycle()) result = false; \ datasette.execute(); \ cycles++; \ rasterlineCycle++; void C64::beginOfRasterline() { // First cycle of rasterline if (rasterline == 0) { vic->beginFrame(); } vic->beginRasterline(rasterline); } void C64::endOfRasterline() { vic->endRasterline(); rasterlineCycle = 1; rasterline++; if (rasterline >= vic->getRasterlinesPerFrame()) { // Last rasterline of frame rasterline = 0; vic->endFrame(); frame++; // Increment time of day clocks every tenth of a second if (frame % (vic->getFramesPerSecond() / 10) == 0) { cia1->incrementTOD(); cia2->incrementTOD(); } // Take a snapshot once in a while if (frame % (vic->getFramesPerSecond() * 4) == 0) { takeSnapshot(); } // Execute remaining SID cycles sid->executeUntil(cycles); /* int diff = sid->resid->writePtr - sid->resid->readPtr; debug(2,"SID readCnt: %8d writeCnt: %8d readPtr: %8d writePtr: %8d diff: %8d volume:%d target:%d\n", sid->resid->readDataCnt, sid->resid->writeDataCnt, sid->resid->readPtr, sid->resid->writePtr, (diff > 0) ? diff : 44100 + diff,sid->resid->volume,sid->resid->targetVolume); sid->resid->readDataCnt = sid->resid->writeDataCnt = 0; */ // Execute the IEC bus iec->execute(); // Count some sheep (zzzzzz) ... if (!getWarp()) synchronizeTiming(); } } inline bool C64::executeOneCycle() { bool result = true; // Don't break execution switch(rasterlineCycle) { case 1: beginOfRasterline(); vic->cycle1(); EXECUTE(1); break; case 2: vic->cycle2(); EXECUTE(2); break; case 3: vic->cycle3(); EXECUTE(3); break; case 4: vic->cycle4(); EXECUTE(4); break; case 5: vic->cycle5(); EXECUTE(5); break; case 6: vic->cycle6(); EXECUTE(6); break; case 7: vic->cycle7(); EXECUTE(7); break; case 8: vic->cycle8(); EXECUTE(8); break; case 9: vic->cycle9(); EXECUTE(9); break; case 10: vic->cycle10(); EXECUTE(10); break; case 11: vic->cycle11(); EXECUTE(11); break; case 12: vic->cycle12(); EXECUTE(12); break; case 13: vic->cycle13(); EXECUTE(13); break; case 14: vic->cycle14(); EXECUTE(14); break; case 15: vic->cycle15(); EXECUTE(15); break; case 16: vic->cycle16(); EXECUTE(16); break; case 17: vic->cycle17(); EXECUTE(17); break; case 18: vic->cycle18(); EXECUTE(18); break; case 19: vic->cycle19to54(); EXECUTE(19); break; case 20: vic->cycle19to54(); EXECUTE(20); break; case 21: vic->cycle19to54(); EXECUTE(21); break; case 22: vic->cycle19to54(); EXECUTE(22); break; case 23: vic->cycle19to54(); EXECUTE(23); break; case 24: vic->cycle19to54(); EXECUTE(24); break; case 25: vic->cycle19to54(); EXECUTE(25); break; case 26: vic->cycle19to54(); EXECUTE(26); break; case 27: vic->cycle19to54(); EXECUTE(27); break; case 28: vic->cycle19to54(); EXECUTE(28); break; case 29: vic->cycle19to54(); EXECUTE(29); break; case 30: vic->cycle19to54(); EXECUTE(30); break; case 31: vic->cycle19to54(); EXECUTE(31); break; case 32: vic->cycle19to54(); EXECUTE(32); break; case 33: vic->cycle19to54(); EXECUTE(33); break; case 34: vic->cycle19to54(); EXECUTE(34); break; case 35: vic->cycle19to54(); EXECUTE(35); break; case 36: vic->cycle19to54(); EXECUTE(36); break; case 37: vic->cycle19to54(); EXECUTE(37); break; case 38: vic->cycle19to54(); EXECUTE(38); break; case 39: vic->cycle19to54(); EXECUTE(39); break; case 40: vic->cycle19to54(); EXECUTE(40); break; case 41: vic->cycle19to54(); EXECUTE(41); break; case 42: vic->cycle19to54(); EXECUTE(42); break; case 43: vic->cycle19to54(); EXECUTE(43); break; case 44: vic->cycle19to54(); EXECUTE(44); break; case 45: vic->cycle19to54(); EXECUTE(45); break; case 46: vic->cycle19to54(); EXECUTE(46); break; case 47: vic->cycle19to54(); EXECUTE(47); break; case 48: vic->cycle19to54(); EXECUTE(48); break; case 49: vic->cycle19to54(); EXECUTE(49); break; case 50: vic->cycle19to54(); EXECUTE(50); break; case 51: vic->cycle19to54(); EXECUTE(51); break; case 52: vic->cycle19to54(); EXECUTE(52); break; case 53: vic->cycle19to54(); EXECUTE(53); break; case 54: vic->cycle19to54(); EXECUTE(54); break; case 55: vic->cycle55(); EXECUTE(55); break; case 56: vic->cycle56(); EXECUTE(56); break; case 57: vic->cycle57(); EXECUTE(57); break; case 58: vic->cycle58(); EXECUTE(58); break; case 59: vic->cycle59(); EXECUTE(59); break; case 60: vic->cycle60(); EXECUTE(60); break; case 61: vic->cycle61(); EXECUTE(61); break; case 62: vic->cycle62(); EXECUTE(62); break; case 63: vic->cycle63(); EXECUTE(63); if (vic->getCyclesPerRasterline() == 63) { // last cycle for PAL machines endOfRasterline(); } break; case 64: vic->cycle64(); EXECUTE(64); break; case 65: vic->cycle65(); EXECUTE(65); endOfRasterline(); break; default: // can't reach assert(false); return false; } return result; } // Execute until the end of the rasterline inline bool C64::executeOneLine() { uint8_t lastCycle = vic->getCyclesPerRasterline(); for (int i = rasterlineCycle; i <= lastCycle; i++) { if (!executeOneCycle()) return false; } return true; } // ----------------------------------------------------------------------------------------------- // ROM and snapshot handling // ----------------------------------------------------------------------------------------------- uint8_t C64::getMissingRoms() { uint8_t missingRoms = 0; if (!mem->basicRomIsLoaded()) missingRoms |= BASIC_ROM; if (!mem->charRomIsLoaded()) missingRoms |= CHAR_ROM; if (!mem->kernelRomIsLoaded()) missingRoms |= KERNEL_ROM; if (!floppy->mem->romIsLoaded()) missingRoms |= VC1541_ROM; return missingRoms; } bool C64::loadRom(const char *filename) { bool result = false; suspend(); bool wasRunnable = isRunnable(); if (C64Memory::isBasicRom(filename)) { result = mem->loadBasicRom(filename); if (result) putMessage(MSG_ROM_LOADED, BASIC_ROM); } if (C64Memory::isCharRom(filename)) { result = mem->loadCharRom(filename); if (result) putMessage(MSG_ROM_LOADED, CHAR_ROM); } if (C64Memory::isKernelRom(filename)) { result = mem->loadKernelRom(filename); if (result) putMessage(MSG_ROM_LOADED, KERNEL_ROM); } if (VC1541Memory::is1541Rom(filename)) { result = floppy->mem->loadRom(filename); if (result) putMessage(MSG_ROM_LOADED, VC1541_ROM); } bool isNowRunnable = isRunnable(); if (!wasRunnable && isNowRunnable) { // Last missing ROM was loaded putMessage(MSG_ROM_COMPLETE); } resume(); return result; } void C64::takeSnapshot() { debug(3, "Taking snapshop %d (%p)\n", backInTimeWritePtr, backInTimeHistory[backInTimeWritePtr]); saveToSnapshot(backInTimeHistory[backInTimeWritePtr]); backInTimeWritePtr = (backInTimeWritePtr + 1) % BACK_IN_TIME_BUFFER_SIZE; } unsigned C64::numHistoricSnapshots() { for (int i = BACK_IN_TIME_BUFFER_SIZE - 1; i >= 0; i--) { if (!backInTimeHistory[i]->isEmpty()) return i + 1; } return 0; } Snapshot * C64::getHistoricSnapshot(int nr) { if (nr >= BACK_IN_TIME_BUFFER_SIZE) return NULL; int pos = (BACK_IN_TIME_BUFFER_SIZE + backInTimeWritePtr - 1 - nr) % BACK_IN_TIME_BUFFER_SIZE; Snapshot *snapshot = backInTimeHistory[pos]; assert(snapshot != NULL); if (snapshot->isEmpty()) return NULL; return snapshot; } // ----------------------------------------------------------------------------------------------- // Timing // ----------------------------------------------------------------------------------------------- void C64::restartTimer() { uint64_t kernelNow = mach_absolute_time(); uint64_t nanoNow = abs_to_nanos(kernelNow); nanoTargetTime = nanoNow + vic->getFrameDelay(); } void C64::synchronizeTiming() { const uint64_t earlyWakeup = 1500000; /* 1.5 milliseconds */ // Convert usec into kernel unit uint64_t kernelTargetTime = nanos_to_abs(nanoTargetTime); // Check how long we're supposed to sleep if (kernelTargetTime - mach_absolute_time() > 200000000 /* 0.2 sec */) { // The emulator is supposed to sleep unusually long. Timers seem to // be out of sync, so we better reset the synchronization timer debug(2, "Emulator lost synchronization. Restarting synchronization timer.\n"); restartTimer(); return; } // Sleep and update target timer // debug(2, "%p Sleeping for %lld\n", this, kernelTargetTime - mach_absolute_time()); int64_t jitter = sleepUntil(kernelTargetTime, earlyWakeup); nanoTargetTime += vic->getFrameDelay(); // debug(2, "Jitter = %d", jitter); if (jitter > 1000000000 /* 1 sec */) { // The emulator did not keep up with the real time clock. Instead of // running behind for a long time, we reset the synchronization timer debug(2, "Jitter exceeds limit. Restarting synchronization timer.\n"); restartTimer(); } } // --------------------------------------------------------------------------------------------- // Archives (disks, tapes, etc.) // --------------------------------------------------------------------------------------------- bool C64::flushArchive(Archive *a, int item) { uint16_t addr; int data; if (a == NULL) return false; addr = a->getDestinationAddrOfItem(item); a->selectItem(item); while (1) { data = a->getByte(); if (data < 0) break; mem->pokeRam(addr, (uint8_t)data); if (addr == 0xFFFF) break; addr++; } return true; } bool C64::mountArchive(Archive *a) { if (a == NULL) return false; floppy->insertDisk(a); return true; } bool C64::insertTape(TAPArchive *a) { if (a == NULL) return false; suspend(); debug("Inserting tape %p\n", a); datasette.insertTape(a); resume(); return true; } // --------------------------------------------------------------------------------------------- // Cartridges // --------------------------------------------------------------------------------------------- bool C64::attachCartridge(Cartridge *c) { return expansionport->attachCartridge(c); } void C64::detachCartridge() { expansionport->detachCartridge(); } bool C64::isCartridgeAttached() { return expansionport->getCartridgeAttached(); } // --------------------------------------------------------------------------------------------- // Misc // --------------------------------------------------------------------------------------------- void C64::threadCleanup() { p = NULL; debug(1, "Execution thread cleanup\n"); }