Reworked 'read from write port' emulation for carts with extended RAM.

The values written and returned in such a case are now more accurate,
and are a combination of the previous databus value and randomization
(the latter emulating the randomness of Z-state bits).  This provides
more accurate emulation than before, where zeros were used instead.
In particular, types 3E and E7 are now working correctly for the first
time.  Thanks to Batari for suggestions in this area.

Moved random number generation from Cartridge to System class.

The Subversion build number is now shown in the AboutDialog box.
Still TODO is add architecture information (i386, x86_64, etc).


git-svn-id: svn://svn.code.sf.net/p/stella/code/trunk@1895 8b62c5a3-ac7e-4cc8-8f21-d9a121418aba
This commit is contained in:
stephena 2009-11-08 01:39:05 +00:00
parent 25f483dc27
commit 3df721e0be
20 changed files with 125 additions and 79 deletions

View File

@ -50,7 +50,7 @@ void Cartridge3E::reset()
{ {
// Initialize RAM with random values // Initialize RAM with random values
for(uInt32 i = 0; i < 32768; ++i) for(uInt32 i = 0; i < 32768; ++i)
myRam[i] = myRandGenerator.next(); myRam[i] = mySystem->randGenerator().next();
// We'll map bank 0 into the first segment upon reset // We'll map bank 0 into the first segment upon reset
bank(0); bank(0);
@ -101,10 +101,19 @@ uInt8 Cartridge3E::peek(uInt16 address)
{ {
if(myCurrentBank < 256) if(myCurrentBank < 256)
return myImage[(address & 0x07FF) + (myCurrentBank << 11)]; return myImage[(address & 0x07FF) + (myCurrentBank << 11)];
else if(address < 0x400) // Read from write port gives undefined values
return myRandGenerator.next();
else else
return myRam[(address & 0x03FF) + ((myCurrentBank - 256) << 10)]; {
if(address < 0x0400)
return myRam[(address & 0x03FF) + ((myCurrentBank - 256) << 10)];
else
{
// Reading from the write port triggers an unwanted write
uInt8 value = mySystem->getDataBusState(0xFF);
if(myBankLocked) return value;
else return myRam[(address & 0x03FF) + ((myCurrentBank - 256) << 10)] = value;
}
}
} }
else else
{ {

View File

@ -46,7 +46,7 @@ void Cartridge4A50::reset()
{ {
// Initialize RAM with random values // Initialize RAM with random values
for(uInt32 i = 0; i < 32768; ++i) for(uInt32 i = 0; i < 32768; ++i)
myRAM[i] = myRandGenerator.next(); myRAM[i] = mySystem->randGenerator().next();
mySliceLow = mySliceMiddle = mySliceHigh = 0; mySliceLow = mySliceMiddle = mySliceHigh = 0;
myIsRomLow = myIsRomMiddle = myIsRomHigh = true; myIsRomLow = myIsRomMiddle = myIsRomHigh = true;

View File

@ -53,7 +53,7 @@ void CartridgeAR::reset()
{ {
// Initialize RAM with random values // Initialize RAM with random values
for(uInt32 i = 0; i < 6 * 1024; ++i) for(uInt32 i = 0; i < 6 * 1024; ++i)
myImage[i] = myRandGenerator.next(); myImage[i] = mySystem->randGenerator().next();
// Initialize SC BIOS ROM // Initialize SC BIOS ROM
initializeROM(); initializeROM();
@ -300,7 +300,7 @@ void CartridgeAR::initializeROM()
// The accumulator should contain a random value after exiting the // The accumulator should contain a random value after exiting the
// SC BIOS code - a value placed in offset 281 will be stored in A // SC BIOS code - a value placed in offset 281 will be stored in A
ourDummyROMCode[281] = myRandGenerator.next(); ourDummyROMCode[281] = mySystem->randGenerator().next();
// Initialize ROM with illegal 6502 opcode that causes a real 6502 to jam // Initialize ROM with illegal 6502 opcode that causes a real 6502 to jam
for(uInt32 i = 0; i < 2048; ++i) for(uInt32 i = 0; i < 2048; ++i)

View File

@ -30,8 +30,6 @@ CartridgeCV::CartridgeCV(const uInt8* image, uInt32 size)
myROM = new uInt8[mySize]; myROM = new uInt8[mySize];
memcpy(myROM, image, mySize); memcpy(myROM, image, mySize);
reset();
// This cart contains 1024 bytes extended RAM @ 0x1000 // This cart contains 1024 bytes extended RAM @ 0x1000
registerRamArea(0x1000, 1024, 0x00, 0x400); registerRamArea(0x1000, 1024, 0x00, 0x400);
} }
@ -52,7 +50,7 @@ void CartridgeCV::reset()
// Initialize RAM with random values // Initialize RAM with random values
for(uInt32 i = 0; i < 1024; ++i) for(uInt32 i = 0; i < 1024; ++i)
myRAM[i] = myRandGenerator.next(); myRAM[i] = mySystem->randGenerator().next();
} }
else if(mySize == 4096) else if(mySize == 4096)
{ {
@ -110,13 +108,13 @@ void CartridgeCV::install(System& system)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt8 CartridgeCV::peek(uInt16 address) uInt8 CartridgeCV::peek(uInt16 address)
{ {
// Reading from the write port triggers an unwanted write
// The value written to RAM is somewhat undefined, so we use 0
// Thanks to Kroko of AtariAge for this advice and code idea
if((address & 0x0FFF) < 0x0800) // Write port is at 0xF400 - 0xF800 (1024 bytes) if((address & 0x0FFF) < 0x0800) // Write port is at 0xF400 - 0xF800 (1024 bytes)
{ // Read port is handled in ::install() { // Read port is handled in ::install()
if(myBankLocked) return 0; // Reading from the write port triggers an unwanted write
else return myRAM[address & 0x03FF] = 0; uInt8 value = mySystem->getDataBusState(0xFF);
if(myBankLocked) return value;
else return myRAM[address & 0x03FF] = value;
} }
else else
{ {

View File

@ -46,7 +46,7 @@ void CartridgeE7::reset()
{ {
// Initialize RAM with random values // Initialize RAM with random values
for(uInt32 i = 0; i < 2048; ++i) for(uInt32 i = 0; i < 2048; ++i)
myRAM[i] = myRandGenerator.next(); myRAM[i] = mySystem->randGenerator().next();
// Install some default banks for the RAM and first segment // Install some default banks for the RAM and first segment
bankRAM(0); bankRAM(0);
@ -104,11 +104,14 @@ uInt8 CartridgeE7::peek(uInt16 address)
bankRAM(address & 0x0003); bankRAM(address & 0x0003);
} }
// NOTE: The following does not handle reading from RAM, however, if((address < 0x0400) && (bank() == 7))
// this function should never be called for RAM because of the {
// way page accessing has been setup // Reading from the write port triggers an unwanted write
if((bank() == 7) && (address < 0x400)) uInt8 value = mySystem->getDataBusState(0xFF);
return myRandGenerator.next(); // Read from write port gives undefined values
if(myBankLocked) return value;
else return myImage[(myCurrentSlice[address >> 11] << 11) + (address & 0x07FF)] = value;
}
else else
return myImage[(myCurrentSlice[address >> 11] << 11) + (address & 0x07FF)]; return myImage[(myCurrentSlice[address >> 11] << 11) + (address & 0x07FF)];
} }

View File

@ -42,7 +42,7 @@ void CartridgeEFSC::reset()
{ {
// Initialize RAM with random values // Initialize RAM with random values
for(uInt32 i = 0; i < 128; ++i) for(uInt32 i = 0; i < 128; ++i)
myRAM[i] = myRandGenerator.next(); myRAM[i] = mySystem->randGenerator().next();
// Upon reset we switch to bank 1 // Upon reset we switch to bank 1
bank(1); bank(1);
@ -99,13 +99,13 @@ uInt8 CartridgeEFSC::peek(uInt16 address)
if((address >= 0x0FE0) && (address <= 0x0FEF)) if((address >= 0x0FE0) && (address <= 0x0FEF))
bank(address - 0x0FE0); bank(address - 0x0FE0);
// Reading from the write port triggers an unwanted write
// The value written to RAM is somewhat undefined, so we use 0
// Thanks to Kroko of AtariAge for this advice and code idea
if(address < 0x0080) // Write port is at 0xF000 - 0xF080 (128 bytes) if(address < 0x0080) // Write port is at 0xF000 - 0xF080 (128 bytes)
{ {
if(myBankLocked) return 0; // Reading from the write port triggers an unwanted write
else return myRAM[address] = 0; uInt8 value = mySystem->getDataBusState(0xFF);
if(myBankLocked) return value;
else return myRAM[address] = value;
} }
else else
return myImage[(myCurrentBank << 12) + address]; return myImage[(myCurrentBank << 12) + address];

View File

@ -42,7 +42,7 @@ void CartridgeF4SC::reset()
{ {
// Initialize RAM with random values // Initialize RAM with random values
for(uInt32 i = 0; i < 128; ++i) for(uInt32 i = 0; i < 128; ++i)
myRAM[i] = myRandGenerator.next(); myRAM[i] = mySystem->randGenerator().next();
// Upon reset we switch to bank 0 // Upon reset we switch to bank 0
bank(0); bank(0);
@ -99,13 +99,13 @@ uInt8 CartridgeF4SC::peek(uInt16 address)
if((address >= 0x0FF4) && (address <= 0x0FFB)) if((address >= 0x0FF4) && (address <= 0x0FFB))
bank(address - 0x0FF4); bank(address - 0x0FF4);
// Reading from the write port triggers an unwanted write
// The value written to RAM is somewhat undefined, so we use 0
// Thanks to Kroko of AtariAge for this advice and code idea
if(address < 0x0080) // Write port is at 0xF000 - 0xF080 (128 bytes) if(address < 0x0080) // Write port is at 0xF000 - 0xF080 (128 bytes)
{ {
if(myBankLocked) return 0; // Reading from the write port triggers an unwanted write
else return myRAM[address] = 0; uInt8 value = mySystem->getDataBusState(0xFF);
if(myBankLocked) return value;
else return myRAM[address] = value;
} }
else else
return myImage[(myCurrentBank << 12) + address]; return myImage[(myCurrentBank << 12) + address];

View File

@ -42,7 +42,7 @@ void CartridgeF6SC::reset()
{ {
// Initialize RAM with random values // Initialize RAM with random values
for(uInt32 i = 0; i < 128; ++i) for(uInt32 i = 0; i < 128; ++i)
myRAM[i] = myRandGenerator.next(); myRAM[i] = mySystem->randGenerator().next();
// Upon reset we switch to bank 0 // Upon reset we switch to bank 0
bank(0); bank(0);
@ -122,13 +122,13 @@ uInt8 CartridgeF6SC::peek(uInt16 address)
break; break;
} }
// Reading from the write port triggers an unwanted write
// The value written to RAM is somewhat undefined, so we use 0
// Thanks to Kroko of AtariAge for this advice and code idea
if(address < 0x0080) // Write port is at 0xF000 - 0xF080 (128 bytes) if(address < 0x0080) // Write port is at 0xF000 - 0xF080 (128 bytes)
{ {
if(myBankLocked) return 0; // Reading from the write port triggers an unwanted write
else return myRAM[address] = 0; uInt8 value = mySystem->getDataBusState(0xFF);
if(myBankLocked) return value;
else return myRAM[address] = value;
} }
else else
return myImage[(myCurrentBank << 12) + address]; return myImage[(myCurrentBank << 12) + address];

View File

@ -42,7 +42,7 @@ void CartridgeF8SC::reset()
{ {
// Initialize RAM with random values // Initialize RAM with random values
for(uInt32 i = 0; i < 128; ++i) for(uInt32 i = 0; i < 128; ++i)
myRAM[i] = myRandGenerator.next(); myRAM[i] = mySystem->randGenerator().next();
// Upon reset we switch to bank 1 // Upon reset we switch to bank 1
bank(1); bank(1);
@ -112,13 +112,13 @@ uInt8 CartridgeF8SC::peek(uInt16 address)
break; break;
} }
// Reading from the write port triggers an unwanted write
// The value written to RAM is somewhat undefined, so we use 0
// Thanks to Kroko of AtariAge for this advice and code idea
if(address < 0x0080) // Write port is at 0xF000 - 0xF080 (128 bytes) if(address < 0x0080) // Write port is at 0xF000 - 0xF080 (128 bytes)
{ {
if(myBankLocked) return 0; // Reading from the write port triggers an unwanted write
else return myRAM[address] = 0; uInt8 value = mySystem->getDataBusState(0xFF);
if(myBankLocked) return value;
else return myRAM[address] = value;
} }
else else
return myImage[(myCurrentBank << 12) + address]; return myImage[(myCurrentBank << 12) + address];

View File

@ -42,7 +42,7 @@ void CartridgeFASC::reset()
{ {
// Initialize RAM with random values // Initialize RAM with random values
for(uInt32 i = 0; i < 256; ++i) for(uInt32 i = 0; i < 256; ++i)
myRAM[i] = myRandGenerator.next(); myRAM[i] = mySystem->randGenerator().next();
// Upon reset we switch to bank 2 // Upon reset we switch to bank 2
bank(2); bank(2);
@ -117,22 +117,16 @@ uInt8 CartridgeFASC::peek(uInt16 address)
break; break;
} }
// Reading from the write port triggers an unwanted write
// The value written to RAM is somewhat undefined, so we use 0
// Thanks to Kroko of AtariAge for this advice and code idea
if(address < 0x0100) // Write port is at 0xF000 - 0xF100 (256 bytes) if(address < 0x0100) // Write port is at 0xF000 - 0xF100 (256 bytes)
{ {
if(!myBankLocked) // Reading from the write port triggers an unwanted write
{ uInt8 value = mySystem->getDataBusState(0xFF);
return myRAM[address] = 0;
} if(myBankLocked) return value;
else else return myRAM[address] = value;
return 0;
} }
else else
{
return myImage[(myCurrentBank << 12) + address]; return myImage[(myCurrentBank << 12) + address];
}
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -22,6 +22,8 @@
#include "System.hxx" #include "System.hxx"
#include "CartMC.hxx" #include "CartMC.hxx"
// TODO - properly handle read from write port functionality
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CartridgeMC::CartridgeMC(const uInt8* image, uInt32 size) CartridgeMC::CartridgeMC(const uInt8* image, uInt32 size)
: mySlot3Locked(false) : mySlot3Locked(false)
@ -46,7 +48,7 @@ void CartridgeMC::reset()
{ {
// Initialize RAM with random values // Initialize RAM with random values
for(uInt32 i = 0; i < 32768; ++i) for(uInt32 i = 0; i < 32768; ++i)
myRAM[i] = myRandGenerator.next(); myRAM[i] = mySystem->randGenerator().next();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -29,7 +29,6 @@
M6532::M6532(const Console& console) M6532::M6532(const Console& console)
: myConsole(console) : myConsole(console)
{ {
reset();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -42,11 +41,11 @@ void M6532::reset()
{ {
// Randomize the 128 bytes of memory // Randomize the 128 bytes of memory
for(uInt32 t = 0; t < 128; ++t) for(uInt32 t = 0; t < 128; ++t)
myRAM[t] = myRandGenerator.next(); myRAM[t] = mySystem->randGenerator().next();
// The timer absolutely cannot be initialized to zero; some games will // The timer absolutely cannot be initialized to zero; some games will
// loop or hang (notably Solaris and H.E.R.O.) // loop or hang (notably Solaris and H.E.R.O.)
myTimer = (0xff - (myRandGenerator.next() % 0xfe)) << 10; myTimer = (0xff - (mySystem->randGenerator().next() % 0xfe)) << 10;
myIntervalShift = 10; myIntervalShift = 10;
myCyclesWhenTimerSet = 0; myCyclesWhenTimerSet = 0;
myInterruptEnabled = false; myInterruptEnabled = false;

View File

@ -65,6 +65,7 @@
#include "Console.hxx" #include "Console.hxx"
#include "Random.hxx" #include "Random.hxx"
#include "StateManager.hxx" #include "StateManager.hxx"
#include "Version.hxx"
#include "OSystem.hxx" #include "OSystem.hxx"
@ -114,7 +115,7 @@ OSystem::OSystem()
ostringstream info; ostringstream info;
const SDL_version* ver = SDL_Linked_Version(); const SDL_version* ver = SDL_Linked_Version();
info << "Build " << "TODO" << ", using "; info << "Build " << STELLA_BUILD << ", using ";
info << "SDL " << (int)ver->major << "." << (int)ver->minor << "." << (int)ver->patch << " "; info << "SDL " << (int)ver->major << "." << (int)ver->minor << "." << (int)ver->patch << " ";
info << "[" << " " << "]"; info << "[" << " " << "]";
myBuildInfo = info.str(); myBuildInfo = info.str();

View File

@ -66,7 +66,6 @@ class Random
// Indicates the next random number // Indicates the next random number
uInt32 myValue; uInt32 myValue;
private:
// Set the OSystem we're using // Set the OSystem we're using
static const OSystem* ourSystem; static const OSystem* ourSystem;
}; };

View File

@ -169,7 +169,9 @@ void TIA::reset()
myDumpEnabled = false; myDumpEnabled = false;
myDumpDisabledCycle = 0; myDumpDisabledCycle = 0;
myFloatTIAOutputPins = mySettings.getBool("tiafloat"); // The mask indicates which pins should be driven high
// If a pin is floating (the default), then its mask value is 0
myOutputPinsMask = mySettings.getBool("tiafloat") ? 0x00 : 0x3F;
myFrameCounter = 0; myFrameCounter = 0;
myScanlineCountForLastFrame = 0; myScanlineCountForLastFrame = 0;
@ -1233,9 +1235,9 @@ uInt8 TIA::peek(uInt16 addr)
} }
// On certain CMOS EPROM chips the unused TIA pins on a read are not // On certain CMOS EPROM chips the unused TIA pins on a read are not
// floating but pulled high. Programmers might want to check their // floating but pulled high. Programmers might want to check their
// games for compatibility, so we make this optional. // games for compatibility, so we make this optional.
value |= myFloatTIAOutputPins ? (mySystem->getDataBusState() & 0x3F) : 0x3F; value |= ((mySystem->getDataBusState() | myOutputPinsMask) & 0x3F);
return value; return value;
} }

View File

@ -563,7 +563,8 @@ class TIA : public Device
bool myAllowHMOVEBlanks; bool myAllowHMOVEBlanks;
// Indicates if unused TIA pins are floating on a peek // Indicates if unused TIA pins are floating on a peek
bool myFloatTIAOutputPins; // Otherwise, they're forced high
uInt8 myOutputPinsMask;
// Bitmap of the objects that should be considered while drawing // Bitmap of the objects that should be considered while drawing
uInt8 myEnabledObjects; uInt8 myEnabledObjects;

View File

@ -22,7 +22,6 @@
Device::Device() Device::Device()
: mySystem(0) : mySystem(0)
{ {
myRandGenerator.initSeed();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -35,6 +34,3 @@ void Device::systemCyclesReset()
{ {
// By default I do nothing when my system resets its cycle counter // By default I do nothing when my system resets its cycle counter
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Random Device::myRandGenerator;

View File

@ -21,7 +21,6 @@
class System; class System;
#include "Random.hxx"
#include "Serializable.hxx" #include "Serializable.hxx"
#include "bspf.hxx" #include "bspf.hxx"
@ -47,7 +46,11 @@ class Device : public Serializable
public: public:
/** /**
Reset device to its power-on state Reset device to its power-on state.
*DO NOT* call this method until the device has been attached to
the System. In fact, it should never be necessary to call this
method directly at all.
*/ */
virtual void reset() = 0; virtual void reset() = 0;
@ -108,10 +111,6 @@ class Device : public Serializable
protected: protected:
/// Pointer to the system the device is installed in or the null pointer /// Pointer to the system the device is installed in or the null pointer
System* mySystem; System* mySystem;
/// Many devices need a source of random numbers, usually for emulating
/// unknown/undefined behaviour
static class Random myRandGenerator;
}; };
#endif #endif

View File

@ -41,6 +41,9 @@ System::System(uInt16 n, uInt16 m)
// Make sure the arguments are reasonable // Make sure the arguments are reasonable
assert((1 <= m) && (m <= n) && (n <= 16)); assert((1 <= m) && (m <= n) && (n <= 16));
// Create a new random number generator
myRandom = new Random();
// Allocate page table // Allocate page table
myPageAccessTable = new PageAccess[myNumberOfPages]; myPageAccessTable = new PageAccess[myNumberOfPages];
@ -72,6 +75,9 @@ System::~System()
// Free my page access table // Free my page access table
delete[] myPageAccessTable; delete[] myPageAccessTable;
// Free the random number generator
delete myRandom;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -28,6 +28,7 @@ class NullDevice;
#include "bspf.hxx" #include "bspf.hxx"
#include "Device.hxx" #include "Device.hxx"
#include "NullDev.hxx" #include "NullDev.hxx"
#include "Random.hxx"
#include "Serializable.hxx" #include "Serializable.hxx"
/** /**
@ -138,6 +139,16 @@ class System : public Serializable
return *myTIA; return *myTIA;
} }
/**
Answer the random generator attached to the system.
@return The random generator
*/
Random& randGenerator()
{
return *myRandom;
}
/** /**
Get the null device associated with the system. Every system Get the null device associated with the system. Every system
has a null device associated with it that's used by pages which has a null device associated with it that's used by pages which
@ -215,13 +226,35 @@ class System : public Serializable
Get the current state of the data bus in the system. The current Get the current state of the data bus in the system. The current
state is the last data that was accessed by the system. state is the last data that was accessed by the system.
@return the data bus state @return The data bus state
*/ */
inline uInt8 getDataBusState() const inline uInt8 getDataBusState() const
{ {
return myDataBusState; return myDataBusState;
} }
/**
Get the current state of the data bus in the system, taking into
account that certain bits are in Z-state (undriven). In those
cases, the bits are floating, but will usually be the same as the
last data bus value (the 'usually' is emulated by randomly driving
certain bits high).
However, some CMOS EPROM chips always drive Z-state bits high.
This is emulated by hmask, which specifies to push a specific
Z-state bit high.
@param zmask The bits which are in Z-state
@param hmask The bits which should always be driven high
@return The data bus state
*/
inline uInt8 getDataBusState(uInt8 zmask, uInt8 hmask = 0x00)
{
// For the pins that are floating, randomly decide which are high or low
// Otherwise, they're specifically driven high
return (myDataBusState | (myRandom->next() | hmask)) & zmask;
}
/** /**
Get the byte at the specified address. No masking of the Get the byte at the specified address. No masking of the
address occurs before it's sent to the device mapped at address occurs before it's sent to the device mapped at
@ -352,6 +385,10 @@ class System : public Serializable
// TIA device attached to the system or the null pointer // TIA device attached to the system or the null pointer
TIA* myTIA; TIA* myTIA;
// Many devices need a source of random numbers, usually for emulating
// unknown/undefined behaviour
Random* myRandom;
// Number of system cycles executed since the last reset // Number of system cycles executed since the last reset
uInt32 myCycles; uInt32 myCycles;