autoformatted. still stabilising to stella standard.

Tabs removed, replaced with 2-char spacing. 
constants for bit masking added
corrected the patch code for the bit-allocations for RAM/ROM banks switch


git-svn-id: svn://svn.code.sf.net/p/stella/code/trunk@2900 8b62c5a3-ac7e-4cc8-8f21-d9a121418aba
This commit is contained in:
adavie 2014-06-03 13:53:49 +00:00
parent 6d6dd7632f
commit 40c8c81245
2 changed files with 259 additions and 276 deletions

View File

@ -24,11 +24,9 @@
#include "TIA.hxx" #include "TIA.hxx"
#include "CartDASH.hxx" #include "CartDASH.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CartridgeDASH::CartridgeDASH(const uInt8* image, uInt32 size, const Settings& settings) CartridgeDASH::CartridgeDASH(const uInt8* image, uInt32 size, const Settings& settings) :
: Cartridge(settings), Cartridge(settings), mySize(size) {
mySize(size)
{
// Allocate array for the ROM image // Allocate array for the ROM image
myImage = new uInt8[mySize]; myImage = new uInt8[mySize];
@ -46,20 +44,20 @@ CartridgeDASH::CartridgeDASH(const uInt8* image, uInt32 size, const Settings& se
registerRamArea(0x1400, RAM_BANK_SIZE, 0x00, RAM_WRITE_OFFSET); // 512 bytes RAM @ 0x1400 registerRamArea(0x1400, RAM_BANK_SIZE, 0x00, RAM_WRITE_OFFSET); // 512 bytes RAM @ 0x1400
registerRamArea(0x1600, RAM_BANK_SIZE, 0x00, RAM_WRITE_OFFSET); // 512 bytes RAM @ 0x1600 registerRamArea(0x1600, RAM_BANK_SIZE, 0x00, RAM_WRITE_OFFSET); // 512 bytes RAM @ 0x1600
myCurrentBank = -1; // nothing switched
// Remember startup bank (0 per spec, rather than last per 3E scheme). // Remember startup bank (0 per spec, rather than last per 3E scheme).
// Set this to go to 3rd 1K Bank. // Set this to go to 3rd 1K Bank.
myStartBank = (3 << BANK_BITS) | 0; myStartBank = (3 << BANK_BITS) | 0;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CartridgeDASH::~CartridgeDASH() CartridgeDASH::~CartridgeDASH() {
{
delete[] myImage; delete[] myImage;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void CartridgeDASH::reset() void CartridgeDASH::reset() {
{
// Initialize RAM // Initialize RAM
if (mySettings.getBool("ramrandom")) if (mySettings.getBool("ramrandom"))
for (uInt32 i = 0; i < RAM_TOTAL_SIZE; ++i) for (uInt32 i = 0; i < RAM_TOTAL_SIZE; ++i)
@ -73,8 +71,7 @@ void CartridgeDASH::reset()
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void CartridgeDASH::install(System& system) void CartridgeDASH::install(System& system) {
{
mySystem = &system; mySystem = &system;
uInt16 shift = mySystem->pageShift(); uInt16 shift = mySystem->pageShift();
@ -95,12 +92,11 @@ void CartridgeDASH::install(System& system)
// Setup the last segment (of 4, each 1K) to point to the first ROM slice // 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 // Actually we DO NOT want "always". It's just on bootup, and can be out switched later
access.type = System::PA_READ; access.type = System::PA_READ;
for (uInt32 byte = 0; byte < ROM_BANK_SIZE; byte++) for (uInt32 byte = 0; byte < ROM_BANK_SIZE; byte++) {
{ uInt32 address = (0x1000 - ROM_BANK_SIZE) + (byte << shift); // which byte in last bank of 2600 address space
uInt32 address = (0x1000 - ROM_BANK_SIZE) + (byte<<shift); // which byte in last bank of 2600 address space
access.directPeekBase = &myImage[byte]; // from base address 0x0000 in image, so just use 'byte' access.directPeekBase = &myImage[byte]; // from base address 0x0000 in image, so just use 'byte'
access.codeAccessBase = &myCodeAccessBase[byte]; access.codeAccessBase = &myCodeAccessBase[byte];
mySystem->setPageAccess(address>>shift, access); mySystem->setPageAccess(address >> shift, access);
// TODO: Stephen: in this and other implementations we appear to be using "shift" as a system-dependant mangle for // TODO: Stephen: in this and other implementations we appear to be using "shift" as a system-dependant mangle for
// different access types (byte/int/32bit) on different architectures. I think I understand that much. However, // different access types (byte/int/32bit) on different architectures. I think I understand that much. However,
@ -112,7 +108,7 @@ void CartridgeDASH::install(System& system)
// Initialise bank values for the 4x 1K bank areas // Initialise bank values for the 4x 1K bank areas
// This is used to reverse-lookup from address to bank location // This is used to reverse-lookup from address to bank location
for(uInt32 b = 0; b < 3; b++) for (uInt32 b = 0; b < 3; b++)
bankInUse[b] = BANK_UNDEFINED; // bank is undefined and inaccessible! bankInUse[b] = BANK_UNDEFINED; // bank is undefined and inaccessible!
// Install pages for the startup bank into the first segment // Install pages for the startup bank into the first segment
@ -120,31 +116,29 @@ void CartridgeDASH::install(System& system)
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt8 CartridgeDASH::peek(uInt16 address) uInt8 CartridgeDASH::peek(uInt16 address) {
{
uInt8 value = 0; uInt8 value = 0;
uInt32 bank = (address >> ROM_BANK_TO_POWER) & 3; // convert to 1K bank index (0-3) uInt32 bank = (address >> ROM_BANK_TO_POWER) & 3; // convert to 1K bank index (0-3)
Int16 imageBank = bankInUse[bank]; // the ROM/RAM bank that's here Int16 imageBank = bankInUse[bank]; // the ROM/RAM bank that's here
if(imageBank == BANK_UNDEFINED) // an uninitialised bank? if (imageBank == BANK_UNDEFINED) { // an uninitialised bank?
{
// accessing invalid bank, so return should be... random? // accessing invalid bank, so return should be... random?
// TODO: Stephen -- throw some sort of error; looking at undefined data // TODO: Stephen -- throw some sort of error; looking at undefined data
assert(false); assert(false);
value = mySystem->randGenerator().next(); value = mySystem->randGenerator().next();
}
else if (imageBank & ROMRAM) // a RAM bank } else if (imageBank & BITMASK_ROMRAM) { // a RAM bank
{
Int32 ramBank = imageBank & BIT_BANK_MASK; // discard irrelevant bits Int32 ramBank = imageBank & BIT_BANK_MASK; // discard irrelevant bits
Int32 offset = ramBank << RAM_BANK_TO_POWER; // base bank address in RAM Int32 offset = ramBank << RAM_BANK_TO_POWER; // base bank address in RAM
offset += (address & (RAM_BANK_SIZE-1)); // + byte offset in RAM bank offset += (address & BITMASK_RAM_BANK); // + byte offset in RAM bank
value = myRAM[offset]; value = myRAM[offset];
}
else // accessing ROM } else { // accessing ROM
{
Int32 offset = imageBank << ROM_BANK_TO_POWER; // base bank address in image Int32 offset = imageBank << ROM_BANK_TO_POWER; // base bank address in image
offset += (address & (ROM_BANK_SIZE-1)); // + byte offset in image bank offset += (address & BITMASK_ROM_BANK); // + byte offset in image bank
value = myImage[offset]; value = myImage[offset];
} }
@ -152,15 +146,14 @@ uInt8 CartridgeDASH::peek(uInt16 address)
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartridgeDASH::poke(uInt16 address, uInt8 value) bool CartridgeDASH::poke(uInt16 address, uInt8 value) {
{
address &= 0x0FFF; // restrict to 4K address range address &= 0x0FFF; // restrict to 4K address range
// Check for write to the bank switch address. RAM/ROM and bank # are encoded in 'value' // Check for write to the bank switch address. RAM/ROM and bank # are encoded in 'value'
// There are NO mirrored hotspots. // There are NO mirrored hotspots.
if ( address == BANK_SWITCH_HOTSPOT) if (address == BANK_SWITCH_HOTSPOT)
bank(value); bank(value);
// Pass the poke through to the TIA. In a real Atari, both the cart and the // 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 // TIA see the address lines, and both react accordingly. In Stella, each
@ -172,9 +165,8 @@ bool CartridgeDASH::poke(uInt16 address, uInt8 value)
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartridgeDASH::bank(uInt16 bank) bool CartridgeDASH::bank(uInt16 bank) {
{ if (bankLocked())
if(bankLocked())
return false; // TODO: Stephen -- ? no idea return false; // TODO: Stephen -- ? no idea
uInt16 shift = mySystem->pageShift(); uInt16 shift = mySystem->pageShift();
@ -182,12 +174,12 @@ bool CartridgeDASH::bank(uInt16 bank)
uInt16 bankNumber = (bank >> BANK_BITS) & 3; // which bank # we are switching TO (BITS D6,D7) uInt16 bankNumber = (bank >> BANK_BITS) & 3; // which bank # we are switching TO (BITS D6,D7)
uInt16 bankID = bank & BIT_BANK_MASK; // The actual bank # to switch in (BITS D5-D0) uInt16 bankID = bank & BIT_BANK_MASK; // The actual bank # to switch in (BITS D5-D0)
if(bank & ROMRAM) // switching to a 512 byte RAM bank if (bank & BITMASK_ROMRAM) { // switching to a 512 byte RAM bank
{
// Wrap around/restrict to valid range // Wrap around/restrict to valid range
uInt16 currentBank = (bank & BIT_BANK_MASK) % RAM_BANK_COUNT; uInt16 currentBank = bank & BIT_BANK_MASK;
// Record which bank switched in (marked as RAM) // Record which bank switched in (marked as RAM)
myCurrentBank = bankInUse[bankNumber] = (Int16) (ROM_BANK_COUNT + currentBank); myCurrentBank = bankInUse[bankNumber] = (Int16) (BITMASK_ROMRAM | currentBank);
// Effectively * 512 bytes // Effectively * 512 bytes
uInt32 startCurrentBank = currentBank << RAM_BANK_TO_POWER; uInt32 startCurrentBank = currentBank << RAM_BANK_TO_POWER;
@ -195,8 +187,7 @@ bool CartridgeDASH::bank(uInt16 bank)
System::PageAccess access(0, 0, 0, this, System::PA_READ); System::PageAccess access(0, 0, 0, this, System::PA_READ);
// Map read-port RAM image into the system // Map read-port RAM image into the system
for(uInt32 byte = 0; byte < RAM_BANK_SIZE; byte += (1 << shift)) for (uInt32 byte = 0; byte < RAM_BANK_SIZE; byte += (1 << shift)) {
{
access.directPeekBase = &myRAM[startCurrentBank + byte]; access.directPeekBase = &myRAM[startCurrentBank + byte];
// TODO: Stephen please explain/review the use of mySize as an offset for RAM access here.... // TODO: Stephen please explain/review the use of mySize as an offset for RAM access here....
@ -209,14 +200,12 @@ bool CartridgeDASH::bank(uInt16 bank)
access.type = System::PA_WRITE; access.type = System::PA_WRITE;
// Map write-port RAM image into the system // Map write-port RAM image into the system
for (uInt32 byte = 0; byte < RAM_BANK_SIZE; byte += (1 << shift)) for (uInt32 byte = 0; byte < RAM_BANK_SIZE; byte += (1 << shift)) {
{
access.directPokeBase = &myRAM[startCurrentBank + RAM_WRITE_OFFSET + byte]; access.directPokeBase = &myRAM[startCurrentBank + RAM_WRITE_OFFSET + byte];
access.codeAccessBase = &myCodeAccessBase[mySize + startCurrentBank + RAM_WRITE_OFFSET + byte]; access.codeAccessBase = &myCodeAccessBase[mySize + startCurrentBank + RAM_WRITE_OFFSET + byte];
mySystem->setPageAccess((startCurrentBank + byte) >> shift, access); mySystem->setPageAccess((startCurrentBank + byte) >> shift, access);
} }
} } else // ROM 1K banks
else // ROM 1K banks
{ {
// Map ROM bank image into the system into the correct slot // Map ROM bank image into the system into the correct slot
// Memory map is 1K slots at 0x1000, 0x1400, 0x1800, 0x1C00 // Memory map is 1K slots at 0x1000, 0x1400, 0x1800, 0x1C00
@ -231,8 +220,7 @@ bool CartridgeDASH::bank(uInt16 bank)
uInt32 bankStart = 0x1000 + (bankNumber << ROM_BANK_TO_POWER); // *1K uInt32 bankStart = 0x1000 + (bankNumber << ROM_BANK_TO_POWER); // *1K
for (uInt32 byte = 0; byte < ROM_BANK_SIZE; byte += (1 << shift)) for (uInt32 byte = 0; byte < ROM_BANK_SIZE; byte += (1 << shift)) {
{
access.directPeekBase = &myImage[startCurrentBank + byte]; access.directPeekBase = &myImage[startCurrentBank + byte];
access.codeAccessBase = &myCodeAccessBase[startCurrentBank + byte]; access.codeAccessBase = &myCodeAccessBase[startCurrentBank + byte];
mySystem->setPageAccess((bankStart + byte) >> shift, access); mySystem->setPageAccess((bankStart + byte) >> shift, access);
@ -243,8 +231,7 @@ bool CartridgeDASH::bank(uInt16 bank)
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt16 CartridgeDASH::bank() const uInt16 CartridgeDASH::bank() const {
{
// TODO: Stephen -- what to do here? We don't really HAVE a "current bank"; we have 4 banks // TODO: Stephen -- what to do here? We don't really HAVE a "current bank"; we have 4 banks
// and they are defined in bankInUse[...]. // and they are defined in bankInUse[...].
// What I've done is kept track of the last switched bank, and return that. But that doesn't tell us WHERE. :( // What I've done is kept track of the last switched bank, and return that. But that doesn't tell us WHERE. :(
@ -253,27 +240,26 @@ uInt16 CartridgeDASH::bank() const
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt16 CartridgeDASH::bankCount() const uInt16 CartridgeDASH::bankCount() const {
{
// Because the RAM banks always start above the ROM banks (see ROM_BANK_COUNT) for value, // We have a constant # banks for this scheme; 32 ROM and 32 RAM (or, at least, RAM_BANK_COUNT and ROM_BANK_COUNT)
// we require the number of ROM banks to be == ROM_BANK_COUNT. Banks are therefore 0-63 ROM 64-127 RAM // See usage of bank bits.
// TODO: Stephen -- ROM banks are 1K. RAM banks are 512 bytes. How does this affect what this routine should return? // TODO: Stephen -- What should this return, given the mangled bank value?
return ROM_BANK_COUNT + RAM_BANK_COUNT; return ROM_BANK_COUNT + RAM_BANK_COUNT;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartridgeDASH::patch(uInt16 address, uInt8 value) bool CartridgeDASH::patch(uInt16 address, uInt8 value) {
{
// Patch the cartridge ROM // Patch the cartridge ROM
// TODO: Stephen... I assume this is for some sort of debugger support....? // TODO: Stephen... I assume this is for some sort of debugger support....?
myBankChanged = true; myBankChanged = true;
uInt32 bankNumber = (address >> 10) & 3; // now 1K bank # (ie: 0-3) uInt32 bankNumber = (address >> ROM_BANK_TO_POWER) & 3; // now 1K bank # (ie: 0-3)
Int32 whichBankIsThere = bankInUse[bankNumber]; // ROM or RAM bank reference Int32 whichBankIsThere = bankInUse[bankNumber]; // ROM or RAM bank reference
if(whichBankIsThere <= BANK_UNDEFINED) if (whichBankIsThere == BANK_UNDEFINED) {
{
// We're trying to access undefined memory (no bank here yet) // We're trying to access undefined memory (no bank here yet)
// TODO: Stephen -- what to do here? Fail silently? // TODO: Stephen -- what to do here? Fail silently?
@ -281,64 +267,58 @@ bool CartridgeDASH::patch(uInt16 address, uInt8 value)
assert(false); assert(false);
myBankChanged = false; myBankChanged = false;
}
else if(whichBankIsThere < ROM_BANK_COUNT) // patching ROM (1K banks) } else if (whichBankIsThere & BITMASK_ROMRAM) { // patching RAM (512 byte banks)
{
uInt32 byteOffset = address & (ROM_BANK_SIZE-1); uInt32 byteOffset = address & BITMASK_RAM_BANK;
uInt32 baseAddress = ((whichBankIsThere & BIT_BANK_MASK) << RAM_BANK_TO_POWER) + byteOffset;
myRAM[baseAddress] = value; // write to RAM
} else { // patching ROM (1K banks)
uInt32 byteOffset = address & BITMASK_ROM_BANK;
uInt32 baseAddress = (whichBankIsThere << ROM_BANK_TO_POWER) + byteOffset; uInt32 baseAddress = (whichBankIsThere << ROM_BANK_TO_POWER) + byteOffset;
myImage[baseAddress] = value; // write to the image myImage[baseAddress] = value; // write to the image
} }
else // patching RAM (512 byte banks)
{
uInt32 byteOffset = address & (RAM_BANK_SIZE-1);
uInt32 baseAddress = ((whichBankIsThere - ROM_BANK_COUNT) << RAM_BANK_TO_POWER) + byteOffset;
myRAM[baseAddress] = value; // write to RAM
}
return myBankChanged; return myBankChanged;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const uInt8* CartridgeDASH::getImage(int& size) const const uInt8* CartridgeDASH::getImage(int& size) const {
{
size = mySize; size = mySize;
return myImage; return myImage;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartridgeDASH::save(Serializer& out) const bool CartridgeDASH::save(Serializer& out) const {
{ try {
try
{
out.putString(name()); out.putString(name());
out.putShort(myCurrentBank); out.putShort(myCurrentBank);
for(uInt32 bank = 0; bank < 4; bank++) for (uInt32 bank = 0; bank < 4; bank++)
out.putShort(bankInUse[bank]); out.putShort(bankInUse[bank]);
out.putByteArray(myRAM, RAM_TOTAL_SIZE); out.putByteArray(myRAM, RAM_TOTAL_SIZE);
} } catch (...) {
catch (...)
{
cerr << "ERROR: CartridgeDASH::save" << endl; cerr << "ERROR: CartridgeDASH::save" << endl;
return false; {
}
return true; return false;
}
return true;
}
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartridgeDASH::load(Serializer& in) bool CartridgeDASH::load(Serializer& in) {
{ try {
try if (in.getString() != name())
{
if(in.getString() != name())
return false; return false;
myCurrentBank = in.getShort(); myCurrentBank = in.getShort();
for(uInt32 bank = 0; bank < 4; bank++) for (uInt32 bank = 0; bank < 4; bank++)
bankInUse[bank] = in.getShort(); bankInUse[bank] = in.getShort();
in.getByteArray(myRAM, RAM_TOTAL_SIZE); in.getByteArray(myRAM, RAM_TOTAL_SIZE);
} } catch (...) {
catch (...)
{
cerr << "ERROR: CartridgeDASH::load" << endl; cerr << "ERROR: CartridgeDASH::load" << endl;
return false; return false;
} }

View File

@ -26,252 +26,255 @@ class System;
#include "Cart.hxx" #include "Cart.hxx"
#ifdef DEBUGGER_SUPPORT #ifdef DEBUGGER_SUPPORT
class CartridgeDASHWidget; class CartridgeDASHWidget;
// #include "CartDASHWidget.hxx" // #include "CartDASHWidget.hxx"
#endif #endif
/** /**
Cartridge class for new tiling engine "Boulder Dash" format games with RAM. Cartridge class for new tiling engine "Boulder Dash" format games with RAM.
Kind of a combination of 3F and 3E, with better switchability. Kind of a combination of 3F and 3E, with better switchability.
B.Watson's Cart3E was used as a template for building this implementation. B.Watson's Cart3E was used as a template for building this implementation.
Because a single bank number is used to define both the destination (0-3) Because a single bank number is used to define both the destination (0-3)
AND the type (ROM/RAM) there are only 5 bits left to indicate the actual bank AND the type (ROM/RAM) there are only 5 bits left to indicate the actual bank
number. This sets the limits of 32K ROM and 16K RAM. number. This sets the limits of 32K ROM and 16K RAM.
D7 RAM/ROM flag (1=RAM) D7 RAM/ROM flag (1=RAM)
D6D5 indicate the bank number (0-3) D6D5 indicate the bank number (0-3)
D4D3D2D1D0 indicate the actual # (0-31) from the image/ram D4D3D2D1D0 indicate the actual # (0-31) from the image/ram
Hotspot 0x3F is used for bank-switching, with the encoded bank # as above. Hotspot 0x3F is used for bank-switching, with the encoded bank # as above.
ROM: ROM:
In this scheme, the 4K address space is broken into four 1K ROM segments. In this scheme, the 4K address space is broken into four 1K ROM segments.
living at 0x1000, 0x1400, 0x1800, 0x1C00 (or, same thing, 0xF000... etc.), living at 0x1000, 0x1400, 0x1800, 0x1C00 (or, same thing, 0xF000... etc.),
and four 512 byte RAM segments, living at 0x1000, 0x1200, 0x1400, 0x1600 and four 512 byte RAM segments, living at 0x1000, 0x1200, 0x1400, 0x1600
with write-mirrors +0x800 of these. The last 1K ROM ($FC00-$FFFF) segment with write-mirrors +0x800 of these. The last 1K ROM ($FC00-$FFFF) segment
is initialised to point to the FIRST 1K of the ROM image, but it may be is initialised to point to the FIRST 1K of the ROM image, but it may be
switched out at any time. Note, this is DIFFERENT to 3E which switches in switched out at any time. Note, this is DIFFERENT to 3E which switches in
the UPPER bank and this bank is fixed. This allows variable sized ROM the UPPER bank and this bank is fixed. This allows variable sized ROM
without having to detect size. First bank (0) in ROM is the default fixed without having to detect size. First bank (0) in ROM is the default fixed
bank mapped to $FC00. bank mapped to $FC00.
The system requires the reset vectors to be valid on a reset, so either the The system requires the reset vectors to be valid on a reset, so either the
hardware first switches in the first bank, or the programmer must ensure hardware first switches in the first bank, or the programmer must ensure
that the reset vector is present in ALL ROM banks which might be switched that the reset vector is present in ALL ROM banks which might be switched
into the last bank area. Currently the latter (programmer onus) is required, into the last bank area. Currently the latter (programmer onus) is required,
but it would be nice for the cartridge hardware to auto-switch on reset. but it would be nice for the cartridge hardware to auto-switch on reset.
ROM switching (write of block+bank number to $3F) D7=0 and D6D5 upper 2 bits of bank # ROM switching (write of block+bank number to $3F) D7=0 and D6D5 upper 2 bits of bank #
indicates the destination segment (0-3, corresponding to $F000, $F400, $F800, $FC00), indicates the destination segment (0-3, corresponding to $F000, $F400, $F800, $FC00),
and lower 5 bits indicate the 1K bank to switch in. Can handle 32 x 1K ROM banks (32K total). and lower 5 bits indicate the 1K bank to switch in. Can handle 32 x 1K ROM banks (32K total).
D7 D6 D5 D4D3D2D1D0 D7 D6 D5 D4D3D2D1D0
0 0 0 x x x x x switch a 1K ROM bank xxxxx to $F000 0 0 0 x x x x x switch a 1K ROM bank xxxxx to $F000
0 0 1 switch a 1K ROM bank xxxxx to $F400 0 0 1 switch a 1K ROM bank xxxxx to $F400
0 1 0 switch a 1K ROM bank xxxxx to $F800 0 1 0 switch a 1K ROM bank xxxxx to $F800
0 1 1 switch a 1K ROM bank xxxxx to $FC00 0 1 1 switch a 1K ROM bank xxxxx to $FC00
RAM switching (write of segment+bank number to $3F) with D7=1 and D6D5 upper 2 bits of bank # RAM switching (write of segment+bank number to $3F) with D7=1 and D6D5 upper 2 bits of bank #
indicates the destination RAM segment (0-3, corresponding to $F000, $F200, $F400, $F600). indicates the destination RAM segment (0-3, corresponding to $F000, $F200, $F400, $F600).
Note that this allows contiguous 2K of RAM to be configured by setting 4 consecutive RAM segments Note that this allows contiguous 2K of RAM to be configured by setting 4 consecutive RAM segments
each 512 bytes with consecutive addresses. However, as the write address of RAM is +0x800, this each 512 bytes with consecutive addresses. However, as the write address of RAM is +0x800, this
invalidates ROM access as described below. invalidates ROM access as described below.
can handle 32 x 512 byte RAM banks (16K total) can handle 32 x 512 byte RAM banks (16K total)
D7 D6 D5 D4D3D2D1D0 D7 D6 D5 D4D3D2D1D0
1 0 0 x x x x x switch a 512 byte RAM bank xxxxx to $F000 with write @ $F800 1 0 0 x x x x x switch a 512 byte RAM bank xxxxx to $F000 with write @ $F800
0 1 switch a 512 byte RAM bank xxxxx to $F200 with write @ $FA00 0 1 switch a 512 byte RAM bank xxxxx to $F200 with write @ $FA00
1 0 switch a 512 byte RAM bank xxxxx to $F400 with write @ $FC00 1 0 switch a 512 byte RAM bank xxxxx to $F400 with write @ $FC00
1 1 switch a 512 byte RAM bank xxxxx to $F600 with write @ $FE00 1 1 switch a 512 byte RAM bank xxxxx to $F600 with write @ $FE00
It is possible to switch multiple RAM banks and ROM banks together It is possible to switch multiple RAM banks and ROM banks together
For example, For example,
F000-F1FF RAM bank A (512 byte READ) F000-F1FF RAM bank A (512 byte READ)
F200-F3FF high 512 bytes of ROM bank previously loaded at F000 F200-F3FF high 512 bytes of ROM bank previously loaded at F000
F400 ROM bank 0 (1K) F400 ROM bank 0 (1K)
F800 RAM bank A (512 byte WRITE) F800 RAM bank A (512 byte WRITE)
FA00-FBFF high 512 bytes of ROM bank previously loaded at F400 FA00-FBFF high 512 bytes of ROM bank previously loaded at F400
FC00 ROM bank 1 FC00 ROM bank 1
This example shows 512 bytes of RAM, and 2 1K ROM banks and two 512 byte ROM This example shows 512 bytes of RAM, and 2 1K ROM banks and two 512 byte ROM
bank halves. bank halves.
Switching RAM blocks (D7D6 of $3E) partially invalidates ROM blocks, as below... Switching RAM blocks (D7D6 of $3E) partially invalidates ROM blocks, as below...
RAM block Invalidates ROM block RAM block Invalidates ROM block
0 0 (lower half), 2 (lower half) 0 0 (lower half), 2 (lower half)
1 0 (upper half), 2 (upper half) 1 0 (upper half), 2 (upper half)
2 1 (lower half), 3 (upper half) 2 1 (lower half), 3 (upper half)
3 1 (upper half), 3 (lower half) 3 1 (upper half), 3 (lower half)
For example, RAM block 1 uses address $F200-$F3FF and $FA00-$FBFF For example, RAM block 1 uses address $F200-$F3FF and $FA00-$FBFF
ROM block 0 uses address $F000-$F3FF, and ROM block 2 uses address $F800-$FBFF ROM block 0 uses address $F000-$F3FF, and ROM block 2 uses address $F800-$FBFF
Switching in RAM block 1 makes F200-F3FF ROM inaccessible, however F000-F1FF is Switching in RAM block 1 makes F200-F3FF ROM inaccessible, however F000-F1FF is
still readable. So, care must be paid. still readable. So, care must be paid.
TODO: THe partial reading of ROM blocks switched out by RAM is not yet implemented!! TODO: THe partial reading of ROM blocks switched out by RAM is not yet implemented!!
This crazy RAM layout is useful as it allows contiguous RAM to be switched in, This crazy RAM layout is useful as it allows contiguous RAM to be switched in,
up to 2K in one sequentially accessible block. This means you CAN have 2K of up to 2K in one sequentially accessible block. This means you CAN have 2K of
consecutive RAM. If you don't detect ROM write area, then you would have NO ROM consecutive RAM. If you don't detect ROM write area, then you would have NO ROM
switched in (don't forget to copy your reset vectors!) switched in (don't forget to copy your reset vectors!)
@author Andrew Davie @author Andrew Davie
*/ */
class CartridgeDASH: public Cartridge class CartridgeDASH: public Cartridge {
{
friend class CartridgeDASHWidget; friend class CartridgeDASHWidget;
public: public:
/** /**
Create a new cartridge using the specified image and size Create a new cartridge using the specified image and size
@param image Pointer to the ROM image @param image Pointer to the ROM image
@param size The size of the ROM image @param size The size of the ROM image
@param settings A reference to the various settings (read-only) @param settings A reference to the various settings (read-only)
*/ */
CartridgeDASH(const uInt8* image, uInt32 size, const Settings& settings); CartridgeDASH(const uInt8* image, uInt32 size, const Settings& settings);
/** /**
Destructor Destructor
*/ */
virtual ~CartridgeDASH(); virtual ~CartridgeDASH();
public: public:
/** /**
Reset device to its power-on state Reset device to its power-on state
*/ */
void reset(); void reset();
/** /**
Install cartridge in the specified system. Invoked by the system Install cartridge in the specified system. Invoked by the system
when the cartridge is attached to it. when the cartridge is attached to it.
@param system The system the device should install itself in @param system The system the device should install itself in
*/ */
void install(System& system); void install(System& system);
/** /**
Install pages for the specified bank in the system. Install pages for the specified bank in the system.
@param bank The bank that should be installed in the system @param bank The bank that should be installed in the system
*/ */
bool bank(uInt16 bank); bool bank(uInt16 bank);
/** /**
Get the current bank. Get the current bank.
*/ */
uInt16 bank() const; uInt16 bank() const;
/** /**
Query the number of banks supported by the cartridge. Query the number of banks supported by the cartridge.
*/ */
uInt16 bankCount() const; uInt16 bankCount() const;
/** /**
Patch the cartridge ROM. Patch the cartridge ROM.
@param address The ROM address to patch @param address The ROM address to patch
@param value The value to place into the address @param value The value to place into the address
@return Success or failure of the patch operation @return Success or failure of the patch operation
*/ */
bool patch(uInt16 address, uInt8 value); bool patch(uInt16 address, uInt8 value);
/** /**
Access the internal ROM image for this cartridge. Access the internal ROM image for this cartridge.
@param size Set to the size of the internal ROM image data @param size Set to the size of the internal ROM image data
@return A pointer to the internal ROM image data @return A pointer to the internal ROM image data
*/ */
const uInt8* getImage(int& size) const; const uInt8* getImage(int& size) const;
/** /**
Save the current state of this cart to the given Serializer. Save the current state of this cart to the given Serializer.
@param out The Serializer object to use @param out The Serializer object to use
@return False on any errors, else true @return False on any errors, else true
*/ */
bool save(Serializer& out) const; bool save(Serializer& out) const;
/** /**
Load the current state of this cart from the given Serializer. Load the current state of this cart from the given Serializer.
@param in The Serializer object to use @param in The Serializer object to use
@return False on any errors, else true @return False on any errors, else true
*/ */
bool load(Serializer& in); bool load(Serializer& in);
/** /**
Get a descriptor for the device name (used in error checking). Get a descriptor for the device name (used in error checking).
@return The name of the object @return The name of the object
*/ */
string name() const { return "CartridgeDASH"; } string name() const {
return "CartridgeDASH";
}
#ifdef DEBUGGER_SUPPORT #ifdef DEBUGGER_SUPPORT
/** /**
Get debugger widget responsible for accessing the inner workings Get debugger widget responsible for accessing the inner workings
of the cart. of the cart.
*/ */
CartDebugWidget* debugWidget(GuiObject* boss, const GUI::Font& lfont, CartDebugWidget* debugWidget(GuiObject* boss, const GUI::Font& lfont,
const GUI::Font& nfont, int x, int y, int w, int h) const GUI::Font& nfont, int x, int y, int w, int h)
{ {
return 0;//new CartridgeDASHWidget(boss, lfont, nfont, x, y, w, h, *this); return 0; //new CartridgeDASHWidget(boss, lfont, nfont, x, y, w, h, *this);
} }
#endif #endif
public: public:
/** /**
Get the byte at the specified address Get the byte at the specified address
@return The byte at the specified address @return The byte at the specified address
*/ */
uInt8 peek(uInt16 address); uInt8 peek(uInt16 address);
/** /**
Change the byte at the specified address to the given value Change the byte at the specified address to the given value
@param address The address where the value should be stored @param address The address where the value should be stored
@param value The value to be stored at the address @param value The value to be stored at the address
@return True if the poke changed the device address space, else false @return True if the poke changed the device address space, else false
*/ */
bool poke(uInt16 address, uInt8 value); bool poke(uInt16 address, uInt8 value);
private: private:
uInt16 myCurrentBank; // whatever the LAST switched bank was... Int16 myCurrentBank; // whatever the LAST switched bank was...
uInt32 mySize; // Size of the ROM image uInt32 mySize; // Size of the ROM image
uInt8* myImage; // Pointer to a dynamically allocated ROM image of the cartridge uInt8* myImage; // Pointer to a dynamically allocated ROM image of the cartridge
Int16 bankInUse[4]; // bank being used for ROM/RAM (-1 = undefined) Int16 bankInUse[4]; // bank being used for ROM/RAM (-1 = undefined)
static const uInt16 BANK_SWITCH_HOTSPOT = 0x3F; // writes to this address cause bankswitching static const uInt16 BANK_SWITCH_HOTSPOT = 0x3F; // writes to this address cause bankswitching
static const uInt8 BANK_BITS = 5; // # bits for bank static const uInt8 BANK_BITS = 5; // # bits for bank
static const uInt8 BIT_BANK_MASK = (1 << BANK_BITS) - 1; // mask for those bits static const uInt8 BIT_BANK_MASK = (1 << BANK_BITS) - 1; // mask for those bits
static const uInt8 ROMRAM = 0x80; // flags ROM or RAM bank switching (1==RAM) static const uInt8 BITMASK_ROMRAM = 0x80; // flags ROM or RAM bank switching (1==RAM)
static const uInt16 RAM_BANK_COUNT = 32; static const uInt16 RAM_BANK_COUNT = 32;
static const uInt16 RAM_BANK_TO_POWER = 9; // 2^n = 512 static const uInt16 RAM_BANK_TO_POWER = 9; // 2^n = 512
static const uInt16 RAM_BANK_SIZE = (1 << RAM_BANK_TO_POWER); static const uInt16 RAM_BANK_SIZE = (1 << RAM_BANK_TO_POWER);
static const uInt32 RAM_TOTAL_SIZE = RAM_BANK_COUNT * RAM_BANK_SIZE; static const uInt16 BITMASK_RAM_BANK = (RAM_BANK_SIZE - 1);
static const uInt32 RAM_TOTAL_SIZE = RAM_BANK_COUNT * RAM_BANK_SIZE;
static const uInt16 ROM_BANK_TO_POWER = 10; // 2^n = 1024 static const uInt16 ROM_BANK_TO_POWER = 10; // 2^n = 1024
static const uInt16 ROM_BANK_SIZE = (1 << ROM_BANK_TO_POWER); static const uInt16 ROM_BANK_SIZE = (1 << ROM_BANK_TO_POWER);
static const uInt16 BITMASK_ROM_BANK = (ROM_BANK_SIZE -1);
static const uInt16 ROM_BANK_COUNT = 32; static const uInt16 ROM_BANK_COUNT = 32;
static const uInt16 ROM_BANK_MASK = (ROM_BANK_COUNT - 1); static const uInt16 BITMASK_ROM_BANK = (ROM_BANK_COUNT - 1);
static const uInt16 RAM_WRITE_OFFSET = 0x800; static const uInt16 RAM_WRITE_OFFSET = 0x800;
static const Int16 BANK_UNDEFINED = -1; // bank is undefined and inaccessible static const Int16 BANK_UNDEFINED = -1; // bank is undefined and inaccessible
uInt8 myRAM[RAM_TOTAL_SIZE]; uInt8 myRAM[RAM_TOTAL_SIZE];
}; };
#endif #endif