//============================================================================ // // SSSS tt lll lll // SS SS tt ll ll // SS tttttt eeee ll ll aaaa // SSSS tt ee ee ll ll aa // SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" // SS SS tt ee ll ll aa aa // SSSS ttt eeeee llll llll aaaaa // // Copyright (c) 1995-2013 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. // // $Id$ //============================================================================ #include #include #ifdef DEBUGGER_SUPPORT #include "Debugger.hxx" #endif #include "System.hxx" #include "Thumbulator.hxx" #include "CartDPCPlus.hxx" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CartridgeDPCPlus::CartridgeDPCPlus(const uInt8* image, uInt32 size, const Settings& settings) : Cartridge(settings), myFastFetch(false), myLDAimmediate(false), myParameterPointer(0), mySystemCycles(0), myFractionalClocks(0.0) { // Store image, making sure it's at least 29KB uInt32 minsize = 4096 * 6 + 4096 + 1024 + 255; mySize = BSPF_max(minsize, size); myImage = new uInt8[mySize]; memcpy(myImage, image, size); createCodeAccessBase(4096 * 6); // Pointer to the program ROM (24K @ 0 byte offset) myProgramImage = myImage; // Pointer to the display RAM myDisplayImage = myDPCRAM + 0xC00; // Pointer to the Frequency RAM myFrequencyImage = myDisplayImage + 0x1000; // If the image is larger than 29K, we assume any excess at the // beginning is ARM code, and skip over it if(size > 29 * 1024) myProgramImage += (size - 29 * 1024); #ifdef THUMB_SUPPORT // Create Thumbulator ARM emulator myThumbEmulator = new Thumbulator((uInt16*)(myProgramImage-0xC00), (uInt16*)myDPCRAM, settings.getBool("thumb.trapfatal")); #endif setInitialState(); // DPC+ always starts in bank 5 myStartBank = 5; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CartridgeDPCPlus::~CartridgeDPCPlus() { delete[] myImage; #ifdef THUMB_SUPPORT delete myThumbEmulator; #endif } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void CartridgeDPCPlus::reset() { // Update cycles to the current system cycles mySystemCycles = mySystem->cycles(); myFractionalClocks = 0.0; setInitialState(); // Upon reset we switch to the startup bank bank(myStartBank); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void CartridgeDPCPlus::setInitialState() { // Reset various ROM and RAM locations memset(myDPCRAM, 0, 8192); // Copy initial DPC display data and Frequency table state to Harmony RAM memcpy(myDisplayImage, myProgramImage + 0x6000, 0x1400); // Initialize the DPC data fetcher registers for(int i = 0; i < 8; ++i) myTops[i] = myBottoms[i] = myCounters[i] = myFractionalIncrements[i] = myFractionalCounters[i] = 0; // Set waveforms to first waveform entry myMusicWaveforms[0] = myMusicWaveforms[1] = myMusicWaveforms[2] = 0; // Initialize the DPC's random number generator register (must be non-zero) myRandomNumber = 0x2B435044; // "DPC+" } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void CartridgeDPCPlus::systemCyclesReset() { // Get the current system cycle uInt32 cycles = mySystem->cycles(); // Adjust the cycle counter so that it reflects the new value mySystemCycles -= cycles; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void CartridgeDPCPlus::install(System& system) { mySystem = &system; uInt16 shift = mySystem->pageShift(); uInt16 mask = mySystem->pageMask(); // Make sure the system we're being installed in has a page size that'll work assert(((0x1080 & mask) == 0) && ((0x1100 & mask) == 0)); System::PageAccess access(0, 0, 0, this, System::PA_READ); // Map all of the accesses to call peek and poke for(uInt32 i = 0x1000; i < 0x1080; i += (1 << shift)) mySystem->setPageAccess(i >> shift, access); // Install pages for the startup bank bank(myStartBank); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - inline void CartridgeDPCPlus::clockRandomNumberGenerator() { // Update random number generator (32-bit LFSR) myRandomNumber = ((myRandomNumber & (1<<10)) ? 0x10adab1e: 0x00) ^ ((myRandomNumber >> 11) | (myRandomNumber << 21)); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - inline void CartridgeDPCPlus::priorClockRandomNumberGenerator() { // Update random number generator (32-bit LFSR, reversed) myRandomNumber = ((myRandomNumber & (1<<31)) ? ((0x10adab1e^myRandomNumber) << 11) | ((0x10adab1e^myRandomNumber) >> 21) : (myRandomNumber << 11) | (myRandomNumber >> 21)); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - inline void CartridgeDPCPlus::updateMusicModeDataFetchers() { // Calculate the number of cycles since the last update Int32 cycles = mySystem->cycles() - mySystemCycles; mySystemCycles = mySystem->cycles(); // Calculate the number of DPC OSC clocks since the last update double clocks = ((20000.0 * cycles) / 1193191.66666667) + myFractionalClocks; Int32 wholeClocks = (Int32)clocks; myFractionalClocks = clocks - (double)wholeClocks; if(wholeClocks <= 0) { return; } // Let's update counters and flags of the music mode data fetchers for(int x = 0; x <= 2; ++x) { myMusicCounters[x] += myMusicFrequencies[x]; } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - inline void CartridgeDPCPlus::callFunction(uInt8 value) { // myParameter uInt16 ROMdata = (myParameter[1] << 8) + myParameter[0]; switch (value) { case 0: // Parameter Pointer reset myParameterPointer = 0; break; case 1: // Copy ROM to fetcher for(int i = 0; i < myParameter[3]; ++i) myDisplayImage[myCounters[myParameter[2] & 0x7]+i] = myProgramImage[ROMdata+i]; myParameterPointer = 0; break; case 2: // Copy value to fetcher for(int i = 0; i < myParameter[3]; ++i) myDisplayImage[myCounters[myParameter[2]]+i] = myParameter[0]; myParameterPointer = 0; break; #ifdef THUMB_SUPPORT case 254: case 255: // Call user written ARM code (most likely be C compiled for ARM) try { myThumbEmulator->run(); } catch(const string& error) { if(!mySystem->autodetectMode()) { #ifdef DEBUGGER_SUPPORT Debugger::debugger().startWithFatalError(error); #else cout << error << endl; #endif } } break; #endif // reserved } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt8 CartridgeDPCPlus::peek(uInt16 address) { address &= 0x0FFF; uInt8 peekvalue = myProgramImage[(myCurrentBank << 12) + address]; uInt8 flag; // In debugger/bank-locked mode, we ignore all hotspots and in general // anything that can change the internal state of the cart if(bankLocked()) return peekvalue; // Check if we're in Fast Fetch mode and the prior byte was an A9 (LDA #value) if(myFastFetch && myLDAimmediate) { if(peekvalue < 0x0028) // if #value is a read-register then we want to use that as the address address = peekvalue; } myLDAimmediate = false; if(address < 0x0028) { uInt8 result = 0; // Get the index of the data fetcher that's being accessed uInt32 index = address & 0x07; uInt32 function = (address >> 3) & 0x07; // Update flag for selected data fetcher flag = (((myTops[index]-(myCounters[index] & 0x00ff)) & 0xFF) > ((myTops[index]-myBottoms[index]) & 0xFF)) ? 0xFF : 0; switch(function) { case 0x00: { switch(index) { case 0x00: // RANDOM0NEXT - advance and return byte 0 of random clockRandomNumberGenerator(); result = myRandomNumber & 0xFF; break; case 0x01: // RANDOM0PRIOR - return to prior and return byte 0 of random priorClockRandomNumberGenerator(); result = myRandomNumber & 0xFF; break; case 0x02: // RANDOM1 result = (myRandomNumber>>8) & 0xFF; break; case 0x03: // RANDOM2 result = (myRandomNumber>>16) & 0xFF; break; case 0x04: // RANDOM3 result = (myRandomNumber>>24) & 0xFF; break; case 0x05: // AMPLITUDE { // Update the music data fetchers (counter & flag) updateMusicModeDataFetchers(); // using myDisplayImage[] instead of myProgramImage[] because waveforms // can be modified during runtime. uInt32 i = myDisplayImage[(myMusicWaveforms[0] << 5) + (myMusicCounters[0] >> 27)] + myDisplayImage[(myMusicWaveforms[1] << 5) + (myMusicCounters[1] >> 27)] + myDisplayImage[(myMusicWaveforms[2] << 5) + (myMusicCounters[2] >> 27)]; result = (uInt8)i; break; } case 0x06: // reserved case 0x07: // reserved break; } break; } // DFxDATA - display data read case 0x01: { result = myDisplayImage[myCounters[index]]; myCounters[index] = (myCounters[index] + 0x1) & 0x0fff; break; } // DFxDATAW - display data read AND'd w/flag ("windowed") case 0x02: { result = myDisplayImage[myCounters[index]] & flag; myCounters[index] = (myCounters[index] + 0x1) & 0x0fff; break; } // DFxFRACDATA - display data read w/fractional increment case 0x03: { result = myDisplayImage[myFractionalCounters[index] >> 8]; myFractionalCounters[index] = (myFractionalCounters[index] + myFractionalIncrements[index]) & 0x0fffff; break; } case 0x04: { switch (index) { case 0x00: // DF0FLAG case 0x01: // DF1FLAG case 0x02: // DF2FLAG case 0x03: // DF3FLAG { result = flag; break; } case 0x04: // reserved case 0x05: // reserved case 0x06: // reserved case 0x07: // reserved break; } break; } default: { result = 0; } } return result; } else { // Switch banks if necessary switch(address) { case 0x0FF6: // Set the current bank to the first 4k bank bank(0); break; case 0x0FF7: // Set the current bank to the second 4k bank bank(1); break; case 0x0FF8: // Set the current bank to the third 4k bank bank(2); break; case 0x0FF9: // Set the current bank to the fourth 4k bank bank(3); break; case 0x0FFA: // Set the current bank to the fifth 4k bank bank(4); break; case 0x0FFB: // Set the current bank to the last 4k bank bank(5); break; default: break; } if(myFastFetch) myLDAimmediate = (peekvalue == 0xA9); return peekvalue; } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeDPCPlus::poke(uInt16 address, uInt8 value) { address &= 0x0FFF; if((address >= 0x0028) && (address < 0x0080)) { // Get the index of the data fetcher that's being accessed uInt32 index = address & 0x07; uInt32 function = ((address - 0x28) >> 3) & 0x0f; switch(function) { //DFxFRACLOW - fractional data pointer low byte case 0x00: myFractionalCounters[index] = (myFractionalCounters[index] & 0x0F0000) | ((uInt16)value << 8); break; // DFxFRACHI - fractional data pointer high byte case 0x01: myFractionalCounters[index] = (((uInt16)value & 0x0F) << 16) | (myFractionalCounters[index] & 0x00ffff); break; //DFxFRACINC - Fractional Increment amount case 0x02: myFractionalIncrements[index] = value; myFractionalCounters[index] = myFractionalCounters[index] & 0x0FFF00; break; // DFxTOP - set top of window (for reads of DFxDATAW) case 0x03: myTops[index] = value; break; // DFxBOT - set bottom of window (for reads of DFxDATAW) case 0x04: myBottoms[index] = value; break; // DFxLOW - data pointer low byte case 0x05: myCounters[index] = (myCounters[index] & 0x0F00) | value ; break; // Control registers case 0x06: switch (index) { case 0x00: // FASTFETCH - turns on LDA #pageShift(); // Setup the page access methods for the current bank System::PageAccess access(0, 0, 0, this, System::PA_READ); // Map Program ROM image into the system for(uInt32 address = 0x1080; address < 0x2000; address += (1 << shift)) { access.codeAccessBase = &myCodeAccessBase[offset + (address & 0x0FFF)]; mySystem->setPageAccess(address >> shift, access); } return myBankChanged = true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt16 CartridgeDPCPlus::bank() const { return myCurrentBank; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt16 CartridgeDPCPlus::bankCount() const { return 6; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeDPCPlus::patch(uInt16 address, uInt8 value) { address &= 0x0FFF; // For now, we ignore attempts to patch the DPC address space if(address >= 0x0080) { myProgramImage[(myCurrentBank << 12) + (address & 0x0FFF)] = value; return myBankChanged = true; } else return false; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - const uInt8* CartridgeDPCPlus::getImage(int& size) const { size = mySize; return myImage; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeDPCPlus::save(Serializer& out) const { try { out.putString(name()); // Indicates which bank is currently active out.putShort(myCurrentBank); // Harmony RAM out.putByteArray(myDPCRAM, 8192); // The top registers for the data fetchers out.putByteArray(myTops, 8); // The bottom registers for the data fetchers out.putByteArray(myBottoms, 8); // The counter registers for the data fetchers out.putShortArray(myCounters, 8); // The counter registers for the fractional data fetchers out.putIntArray(myFractionalCounters, 8); // The fractional registers for the data fetchers out.putByteArray(myFractionalIncrements, 8); // The Fast Fetcher Enabled flag out.putBool(myFastFetch); out.putBool(myLDAimmediate); // Control Byte to update out.putByteArray(myParameter, 8); // The music counters out.putIntArray(myMusicCounters, 3); // The music frequencies out.putIntArray(myMusicFrequencies, 3); // The music waveforms out.putShortArray(myMusicWaveforms, 3); // The random number generator register out.putInt(myRandomNumber); out.putInt(mySystemCycles); out.putInt((uInt32)(myFractionalClocks * 100000000.0)); } catch(...) { cerr << "ERROR: CartridgeDPCPlus::save" << endl; return false; } return true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeDPCPlus::load(Serializer& in) { try { if(in.getString() != name()) return false; // Indicates which bank is currently active myCurrentBank = in.getShort(); // Harmony RAM in.getByteArray(myDPCRAM, 8192); // The top registers for the data fetchers in.getByteArray(myTops, 8); // The bottom registers for the data fetchers in.getByteArray(myBottoms, 8); // The counter registers for the data fetchers in.getShortArray(myCounters, 8); // The counter registers for the fractional data fetchers in.getIntArray(myFractionalCounters, 8); // The fractional registers for the data fetchers in.getByteArray(myFractionalIncrements, 8); // The Fast Fetcher Enabled flag myFastFetch = in.getBool(); myLDAimmediate = in.getBool(); // Control Byte to update in.getByteArray(myParameter, 8); // The music mode counters for the data fetchers in.getIntArray(myMusicCounters, 3); // The music mode frequency addends for the data fetchers in.getIntArray(myMusicFrequencies, 3); // The music waveforms in.getShortArray(myMusicWaveforms, 3); // The random number generator register myRandomNumber = in.getInt(); // Get system cycles and fractional clocks mySystemCycles = (Int32)in.getInt(); myFractionalClocks = (double)in.getInt() / 100000000.0; } catch(...) { cerr << "ERROR: CartridgeDPCPlus::load" << endl; return false; } // Now, go to the current bank bank(myCurrentBank); return true; }