mirror of https://github.com/stella-emu/stella.git
Added preliminary support for DASH bankswitching scheme by A. Davie.
This hasn't been tested yet, since no ROMs currently exist. git-svn-id: svn://svn.code.sf.net/p/stella/code/trunk@2895 8b62c5a3-ac7e-4cc8-8f21-d9a121418aba
This commit is contained in:
parent
8f2123a5bf
commit
e81f2b0ffd
|
@ -14,6 +14,8 @@
|
|||
|
||||
3.9.3 to 4.0: (xxxx xx, 2014)
|
||||
|
||||
* Added preliminary support for 'DASH' bankswitching scheme by A. Davie.
|
||||
|
||||
* Added 'savesnap' debugger prompt command, and also associated
|
||||
context menu item to the debugger TIA output area. This saves the
|
||||
current TIA image to a PNG file.
|
||||
|
|
|
@ -3199,6 +3199,7 @@ Ms Pac-Man (Stella extended codes):
|
|||
<tr><td>BFSC </td><td>CPUWIZ 256K + ram</td></tr>
|
||||
<tr><td>CM ¹</td><td>Spectravideo CompuMate </td></tr>
|
||||
<tr><td>CV </td><td>Commavid extra ram </td></tr>
|
||||
<tr><td>DASH </td><td>Boulder Dash 2 </td></tr>
|
||||
<tr><td>DF </td><td>CPUWIZ 128K </td></tr>
|
||||
<tr><td>DFSC </td><td>CPUWIZ 128K + ram</td></tr>
|
||||
<tr><td>DPC </td><td>Pitfall II </td></tr>
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include "CartCM.hxx"
|
||||
#include "CartCTY.hxx"
|
||||
#include "CartCV.hxx"
|
||||
#include "CartDASH.hxx"
|
||||
#include "CartDPC.hxx"
|
||||
#include "CartDPCPlus.hxx"
|
||||
#include "CartE0.hxx"
|
||||
|
@ -199,6 +200,8 @@ Cartridge* Cartridge::create(const uInt8* image, uInt32 size, string& md5,
|
|||
cartridge = new CartridgeCTY(image, size, osystem);
|
||||
else if(type == "CV")
|
||||
cartridge = new CartridgeCV(image, size, settings);
|
||||
else if(type == "DASH")
|
||||
cartridge = new CartridgeDASH(image, size, settings);
|
||||
else if(type == "DPC")
|
||||
cartridge = new CartridgeDPC(image, size, settings);
|
||||
else if(type == "DPC+")
|
||||
|
@ -579,6 +582,7 @@ bool Cartridge::isProbablySC(const uInt8* image, uInt32 size)
|
|||
return true;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
bool Cartridge::isProbably4KSC(const uInt8* image, uInt32 size)
|
||||
{
|
||||
// We check if the first 256 bytes are identical *and* if there's
|
||||
|
@ -595,7 +599,6 @@ bool Cartridge::isProbably4KSC(const uInt8* image, uInt32 size)
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
bool Cartridge::isProbablyARM(const uInt8* image, uInt32 size)
|
||||
{
|
||||
|
@ -696,6 +699,12 @@ bool Cartridge::isProbablyCV(const uInt8* image, uInt32 size)
|
|||
return searchForBytes(image, size, signature[1], 3, 1);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
bool Cartridge::isProbablyDASH(const uInt8* image, uInt32 size)
|
||||
{
|
||||
return false; // TODO - add autodetection
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
bool Cartridge::isProbablyDPCplus(const uInt8* image, uInt32 size)
|
||||
{
|
||||
|
|
|
@ -323,6 +323,11 @@ class Cartridge : public Device
|
|||
*/
|
||||
static bool isProbablyCV(const uInt8* image, uInt32 size);
|
||||
|
||||
/**
|
||||
Returns true if the image is probably a DASH bankswitching cartridge
|
||||
*/
|
||||
static bool isProbablyDASH(const uInt8* image, uInt32 size);
|
||||
|
||||
/**
|
||||
Returns true if the image is probably a DPC+ bankswitching cartridge
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,366 @@
|
|||
//============================================================================
|
||||
//
|
||||
// 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);
|
||||
|
||||
// This cart can address 4 banks of RAM, each 512 bytes @ 1000, 1200, 1400, 1600
|
||||
// However, it may not be addressable all the time (it may be swapped out)
|
||||
|
||||
// TODO: Stephen -- is this correct for defining 4 separate RAM areas, or can it be done as one block?
|
||||
|
||||
registerRamArea(0x1000, RAM_BANK_SIZE, 0x00, RAM_WRITE_OFFSET); // 512 bytes RAM @ 0x1000
|
||||
registerRamArea(0x1200, RAM_BANK_SIZE, 0x00, RAM_WRITE_OFFSET); // 512 bytes RAM @ 0x1200
|
||||
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
|
||||
|
||||
// Remember startup bank (0 per spec, rather than last per 3E scheme).
|
||||
// Set this to go to 3rd 1K Bank.
|
||||
myStartBank = (3 << BANK_BITS) | 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);
|
||||
|
||||
// We'll map the startup bank (0) from the image into the third 1K bank upon reset
|
||||
|
||||
bank(myStartBank);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
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(0, 0, 0, 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);
|
||||
|
||||
// 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
|
||||
access.type = System::PA_READ;
|
||||
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
|
||||
access.directPeekBase = &myImage[byte]; // from base address 0x0000 in image, so just use 'byte'
|
||||
access.codeAccessBase = &myCodeAccessBase[byte];
|
||||
mySystem->setPageAccess(address>>shift, access);
|
||||
|
||||
// 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,
|
||||
// I have an issue with how these loops are encoded in all the bank schemes I've seen. The issue being that
|
||||
// the loop inits set some address (e.g., 0x1800) and then add a shifted value to it every loop. But when
|
||||
// they want to get the downshifted value, they just shift down the whole value. Which will also shift down that base address
|
||||
// and that makes no sense at all to me. I don't understand it.
|
||||
}
|
||||
|
||||
// Initialise bank values for the 4x 1K bank areas
|
||||
// This is used to reverse-lookup from address to bank location
|
||||
for(uInt32 b = 0; b < 3; b++)
|
||||
bankInUse[b] = BANK_UNDEFINED; // bank is undefined and inaccessible!
|
||||
|
||||
// Install pages for the startup bank into the first segment
|
||||
bank(myStartBank);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
uInt8 CartridgeDASH::peek(uInt16 address)
|
||||
{
|
||||
uInt8 value = 0;
|
||||
uInt32 bank = (address >> 10) & 3; // convert to 1K bank index (0-3)
|
||||
Int16 imageBank = bankInUse[bank]; // the ROM/RAM bank that's here
|
||||
|
||||
if(imageBank <= BANK_UNDEFINED)
|
||||
{
|
||||
// accessing invalid bank, so return should be... random?
|
||||
// TODO: Stephen -- throw some sort of error; looking at undefined data
|
||||
|
||||
assert(false);
|
||||
value = mySystem->randGenerator().next();
|
||||
}
|
||||
|
||||
else if(imageBank < ROM_BANK_COUNT) // accessing ROM
|
||||
{
|
||||
Int32 offset = imageBank << ROM_BANK_TO_POWER; // base bank address in image
|
||||
offset += (address & (ROM_BANK_SIZE-1)); // + byte offset in image bank
|
||||
value = myImage[offset];
|
||||
}
|
||||
else // must be a RAM bank
|
||||
{
|
||||
Int32 ramBank = imageBank - ROM_BANK_COUNT;
|
||||
assert(ramBank < RAM_BANK_COUNT); // would be bad, otherwise
|
||||
|
||||
Int32 offset = ramBank << RAM_BANK_TO_POWER; // base bank address in RAM
|
||||
offset += (address & (RAM_BANK_SIZE-1)); // + byte offset in RAM bank
|
||||
value = myRAM[offset];
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
bool CartridgeDASH::poke(uInt16 address, uInt8 value)
|
||||
{
|
||||
address &= 0x0FFF; // restrict to 4K address range
|
||||
|
||||
// Check for write to 3E (RAM switching) or 3F (ROM switching) and switch
|
||||
// banks if necessary. There are NO mirrored hotspots.
|
||||
|
||||
switch (address)
|
||||
{
|
||||
case 0x3F: // a ROM switch
|
||||
assert(value < ROM_BANK_COUNT);
|
||||
bank(value);
|
||||
break;
|
||||
case 0x3E: // a RAM switch
|
||||
assert(value < RAM_BANK_COUNT);
|
||||
bank(ROMRAM|value);
|
||||
break;
|
||||
}
|
||||
|
||||
// @THOMAS -- well, really we don't need to use 3E and 3F -- we can just use (say) 3E
|
||||
// and the value determines if we're doing RAM(64+bank) or ROM(bank) writes.
|
||||
// No need to have two addresses at all, right?
|
||||
|
||||
// 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 false;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
bool CartridgeDASH::bank(uInt16 bank)
|
||||
{
|
||||
if(bankLocked())
|
||||
return false; // TODO: Stephen -- ? no idea
|
||||
|
||||
uInt16 shift = mySystem->pageShift();
|
||||
|
||||
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)
|
||||
|
||||
if(bank & ROMRAM) // switching to a 512 byte RAM bank
|
||||
{
|
||||
// Wrap around/restrict to valid range
|
||||
uInt16 currentBank = (bank & BIT_BANK_MASK) % RAM_BANK_COUNT;
|
||||
// Record which bank switched in (marked as RAM)
|
||||
myCurrentBank = bankInUse[bankNumber] = (Int16) (ROM_BANK_COUNT + currentBank);
|
||||
// Effectively * 512 bytes
|
||||
uInt32 startCurrentBank = currentBank << RAM_BANK_TO_POWER;
|
||||
|
||||
// Setup the page access methods for the current bank
|
||||
System::PageAccess access(0, 0, 0, this, System::PA_READ);
|
||||
|
||||
// Map read-port RAM image into the system
|
||||
for(uInt32 byte = 0; byte < RAM_BANK_SIZE; byte += (1 << shift))
|
||||
{
|
||||
access.directPeekBase = &myRAM[startCurrentBank + byte];
|
||||
|
||||
// TODO: Stephen please explain/review the use of mySize as an offset for RAM access here....
|
||||
access.codeAccessBase = &myCodeAccessBase[mySize + startCurrentBank + byte]; //?eh
|
||||
|
||||
mySystem->setPageAccess((startCurrentBank + byte) >> shift, access);
|
||||
}
|
||||
|
||||
access.directPeekBase = 0;
|
||||
access.type = System::PA_WRITE;
|
||||
|
||||
// Map write-port RAM image into the system
|
||||
for (uInt32 byte = 0; byte < RAM_BANK_SIZE; byte += (1 << shift))
|
||||
{
|
||||
access.directPokeBase = &myRAM[startCurrentBank + RAM_WRITE_OFFSET + byte];
|
||||
access.codeAccessBase = &myCodeAccessBase[mySize + startCurrentBank + RAM_WRITE_OFFSET + byte];
|
||||
mySystem->setPageAccess((startCurrentBank + byte) >> shift, access);
|
||||
}
|
||||
}
|
||||
else // ROM 1K banks
|
||||
{
|
||||
// Map ROM bank image into the system into the correct slot
|
||||
// Memory map is 1K slots at 0x1000, 0x1400, 0x1800, 0x1C00
|
||||
|
||||
// Record which bank switched in (as ROM)
|
||||
myCurrentBank = bankInUse[bankNumber] = (Int16) bankID;
|
||||
// Effectively *1K
|
||||
uInt32 startCurrentBank = bankID << ROM_BANK_TO_POWER;
|
||||
|
||||
// Setup the page access methods for the current bank
|
||||
System::PageAccess access(0, 0, 0, this, System::PA_READ);
|
||||
|
||||
uInt32 bankStart = 0x1000 + (bankNumber << ROM_BANK_TO_POWER); // *1K
|
||||
|
||||
for (uInt32 byte = 0; byte < ROM_BANK_SIZE; byte += (1 << shift))
|
||||
{
|
||||
access.directPeekBase = &myImage[startCurrentBank + byte];
|
||||
access.codeAccessBase = &myCodeAccessBase[startCurrentBank + byte];
|
||||
mySystem->setPageAccess((bankStart + byte) >> shift, access);
|
||||
}
|
||||
}
|
||||
|
||||
return myBankChanged = true;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
uInt16 CartridgeDASH::bank() const
|
||||
{
|
||||
// TODO: Stephen -- what to do here? We don't really HAVE a "current bank"; we have 4 banks
|
||||
// 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. :(
|
||||
|
||||
return myCurrentBank;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
uInt16 CartridgeDASH::bankCount() const
|
||||
{
|
||||
// Because the RAM banks always start above the ROM banks (see ROM_BANK_COUNT) for value,
|
||||
// we require the number of ROM banks to be == ROM_BANK_COUNT. Banks are therefore 0-63 ROM 64-127 RAM
|
||||
// TODO: Stephen -- ROM banks are 1K. RAM banks are 512 bytes. How does this affect what this routine should return?
|
||||
return ROM_BANK_COUNT + RAM_BANK_COUNT;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
bool CartridgeDASH::patch(uInt16 address, uInt8 value)
|
||||
{
|
||||
// Patch the cartridge ROM
|
||||
// TODO: Stephen... I assume this is for some sort of debugger support....?
|
||||
|
||||
myBankChanged = true;
|
||||
|
||||
uInt32 bankNumber = (address >> 10) & 3; // now 1K bank # (ie: 0-3)
|
||||
Int32 whichBankIsThere = bankInUse[bankNumber]; // ROM or RAM bank reference
|
||||
|
||||
if(whichBankIsThere <= BANK_UNDEFINED)
|
||||
{
|
||||
// We're trying to access undefined memory (no bank here yet)
|
||||
|
||||
// TODO: Stephen -- what to do here? Fail silently?
|
||||
// want to throw some sort of Stella error -- trying to patch an unswitched in bank!
|
||||
|
||||
assert(false);
|
||||
myBankChanged = false;
|
||||
}
|
||||
else if(whichBankIsThere < ROM_BANK_COUNT) // patching ROM (1K banks)
|
||||
{
|
||||
uInt32 byteOffset = address & (ROM_BANK_SIZE-1);
|
||||
uInt32 baseAddress = (whichBankIsThere << ROM_BANK_TO_POWER) + byteOffset;
|
||||
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;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
const uInt8* CartridgeDASH::getImage(int& size) const
|
||||
{
|
||||
size = mySize;
|
||||
return myImage;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
bool CartridgeDASH::save(Serializer& out) const
|
||||
{
|
||||
try
|
||||
{
|
||||
out.putString(name());
|
||||
out.putShort(myCurrentBank);
|
||||
for(uInt32 bank = 0; bank < 4; bank++)
|
||||
out.putShort(bankInUse[bank]);
|
||||
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;
|
||||
myCurrentBank = in.getShort();
|
||||
for(uInt32 bank = 0; bank < 4; bank++)
|
||||
bankInUse[bank] = in.getShort();
|
||||
in.getByteArray(myRAM, RAM_TOTAL_SIZE);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
cerr << "ERROR: CartridgeDASH::load" << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now, go to the current bank
|
||||
bank(myCurrentBank);
|
||||
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,306 @@
|
|||
//============================================================================
|
||||
//
|
||||
// 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$
|
||||
//============================================================================
|
||||
|
||||
#ifndef CARTRIDGEDASH_HXX
|
||||
#define CARTRIDGEDASH_HXX
|
||||
|
||||
class System;
|
||||
|
||||
#include "bspf.hxx"
|
||||
#include "Cart.hxx"
|
||||
|
||||
#ifdef DEBUGGER_SUPPORT
|
||||
class CartridgeDASHWidget;
|
||||
// #include "CartDASHWidget.hxx"
|
||||
#endif
|
||||
|
||||
/**
|
||||
Cartridge class for new tiling engine "Boulder Dash" format games with RAM.
|
||||
Kind of a combination of 3F and 3E, with better switchability.
|
||||
This code is B.Watson's Cart3E modified to new specs by Andrew Davie.
|
||||
|
||||
Note: 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
|
||||
number. This sets the limits of 32K ROM and 16K RAM.
|
||||
|
||||
D7 is that RAM/ROM flag (1=RAM)
|
||||
D6D5 indciates the bank number (0-3)
|
||||
D4D0 indicate the actual # (0-31) from the image/ram
|
||||
|
||||
ROM:
|
||||
|
||||
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.),
|
||||
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
|
||||
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
|
||||
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
|
||||
bank mapped to $FC00.
|
||||
|
||||
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
|
||||
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,
|
||||
but it would be nice for the cartridge hardware to auto-switch on reset.
|
||||
|
||||
For both ROM (write to 0x3F) and RAM (write to 0x3E) bank switching, the
|
||||
top two bits indicate the physical address segment which is being switched,
|
||||
and the low 6 bits indicate the bank number, as per the following ...
|
||||
|
||||
ROM switching (write of block+bank number to $3F) upper 2 bits of bank #
|
||||
indicates the destination segment (0-3, corresponding to $F000, $F400,
|
||||
$F800, $FC00), and lower 6 bits indicate the 1K bank to switch in. Can
|
||||
handle 64 x 1K ROM banks (64K total).
|
||||
|
||||
BITS ACTION
|
||||
D7D6 0xxxxx
|
||||
0 0 -- switch a 1K ROM bank 0xxxxx to $F000
|
||||
0 1 -- switch a 1K ROM bank 0xxxxx to $F400
|
||||
1 0 -- switch 1K ROM bank 0xxxxx to $F800
|
||||
1 1 -- switch 1K ROM bank 0xxxxx to $FC00
|
||||
|
||||
can handle 32K ROM maximum
|
||||
|
||||
RAM switching (write of segment+bank number to $3E) upper 2 bits of bank #
|
||||
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 with consecutive addresses. However,
|
||||
as the write address of RAM is +0x800, this invalidates ROM access as
|
||||
described below.
|
||||
|
||||
write access uses +$800
|
||||
can handle 32 x 512 byte RAM banks (16K total)
|
||||
|
||||
BITS ACTION
|
||||
D7D6 1xxxxx
|
||||
0 0 -- 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
|
||||
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
|
||||
|
||||
It is possible to switch multiple RAM banks and ROM banks together
|
||||
|
||||
For example,
|
||||
F000-F1FF RAM bank A (512 byte READ)
|
||||
F200-F3FF high 512 bytes of ROM bank previously loaded at F000
|
||||
F400 ROM bank 0 (1K)
|
||||
F800 RAM bank A (512 byte WRITE)
|
||||
FA00-FBFF high 512 bytes of ROM bank previously loaded at F400
|
||||
FC00 ROM bank 1
|
||||
|
||||
This example shows 512 bytes of RAM, and 2 1K ROM banks and two 512 byte ROM
|
||||
bank halves.
|
||||
|
||||
Switching RAM blocks (D7D6 of $3E) partially invalidates ROM blocks, as below...
|
||||
|
||||
RAM block Invalidates ROM block
|
||||
0 0 (lower half), 2 (lower half)
|
||||
1 0 (upper half), 2 (upper half)
|
||||
2 1 (lower half), 3 (upper half)
|
||||
3 1 (upper half), 3 (lower half)
|
||||
|
||||
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
|
||||
Switching in RAM block 1 makes F200-F3FF ROM inaccessible, however F000-F1FF is
|
||||
still readable. So, care must be paid.
|
||||
|
||||
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
|
||||
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!)
|
||||
|
||||
NOTE:
|
||||
We could consider the 4A50 method where the cart detects read/writes and we
|
||||
use the same address for reading/writing. magic writes. If we could avoid
|
||||
the memory doubling for RAM, that would make it easier to code -- use the
|
||||
same variables for read/write This would then mean that ROM banks 2,3 would
|
||||
never be bothered by RAM. I like this.
|
||||
|
||||
----------------------------------------------------------------------------------------------
|
||||
|
||||
This implementation of DASH bankswitching numbers the ROM banks 0 to 63, and
|
||||
the RAM banks 64 to 127. This is done because the public bankswitching
|
||||
interface requires us to use one bank number, not one bank number plus the
|
||||
knowledge of whether it's RAM or ROM.
|
||||
|
||||
All 32K of potential RAM is available to a game using this class, even though
|
||||
real cartridges might not have the full 32K: We have no way to tell how much
|
||||
RAM the game expects. This may change in the future (we may add a stella.pro
|
||||
property for this), but for now it shouldn't cause any problems.
|
||||
(Famous last words...)
|
||||
|
||||
@author A. Davie
|
||||
*/
|
||||
|
||||
class CartridgeDASH: public Cartridge
|
||||
{
|
||||
friend class CartridgeDASHWidget;
|
||||
|
||||
public:
|
||||
/**
|
||||
Create a new cartridge using the specified image and size
|
||||
|
||||
@param image Pointer to the ROM image
|
||||
@param size The size of the ROM image
|
||||
@param settings A reference to the various settings (read-only)
|
||||
*/
|
||||
CartridgeDASH(const uInt8* image, uInt32 size, const Settings& settings);
|
||||
|
||||
/**
|
||||
Destructor
|
||||
*/
|
||||
virtual ~CartridgeDASH();
|
||||
|
||||
public:
|
||||
/**
|
||||
Reset device to its power-on state
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
Install cartridge in the specified system. Invoked by the system
|
||||
when the cartridge is attached to it.
|
||||
|
||||
@param system The system the device should install itself in
|
||||
*/
|
||||
void install(System& system);
|
||||
|
||||
/**
|
||||
Install pages for the specified bank in the system.
|
||||
|
||||
@param bank The bank that should be installed in the system
|
||||
*/
|
||||
bool bank(uInt16 bank);
|
||||
|
||||
/**
|
||||
Get the current bank.
|
||||
*/
|
||||
uInt16 bank() const;
|
||||
|
||||
/**
|
||||
Query the number of banks supported by the cartridge.
|
||||
*/
|
||||
uInt16 bankCount() const;
|
||||
|
||||
/**
|
||||
Patch the cartridge ROM.
|
||||
|
||||
@param address The ROM address to patch
|
||||
@param value The value to place into the address
|
||||
@return Success or failure of the patch operation
|
||||
*/
|
||||
bool patch(uInt16 address, uInt8 value);
|
||||
|
||||
/**
|
||||
Access the internal ROM image for this cartridge.
|
||||
|
||||
@param size Set to the size of the internal ROM image data
|
||||
@return A pointer to the internal ROM image data
|
||||
*/
|
||||
const uInt8* getImage(int& size) const;
|
||||
|
||||
/**
|
||||
Save the current state of this cart to the given Serializer.
|
||||
|
||||
@param out The Serializer object to use
|
||||
@return False on any errors, else true
|
||||
*/
|
||||
bool save(Serializer& out) const;
|
||||
|
||||
/**
|
||||
Load the current state of this cart from the given Serializer.
|
||||
|
||||
@param in The Serializer object to use
|
||||
@return False on any errors, else true
|
||||
*/
|
||||
bool load(Serializer& in);
|
||||
|
||||
/**
|
||||
Get a descriptor for the device name (used in error checking).
|
||||
|
||||
@return The name of the object
|
||||
*/
|
||||
string name() const { return "CartridgeDASH"; }
|
||||
|
||||
#ifdef DEBUGGER_SUPPORT
|
||||
/**
|
||||
Get debugger widget responsible for accessing the inner workings
|
||||
of the cart.
|
||||
*/
|
||||
CartDebugWidget* debugWidget(GuiObject* boss, const GUI::Font& lfont,
|
||||
const GUI::Font& nfont, int x, int y, int w, int h)
|
||||
{
|
||||
return 0;//new CartridgeDASHWidget(boss, lfont, nfont, x, y, w, h, *this);
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
/**
|
||||
Get the byte at the specified address
|
||||
|
||||
@return The byte at the specified address
|
||||
*/
|
||||
uInt8 peek(uInt16 address);
|
||||
|
||||
/**
|
||||
Change the byte at the specified address to the given value
|
||||
|
||||
@param address The address where the value should be stored
|
||||
@param value The value to be stored at the address
|
||||
@return True if the poke changed the device address space, else false
|
||||
*/
|
||||
bool poke(uInt16 address, uInt8 value);
|
||||
|
||||
private:
|
||||
uInt16 myCurrentBank; // whatever the LAST switched bank was...
|
||||
|
||||
uInt32 mySize; // Size of the ROM image
|
||||
uInt8* myImage; // Pointer to a dynamically allocated ROM image of the cartridge
|
||||
|
||||
Int16 bankInUse[4]; // bank being used for ROM/RAM (-1 = undefined)
|
||||
|
||||
// RAM contents. RAM banks are 512 bytes, and there are a maximum of 64 of them
|
||||
|
||||
enum {
|
||||
BANK_BITS = 5, // # bits for bank
|
||||
BIT_BANK_MASK = (1 << BANK_BITS) - 1, // mask for those bits
|
||||
ROMRAM = 0x80, // flags ROM or RAM bank switching (1==RAM)
|
||||
|
||||
RAM_BANK_COUNT = 32,
|
||||
RAM_BANK_TO_POWER = 9, // 2^n = 512
|
||||
RAM_BANK_SIZE = (1 << RAM_BANK_TO_POWER),
|
||||
RAM_TOTAL_SIZE = RAM_BANK_COUNT * RAM_BANK_SIZE,
|
||||
|
||||
ROM_BANK_TO_POWER = 10, // 2^n = 1024
|
||||
ROM_BANK_SIZE = (1 << ROM_BANK_TO_POWER),
|
||||
|
||||
ROM_BANK_COUNT = 32,
|
||||
ROM_BANK_MASK = (ROM_BANK_COUNT - 1),
|
||||
|
||||
RAM_WRITE_OFFSET = 0x800,
|
||||
|
||||
BANK_UNDEFINED = -1 // bank is undefined and inaccessible
|
||||
};
|
||||
|
||||
uInt8 myRAM[RAM_TOTAL_SIZE];
|
||||
};
|
||||
|
||||
#endif
|
|
@ -15,6 +15,7 @@ MODULE_OBJS := \
|
|||
src/emucore/CartCM.o \
|
||||
src/emucore/CartCTY.o \
|
||||
src/emucore/CartCV.o \
|
||||
src/emucore/CartDASH.o \
|
||||
src/emucore/CartDPC.o \
|
||||
src/emucore/CartDPCPlus.o \
|
||||
src/emucore/CartE0.o \
|
||||
|
|
|
@ -152,6 +152,7 @@ GameInfoDialog::GameInfoDialog(
|
|||
items.push_back("BFSC (CPUWIZ 256K + ram)", "BFSC" );
|
||||
items.push_back("CV (Commavid extra ram)", "CV" );
|
||||
items.push_back("CM (SpectraVideo CompuMate)", "CM" );
|
||||
items.push_back("DASH (Boulder Dash 2)", "DASH" );
|
||||
items.push_back("DF (CPUWIZ 128K)", "DF" );
|
||||
items.push_back("DFSC (CPUWIZ 128K + ram)", "DFSC" );
|
||||
items.push_back("DPC (Pitfall II)", "DPC" );
|
||||
|
|
|
@ -83,6 +83,7 @@ GlobalPropsDialog::GlobalPropsDialog(GuiObject* boss, const GUI::Font& font)
|
|||
items.push_back("BFSC (CPUWIZ 256K + ram)", "BFSC" );
|
||||
items.push_back("CV (Commavid extra ram)", "CV" );
|
||||
items.push_back("CM (SpectraVideo CompuMate)", "CM" );
|
||||
items.push_back("DASH (Boulder Dash 2)", "DASH" );
|
||||
items.push_back("DF (CPUWIZ 128K)", "DF" );
|
||||
items.push_back("DFSC (CPUWIZ 128K + ram)", "DFSC" );
|
||||
items.push_back("DPC (Pitfall II)", "DPC" );
|
||||
|
|
Loading…
Reference in New Issue