stella/src/emucore/CartDPCPlus.cxx

780 lines
21 KiB
C++
Raw Normal View History

//============================================================================
//
// 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 <cassert>
#include <cstring>
#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(uInt16 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 #<DFxDATA mode of value is 0
myFastFetch = (value == 0);
break;
case 0x01: // PARAMETER - set parameter used by CALLFUNCTION (not all functions use the parameter)
if(myParameterPointer < 8)
myParameter[myParameterPointer++] = value;
break;
case 0x02: // CALLFUNCTION
callFunction(value);
break;
case 0x03: // reserved
case 0x04: // reserved
break;
case 0x05: // WAVEFORM0
case 0x06: // WAVEFORM1
case 0x07: // WAVEFORM2
myMusicWaveforms[index - 5] = value & 0x7f;
break;
}
break;
// DFxPUSH - Push value into data bank
case 0x07:
{
myCounters[index] = (myCounters[index] - 0x1) & 0x0fff;
myDisplayImage[myCounters[index]] = value;
break;
}
// DFxHI - data pointer high byte
case 0x08:
{
myCounters[index] = (((uInt16)value & 0x0F) << 8) | (myCounters[index] & 0x00ff);
break;
}
case 0x09:
{
switch (index)
{
case 0x00: // RRESET - Random Number Generator Reset
{
myRandomNumber = 0x2B435044; // "DPC+"
break;
}
case 0x01: // RWRITE0 - update byte 0 of random number
{
myRandomNumber = (myRandomNumber & 0xFFFFFF00) | value;
break;
}
case 0x02: // RWRITE1 - update byte 1 of random number
{
myRandomNumber = (myRandomNumber & 0xFFFF00FF) | (value<<8);
break;
}
case 0x03: // RWRITE2 - update byte 2 of random number
{
myRandomNumber = (myRandomNumber & 0xFF00FFFF) | (value<<16);
break;
}
case 0x04: // RWRITE3 - update byte 3 of random number
{
myRandomNumber = (myRandomNumber & 0x00FFFFFF) | (value<<24);
break;
}
case 0x05: // NOTE0
case 0x06: // NOTE1
case 0x07: // NOTE2
{
myMusicFrequencies[index-5] = myFrequencyImage[(value<<2)] +
(myFrequencyImage[(value<<2)+1]<<8) +
(myFrequencyImage[(value<<2)+2]<<16) +
(myFrequencyImage[(value<<2)+3]<<24);
break;
}
default:
break;
}
break;
}
// DFxWRITE - write into data bank
case 0x0a:
{
myDisplayImage[myCounters[index]] = value;
myCounters[index] = (myCounters[index] + 0x1) & 0x0fff;
break;
}
default:
{
break;
}
}
}
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;
}
}
return false;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartridgeDPCPlus::bank(uInt16 bank)
{
if(bankLocked()) return false;
// Remember what bank we're in
myCurrentBank = bank;
uInt16 offset = myCurrentBank << 12;
uInt16 shift = mySystem->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;
}