stella/src/emucore/CartDASH.cxx

351 lines
12 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-2014 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>
#include "System.hxx"
#include "TIA.hxx"
#include "CartDASH.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CartridgeDASH::CartridgeDASH(const uInt8* image, uInt32 size, const Settings& settings) :
Cartridge(settings), mySize(size) {
// Allocate array for the ROM image
myImage = new uInt8[mySize];
// Copy the ROM image into my buffer
memcpy(myImage, image, mySize);
createCodeAccessBase(mySize + RAM_TOTAL_SIZE);
// Remember startup bank (0 per spec, rather than last per 3E scheme).
// Set this to go to 3rd 1K Bank.
myStartBank = 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CartridgeDASH::~CartridgeDASH() {
delete[] myImage;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void CartridgeDASH::reset() {
// Initialize RAM
if (mySettings.getBool("ramrandom"))
for (uInt32 i = 0; i < RAM_TOTAL_SIZE; ++i)
myRAM[i] = mySystem->randGenerator().next();
else
memset(myRAM, 0, RAM_TOTAL_SIZE);
// Initialise bank values for all ROM/RAM access
// This is used to reverse-lookup from address to bank location
for (uInt32 b = 0; b < 8; b++)
bankInUse[b] = BANK_UNDEFINED; // bank is undefined and inaccessible!
initializeBankState();
// We'll map the startup banks 0 and 3 from the image into the third 1K bank upon reset
bankROM((0 << BANK_BITS) | 0);
bankROM((3 << BANK_BITS) | 0);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void CartridgeDASH::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((0x1800 & mask) == 0);
System::PageAccess access(this, System::PA_READWRITE);
// Set the page accessing methods for the hot spots (for 100% emulation
// we need to chain any accesses below 0x40 to the TIA. Our poke() method
// does this via mySystem->tiaPoke(...), at least until we come up with a
// cleaner way to do it).
for (uInt32 i = 0x00; i < 0x40; i += (1 << shift))
mySystem->setPageAccess(i >> shift, access);
// Initialise bank values for all ROM/RAM access
// This is used to reverse-lookup from address to bank location
for (uInt32 b = 0; b < 8; b++)
bankInUse[b] = BANK_UNDEFINED; // bank is undefined and inaccessible!
initializeBankState();
// Setup the last segment (of 4, each 1K) to point to the first ROM slice
// Actually we DO NOT want "always". It's just on bootup, and can be out switched later
bankROM((0 << BANK_BITS) | 0);
bankROM((3 << BANK_BITS) | 0);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt8 CartridgeDASH::peek(uInt16 address) {
uInt16 peekAddress = address;
address &= 0x0FFF; // restrict to 4K address range
uInt8 value = 0;
uInt32 bank = (address >> (ROM_BANK_TO_POWER - 1)) & 7; // convert to 512 byte bank index (0-7)
Int16 imageBank = bankInUse[bank]; // the ROM/RAM bank that's here
if (imageBank == BANK_UNDEFINED) { // an uninitialised bank?
// accessing invalid bank, so return should be... random?
value = mySystem->randGenerator().next();
} else if (imageBank & BITMASK_ROMRAM) { // a RAM bank
// Reading from the write port triggers an unwanted write
value = mySystem->getDataBusState(0xFF);
if(bankLocked())
return value;
else
{
triggerReadFromWritePort(peekAddress);
Int32 ramBank = imageBank & BIT_BANK_MASK; // discard irrelevant bits
Int32 offset = ramBank << RAM_BANK_TO_POWER; // base bank address in RAM
offset += (address & BITMASK_RAM_BANK); // + byte offset in RAM bank
return myRAM[offset] = value;
}
}
return value;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartridgeDASH::poke(uInt16 address, uInt8 value) {
bool myBankChanged = false;
// Check for write to the bank switch address. RAM/ROM and bank # are encoded in 'value'
// There are NO mirrored hotspots.
if (address == BANK_SWITCH_HOTSPOT_RAM)
myBankChanged = bankRAM(value);
else if (address == BANK_SWITCH_HOTSPOT_ROM)
myBankChanged = bankROM(value);
// Pass the poke through to the TIA. In a real Atari, both the cart and the
// TIA see the address lines, and both react accordingly. In Stella, each
// 64-byte chunk of address space is "owned" by only one device. If we
// don't chain the poke to the TIA, then the TIA can't see it...
mySystem->tia().poke(address, value);
return myBankChanged;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartridgeDASH::bankRAM(uInt8 bank) {
if (bankLocked()) // debugger can lock RAM
return false;
// Each RAM bank uses two slots, separated by 0x800 in memory -- one read, one write.
bankRAMSlot(bank | BITMASK_ROMRAM | 0);
bankRAMSlot(bank | BITMASK_ROMRAM | BITMASK_LOWERUPPER);
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void CartridgeDASH::bankRAMSlot(uInt16 bank) {
uInt16 shift = mySystem->pageShift();
uInt16 bankNumber = (bank >> BANK_BITS) & 3; // which bank # we are switching TO (BITS D6,D7) to 512 byte block
uInt16 currentBank = bank & BIT_BANK_MASK; // Wrap around/restrict to valid range
bool upper = bank & BITMASK_LOWERUPPER; // is this the read or write port
uInt32 startCurrentBank = currentBank << RAM_BANK_TO_POWER; // Effectively * 512 bytes
uInt32 blockSize = 1 << shift;
// Setup the page access methods for the current bank
System::PageAccess access(this, System::PA_READ);
if (upper) { // We're mapping the write port
bankInUse[bankNumber + 4] = (Int16) bank;
access.type = System::PA_WRITE;
} else { // We're mapping the read port
bankInUse[bankNumber] = (Int16) bank;
access.type = System::PA_READ;
}
uInt32 start = 0x1000 + (bankNumber << RAM_BANK_TO_POWER) + (upper ? RAM_WRITE_OFFSET : 0);
uInt32 end = start + RAM_BANK_SIZE - 1;
for (uInt32 address = start; address <= end; address += blockSize) {
if(upper)
access.directPokeBase = &myRAM[startCurrentBank + (address & (RAM_BANK_SIZE - 1))];
else
access.directPeekBase = &myRAM[startCurrentBank + (address & (RAM_BANK_SIZE - 1))];
access.codeAccessBase = &myCodeAccessBase[mySize + startCurrentBank + (address & (RAM_BANK_SIZE - 1))];
mySystem->setPageAccess(address >> shift, access);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartridgeDASH::bankROM(uInt8 bank) {
if (bankLocked()) // debugger can lock ROM
return false;
// Map ROM bank image into the system into the correct slot
// Memory map is 1K slots at 0x1000, 0x1400, 0x1800, 0x1C00
// Each ROM uses 2 consecutive 512 byte slots
bankROMSlot(bank | 0);
bankROMSlot(bank | BITMASK_LOWERUPPER);
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void CartridgeDASH::bankROMSlot(uInt16 bank) {
uInt16 shift = mySystem->pageShift();
uInt16 bankNumber = (bank >> BANK_BITS) & 3; // which bank # we are switching TO (BITS D6,D7)
uInt16 currentBank = bank & BIT_BANK_MASK; // Wrap around/restrict to valid range
bool upper = bank & BITMASK_LOWERUPPER; // is this the lower or upper 512b
bankInUse[bankNumber * 2 + (upper ? 1 : 0)] = (Int16) bank; // Record which bank switched in (as ROM)
uInt32 startCurrentBank = currentBank << ROM_BANK_TO_POWER; // Effectively *1K
// Setup the page access methods for the current bank
System::PageAccess access(this, System::PA_READ);
uInt32 start = 0x1000 + (bankNumber << ROM_BANK_TO_POWER) + (upper ? ROM_BANK_SIZE / 2 : 0);
uInt32 end = start + ROM_BANK_SIZE / 2 - 1;
for (uInt32 address = start; address <= end; address += (1 << shift)) {
access.directPeekBase = &myImage[startCurrentBank + (address & (ROM_BANK_SIZE - 1))];
access.codeAccessBase = &myCodeAccessBase[startCurrentBank + (address & (ROM_BANK_SIZE - 1))];
mySystem->setPageAccess(address >> shift, access);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void CartridgeDASH::initializeBankState() {
const uInt16& shift = mySystem->pageShift();
// Switch in each 512b slot
for (uInt32 b = 0; b < 8; b++) {
if (bankInUse[b] == BANK_UNDEFINED) {
// Setup the page access methods for the current bank
System::PageAccess access(this, System::PA_READ);
uInt32 start = 0x1000 + (b << RAM_BANK_TO_POWER);
uInt32 end = start + RAM_BANK_SIZE - 1;
for (uInt32 address = start; address <= end; address += (1 << shift))
mySystem->setPageAccess(address >> shift, access);
}
else if (bankInUse[b] & BITMASK_ROMRAM)
bankRAMSlot(bankInUse[b]);
else
bankROMSlot(bankInUse[b]);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartridgeDASH::patch(uInt16 address, uInt8 value) {
#if 0
// Patch the cartridge ROM (for debugger)
myBankChanged = true;
uInt32 bankNumber = (address >> RAM_BANK_TO_POWER) & 7; // now 512 byte bank # (ie: 0-7)
Int16 whichBankIsThere = bankInUse[bankNumber]; // ROM or RAM bank reference
if (whichBankIsThere == BANK_UNDEFINED) {
// We're trying to access undefined memory (no bank here yet). Fail!
myBankChanged = false;
} else if (whichBankIsThere & BITMASK_ROMRAM) { // patching RAM (512 byte banks)
uInt32 byteOffset = address & BITMASK_RAM_BANK;
uInt32 baseAddress = ((whichBankIsThere & BIT_BANK_MASK) << RAM_BANK_TO_POWER) + byteOffset;
myRAM[baseAddress] = value; // write to RAM
// TODO: Stephen -- should we set 'myBankChanged' true when there's a RAM write?
} else { // patching ROM (1K banks)
uInt32 byteOffset = address & BITMASK_ROM_BANK;
uInt32 baseAddress = (whichBankIsThere << ROM_BANK_TO_POWER) + byteOffset;
myImage[baseAddress] = value; // write to the image
}
return myBankChanged;
#else
return false;
#endif
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const uInt8* CartridgeDASH::getImage(int& size) const {
size = mySize;
return myImage;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartridgeDASH::save(Serializer& out) const {
try {
out.putString(name());
out.putShortArray((uInt16*)bankInUse, 8);
out.putByteArray(myRAM, RAM_TOTAL_SIZE);
} catch (...) {
cerr << "ERROR: CartridgeDASH::save" << endl;
return false;
}
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartridgeDASH::load(Serializer& in) {
try {
if (in.getString() != name())
return false;
in.getShortArray((uInt16*)bankInUse, 8);
in.getByteArray(myRAM, RAM_TOTAL_SIZE);
} catch (...) {
cerr << "ERROR: CartridgeDASH::load" << endl;
return false;
}
initializeBankState();
return true;
}