stella/src/emucore/CartDPCPlus.cxx

818 lines
22 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-2010 by Bradford W. Mott 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>
#include "System.hxx"
#include "CartDPCPlus.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CartridgeDPCPlus::CartridgeDPCPlus(const uInt8* image, uInt32 size)
: myFastFetch(false),
myLDAimmediate(false),
mySelectByte(0),
myMusicVolume(0),
mySystemCycles(0),
myFractionalClocks(0.0)
{
// Make a copy of the entire image as-is, for use by getImage()
// (this wastes 29K of RAM, should be controlled by a #ifdef)
memcpy(myImageCopy, image, size);
// Copy the program ROM image into my buffer
memcpy(myProgramImage, image, 4096 * 6);
// Copy the display ROM image into my buffer
memcpy(myDisplayImage, image + 4096 * 6, 4096);
// Copy the Frequency ROM image into my buffer
memcpy(myFrequencyImage, image + 4096 * 6 + 4096, 1024);
// Initialize the DPC data fetcher registers
for(uInt16 i = 0; i < 8; ++i)
myTops[i] = myBottoms[i] = myCounters[i] = myFlags[i] = myFractionalIncrements[i] = 0;
// Set waveforms to square waves
myMusicWaveforms[0] = myMusicWaveforms[1] = myMusicWaveforms[2] = 0xAAAAAAAA;
// Initialize the DPC's random number generator register (must be non-zero)
myRandomNumber = 0x2B435044; // "DPC+"
// Remember startup bank
myStartBank = 5;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CartridgeDPCPlus::~CartridgeDPCPlus()
{
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void CartridgeDPCPlus::reset()
{
// Update cycles to the current system cycles
mySystemCycles = mySystem->cycles();
myFractionalClocks = 0.0;
// Upon reset we switch to the startup bank
bank(myStartBank);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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));
// Map all of the accesses to call peek and poke
System::PageAccess access;
access.directPeekBase = 0;
access.directPokeBase = 0;
access.device = this;
for(uInt32 i = 0x1000; i < 0x2000; 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) ? 0xa260012b: 0x00) ^ ((myRandomNumber >> 1) & 0x7FFFFFFF);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
inline void CartridgeDPCPlus::priorClockRandomNumberGenerator()
{
// Update random number generator (32-bit LFSR, reversed)
myRandomNumber = ((myRandomNumber & (1<<31)) ? 0x44c00257: 0x00) ^ (myRandomNumber << 1);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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 = 5; x <= 7; ++x)
{
myMusicCounters[x - 5] += myMusicFrequencies[x - 5];
if (myMusicFrequencies[x-5]>myMusicCounters[x-5])
myMusicWaveforms[x-5] = (myMusicWaveforms[x-5]>>1)|(myMusicWaveforms[x-5]<<31);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
inline void CartridgeDPCPlus::writeByte(uInt8 value)
{
switch (mySelectByte)
{
case 0x00:
{
myMusicWaveforms[0] = (myMusicWaveforms[0] & 0xFFFFFF00) | value;
break;
}
case 0x01:
{
myMusicWaveforms[0] = (myMusicWaveforms[0] & 0xFFFF00FF) | (value<<8);
break;
}
case 0x02:
{
myMusicWaveforms[0] = (myMusicWaveforms[0] & 0xFF00FFFF) | (value<<16);
break;
}
case 0x03:
{
myMusicWaveforms[0] = (myMusicWaveforms[0] & 0x00FFFFFF) | (value<<24);
break;
}
case 0x04:
{
myMusicWaveforms[1] = (myMusicWaveforms[1] & 0xFFFFFF00) | value;
break;
}
case 0x05:
{
myMusicWaveforms[1] = (myMusicWaveforms[1] & 0xFFFF00FF) | (value<<8);
break;
}
case 0x06:
{
myMusicWaveforms[1] = (myMusicWaveforms[1] & 0xFF00FFFF) | (value<<16);
break;
}
case 0x07:
{
myMusicWaveforms[1] = (myMusicWaveforms[1] & 0x00FFFFFF) | (value<<24);
break;
}
case 0x08:
{
myMusicWaveforms[2] = (myMusicWaveforms[2] & 0xFFFFFF00) | value;
break;
}
case 0x09:
{
myMusicWaveforms[2] = (myMusicWaveforms[2] & 0xFFFF00FF) | (value<<8);
break;
}
case 0x0A:
{
myMusicWaveforms[2] = (myMusicWaveforms[2] & 0xFF00FFFF) | (value<<16);
break;
}
case 0x0B:
{
myMusicWaveforms[2] = (myMusicWaveforms[2] & 0x00FFFFFF) | (value<<24);
break;
}
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt8 CartridgeDPCPlus::peek(uInt16 address)
{
address &= 0x0FFF;
uInt8 peekvalue = myProgramImage[(myCurrentBank << 12) + address];
// 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 register for selected data fetcher
if((myCounters[index] & 0x00ff) == ((myTops[index]+1) & 0xff))
{
myFlags[index] = 0xff;
}
else if((myCounters[index] & 0x00ff) == myBottoms[index])
{
myFlags[index] = 0x00;
}
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();
uInt32 i = 0;
i = myMusicVolume + ((myMusicVolume >> 12) * (uInt8) (myMusicWaveforms[0] >> 31)) +
((myMusicVolume >> 16) * (uInt8) (myMusicWaveforms[1] >> 31)) +
((myMusicVolume >> 24) * (uInt8) (myMusicWaveforms[2] >> 31));
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]] & myFlags[index];
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 = myFlags[index];
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;
myFlags[index] = 0x00;
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: // SELECTBYTE - set byte to update in writeByte()
mySelectByte = value;
break;
case 0x02: // WRITEBYTE
writeByte(value);
break;
case 0x03: // reserved
case 0x04: // reserved
break;
case 0x05: // VOLUME0
myMusicVolume = (myMusicVolume & 0xFFFF00FF) | ((value & 0x0F)<<12);
break;
case 0x06: // VOLUME1
myMusicVolume = (myMusicVolume & 0xFF00FFFF) | ((value & 0x0F)<<16);
break;
case 0x07: // VOLUME2
myMusicVolume = (myMusicVolume & 0x00FFFFFF) | ((value & 0x0F)<<24);
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;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void CartridgeDPCPlus::bank(uInt16 bank)
{
if(bankLocked()) return;
// Remember what bank we're in
myCurrentBank = bank;
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 = 4096 * 6 + 4096 + 255;
return myImageCopy;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartridgeDPCPlus::save(Serializer& out) const
{
const string& cart = name();
try
{
uInt32 i;
out.putString(cart);
// Indicates which bank is currently active
out.putInt(myCurrentBank);
// The top registers for the data fetchers
out.putInt(8);
for(i = 0; i < 8; ++i)
out.putByte((char)myTops[i]);
// The bottom registers for the data fetchers
out.putInt(8);
for(i = 0; i < 8; ++i)
out.putByte((char)myBottoms[i]);
// The counter registers for the data fetchers
out.putInt(8);
for(i = 0; i < 8; ++i)
out.putInt(myCounters[i]);
// The counter registers for the fractional data fetchers
out.putInt(8);
for(i = 0; i < 8; ++i)
out.putInt(myFractionalCounters[i]);
// The fractional registers for the data fetchers
out.putInt(8);
for(i = 0; i < 8; ++i)
out.putByte((char)myFractionalIncrements[i]);
// The flag registers for the data fetchers
out.putInt(8);
for(i = 0; i < 8; ++i)
out.putByte((char)myFlags[i]);
// The Fast Fetcher Enabled flag
out.putBool(myFastFetch);
out.putBool(myLDAimmediate);
// Control Byte to update
out.putByte((char) mySelectByte);
// The music volume
out.putInt(myMusicVolume);
// The music counters
out.putInt(3);
for(i = 0; i < 3; ++i)
out.putInt(myMusicCounters[i]);
// The music frequencies
out.putInt(3);
for(i = 0; i < 3; ++i)
out.putInt(myMusicFrequencies[i]);
// The music waveforms
out.putInt(3);
for(i = 0; i < 3; ++i)
out.putInt(myMusicWaveforms[i]);
// The random number generator register
out.putInt(myRandomNumber);
out.putInt(mySystemCycles);
out.putInt((uInt32)(myFractionalClocks * 100000000.0));
}
catch(const char* msg)
{
cerr << "ERROR: CartridgeDPCPlus::save" << endl << " " << msg << endl;
return false;
}
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartridgeDPCPlus::load(Serializer& in)
{
const string& cart = name();
try
{
if(in.getString() != cart)
return false;
uInt32 i, limit;
// Indicates which bank is currently active
myCurrentBank = (uInt16) in.getInt();
// The top registers for the data fetchers
limit = (uInt32) in.getInt();
for(i = 0; i < limit; ++i)
myTops[i] = (uInt8) in.getByte();
// The bottom registers for the data fetchers
limit = (uInt32) in.getInt();
for(i = 0; i < limit; ++i)
myBottoms[i] = (uInt8) in.getByte();
// The counter registers for the data fetchers
limit = (uInt32) in.getInt();
for(i = 0; i < limit; ++i)
myCounters[i] = (uInt16) in.getInt();
// The counter registers for the fractional data fetchers
limit = (uInt32) in.getInt();
for(i = 0; i < limit; ++i)
myFractionalCounters[i] = (uInt32) in.getInt();
// The fractional registers for the data fetchers
limit = (uInt32) in.getInt();
for(i = 0; i < limit; ++i)
myFractionalIncrements[i] = (uInt8) in.getByte();
// The flag registers for the data fetchers
limit = (uInt32) in.getInt();
for(i = 0; i < limit; ++i)
myFlags[i] = (uInt8) in.getByte();
// The Fast Fetcher Enabled flag
myFastFetch = in.getBool();
myLDAimmediate = in.getBool();
// Control Byte to update
mySelectByte = (uInt8) in.getByte();
// The music volume
myMusicVolume = (uInt32) in.getInt();
// The music mode counters for the data fetchers
limit = (uInt32) in.getInt();
for(i = 0; i < limit; ++i)
myMusicCounters[i] = (uInt32) in.getInt();
// The music mode frequency addends for the data fetchers
limit = (uInt32) in.getInt();
for(i = 0; i < limit; ++i)
myMusicFrequencies[i] = (uInt32) in.getInt();
// The music waveforms
limit = (uInt32) in.getInt();
for(i = 0; i < limit; ++i)
myMusicWaveforms[i] = (uInt32) in.getInt();
// The random number generator register
myRandomNumber = (uInt32) in.getInt();
// Get system cycles and fractional clocks
mySystemCycles = in.getInt();
myFractionalClocks = (double)in.getInt() / 100000000.0;
}
catch(const char* msg)
{
cerr << "ERROR: CartridgeDPCPlus::load" << endl << " " << msg << endl;
return false;
}
// Now, go to the current bank
bank(myCurrentBank);
return true;
}