mirror of https://github.com/stella-emu/stella.git
Refactoring, properly model bus activity.
This commit is contained in:
parent
fe6e67de32
commit
4e6d009435
|
@ -315,6 +315,12 @@ class Cartridge : public Device
|
||||||
*/
|
*/
|
||||||
virtual uInt32 thumbCallback(uInt8 function, uInt32 value1, uInt32 value2) { return 0; }
|
virtual uInt32 thumbCallback(uInt8 function, uInt32 value1, uInt32 value2) { return 0; }
|
||||||
|
|
||||||
|
virtual uInt8 overdrivePeek(uInt16 address, uInt8 value) { return value; }
|
||||||
|
|
||||||
|
virtual uInt8 overdrivePoke(uInt16 address, uInt8 value) { return value; }
|
||||||
|
|
||||||
|
virtual bool doesBusStuffing() { return false; }
|
||||||
|
|
||||||
#ifdef DEBUGGER_SUPPORT
|
#ifdef DEBUGGER_SUPPORT
|
||||||
/**
|
/**
|
||||||
Get optional debugger widget responsible for displaying info about the cart.
|
Get optional debugger widget responsible for displaying info about the cart.
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
#include "CartELF.hxx"
|
#include "CartELF.hxx"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr size_t READ_STREAM_CAPACITY = 1024;
|
constexpr size_t TRANSACTION_QUEUE_CAPACITY = 16384;
|
||||||
|
|
||||||
constexpr uInt8 OVERBLANK_PROGRAM[] = {
|
constexpr uInt8 OVERBLANK_PROGRAM[] = {
|
||||||
0xa0,0x00, // ldy #0
|
0xa0,0x00, // ldy #0
|
||||||
|
@ -82,11 +82,13 @@ CartridgeELF::~CartridgeELF() {}
|
||||||
void CartridgeELF::reset()
|
void CartridgeELF::reset()
|
||||||
{
|
{
|
||||||
std::fill_n(myLastPeekResult.get(), 0x1000, 0);
|
std::fill_n(myLastPeekResult.get(), 0x1000, 0);
|
||||||
|
myIsBusDriven = false;
|
||||||
|
myDriveBusValue = 0;
|
||||||
|
|
||||||
myReadStream.reset();
|
myTransactionQueue.reset();
|
||||||
myReadStream.push(0x00, 0x0ffc);
|
myTransactionQueue.injectROM(0x00, 0x1ffc);
|
||||||
myReadStream.push(0x10);
|
myTransactionQueue.injectROM(0x10);
|
||||||
myReadStream.setNextPushAddress(0);
|
myTransactionQueue.setNextPushAddress(0x1000);
|
||||||
|
|
||||||
vcsCopyOverblankToRiotRam();
|
vcsCopyOverblankToRiotRam();
|
||||||
vcsStartOverblank();
|
vcsStartOverblank();
|
||||||
|
@ -121,10 +123,8 @@ bool CartridgeELF::load(Serializer& in)
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
uInt8 CartridgeELF::peek(uInt16 address)
|
uInt8 CartridgeELF::peek(uInt16 address)
|
||||||
{
|
{
|
||||||
if (myReadStream.isYield()) return mySystem->getDataBusState();
|
// The actual handling happens in overdrivePeek
|
||||||
|
return 0;
|
||||||
myLastPeekResult[address & 0xfff] = myReadStream.pop(address);
|
|
||||||
return myLastPeekResult[address & 0xfff];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
@ -146,14 +146,41 @@ const ByteBuffer& CartridgeELF::getImage(size_t& size) const
|
||||||
return myImage;
|
return myImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uInt8 CartridgeELF::overdrivePeek(uInt16 address, uInt8 value)
|
||||||
|
{
|
||||||
|
value = driveBus(address, value);
|
||||||
|
|
||||||
|
if (address & 0x1000) {
|
||||||
|
if (!myIsBusDriven) value = mySystem->getDataBusState();
|
||||||
|
myLastPeekResult[address & 0xfff] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
uInt8 CartridgeELF::overdrivePoke(uInt16 address, uInt8 value)
|
||||||
|
{
|
||||||
|
return driveBus(address, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uInt8 CartridgeELF::driveBus(uInt16 address, uInt8 value)
|
||||||
|
{
|
||||||
|
BusTransaction* nextTransaction = myTransactionQueue.getNextTransaction(address);
|
||||||
|
if (nextTransaction) nextTransaction->setBusState(myIsBusDriven, myDriveBusValue);
|
||||||
|
|
||||||
|
if (myIsBusDriven) value |= myDriveBusValue;
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
void CartridgeELF::vcsWrite5(uInt8 zpAddress, uInt8 value)
|
void CartridgeELF::vcsWrite5(uInt8 zpAddress, uInt8 value)
|
||||||
{
|
{
|
||||||
myReadStream.push(0xa9);
|
myTransactionQueue.injectROM(0xa9);
|
||||||
myReadStream.push(value);
|
myTransactionQueue.injectROM(value);
|
||||||
myReadStream.push(0x85);
|
myTransactionQueue.injectROM(0x85);
|
||||||
myReadStream.push(zpAddress);
|
myTransactionQueue.injectROM(zpAddress);
|
||||||
myReadStream.yield();
|
myTransactionQueue.yield(zpAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
@ -165,101 +192,111 @@ void CartridgeELF::vcsCopyOverblankToRiotRam()
|
||||||
|
|
||||||
void CartridgeELF::vcsStartOverblank()
|
void CartridgeELF::vcsStartOverblank()
|
||||||
{
|
{
|
||||||
myReadStream.push(0x4c);
|
myTransactionQueue.injectROM(0x4c);
|
||||||
myReadStream.push(0x80);
|
myTransactionQueue.injectROM(0x80);
|
||||||
myReadStream.push(0x00);
|
myTransactionQueue.injectROM(0x00);
|
||||||
myReadStream.yield();
|
myTransactionQueue.yield(0x0080);
|
||||||
|
}
|
||||||
|
|
||||||
|
CartridgeELF::BusTransaction CartridgeELF::BusTransaction::transactionYield(uInt16 address)
|
||||||
|
{
|
||||||
|
address &= 0x1fff;
|
||||||
|
return {.address = address, .value = 0, .yield = true};
|
||||||
|
}
|
||||||
|
|
||||||
|
CartridgeELF::BusTransaction CartridgeELF::BusTransaction::transactionDrive(uInt16 address, uInt8 value)
|
||||||
|
{
|
||||||
|
address &= 0x1fff;
|
||||||
|
return {.address = address, .value = value, .yield = false};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void CartridgeELF::BusTransaction::setBusState(bool& drive, uInt8& value)
|
||||||
|
{
|
||||||
|
if (yield) {
|
||||||
|
drive = false;
|
||||||
|
} else {
|
||||||
|
drive = true;
|
||||||
|
value = this->value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
CartridgeELF::ReadStream::ReadStream()
|
CartridgeELF::BusTransactionQueue::BusTransactionQueue()
|
||||||
{
|
{
|
||||||
myStream = make_unique<ScheduledRead[]>(READ_STREAM_CAPACITY);
|
myQueue = make_unique<BusTransaction[]>(TRANSACTION_QUEUE_CAPACITY);
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
void CartridgeELF::ReadStream::reset()
|
void CartridgeELF::BusTransactionQueue::reset()
|
||||||
{
|
{
|
||||||
myStreamNext = myStreamSize = myNextPushAddress = 0;
|
myQueueNext = myQueueSize = 0;
|
||||||
myIsYield = true;
|
myNextInjectAddress = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
void CartridgeELF::ReadStream::setNextPushAddress(uInt16 address)
|
void CartridgeELF::BusTransactionQueue::setNextPushAddress(uInt16 address)
|
||||||
{
|
{
|
||||||
myNextPushAddress = address;
|
myNextInjectAddress = address;
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
void CartridgeELF::ReadStream::push(uInt8 value)
|
void CartridgeELF::BusTransactionQueue::injectROM(uInt8 value)
|
||||||
{
|
{
|
||||||
if (myNextPushAddress > 0xfff)
|
injectROM(value, myNextInjectAddress);
|
||||||
throw FatalEmulationError("read stream pointer overflow");
|
|
||||||
|
|
||||||
push(value, myNextPushAddress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
void CartridgeELF::ReadStream::push(uInt8 value, uInt16 address)
|
void CartridgeELF::BusTransactionQueue::injectROM(uInt8 value, uInt16 address)
|
||||||
{
|
{
|
||||||
if (myStreamSize == READ_STREAM_CAPACITY)
|
push(BusTransaction::transactionDrive(address, value));
|
||||||
|
myNextInjectAddress = address + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void CartridgeELF::BusTransactionQueue::yield(uInt16 address)
|
||||||
|
{
|
||||||
|
push(BusTransaction::transactionYield(address));
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
bool CartridgeELF::BusTransactionQueue::hasPendingTransaction() const
|
||||||
|
{
|
||||||
|
return myQueueSize > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
CartridgeELF::BusTransaction* CartridgeELF::BusTransactionQueue::getNextTransaction(uInt16 address)
|
||||||
|
{
|
||||||
|
if (myQueueSize == 0) return nullptr;
|
||||||
|
|
||||||
|
BusTransaction* nextTransaction = &myQueue[myQueueNext];
|
||||||
|
if (nextTransaction->address != (address & 0x1fff)) return nullptr;
|
||||||
|
|
||||||
|
myQueueNext = (myQueueNext + 1) % TRANSACTION_QUEUE_CAPACITY;
|
||||||
|
myQueueSize--;
|
||||||
|
|
||||||
|
return nextTransaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void CartridgeELF::BusTransactionQueue::push(const BusTransaction& transaction)
|
||||||
|
{
|
||||||
|
if (myQueueSize > 0) {
|
||||||
|
BusTransaction& lastTransaction = myQueue[(myQueueNext + myQueueSize - 1) % TRANSACTION_QUEUE_CAPACITY];
|
||||||
|
|
||||||
|
if (lastTransaction.address == transaction.address) {
|
||||||
|
lastTransaction = transaction;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (myQueueSize == TRANSACTION_QUEUE_CAPACITY)
|
||||||
throw FatalEmulationError("read stream overflow");
|
throw FatalEmulationError("read stream overflow");
|
||||||
|
|
||||||
address &= 0xfff;
|
myQueue[(myQueueNext + myQueueSize++) % TRANSACTION_QUEUE_CAPACITY] = transaction;
|
||||||
|
|
||||||
myStream[(myStreamNext + myStreamSize++) % READ_STREAM_CAPACITY] =
|
|
||||||
{.address = address, .value = value, .yield = false};
|
|
||||||
|
|
||||||
myNextPushAddress = address + 1;
|
|
||||||
myIsYield = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
void CartridgeELF::ReadStream::yield()
|
|
||||||
{
|
|
||||||
if (myStreamSize == 0)
|
|
||||||
throw new FatalEmulationError("yield called on empty stream");
|
|
||||||
|
|
||||||
myStream[(myStreamNext + myStreamSize - 1) % READ_STREAM_CAPACITY].yield = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
bool CartridgeELF::ReadStream::isYield() const
|
|
||||||
{
|
|
||||||
return myIsYield;
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
bool CartridgeELF::ReadStream::hasPendingRead() const
|
|
||||||
{
|
|
||||||
return myStreamSize > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
uInt8 CartridgeELF::ReadStream::pop(uInt16 readAddress)
|
|
||||||
{
|
|
||||||
if (myStreamSize == 0) {
|
|
||||||
ostringstream s;
|
|
||||||
s << "read stream underflow at 0x" << std::hex << std::setw(4) << readAddress;
|
|
||||||
throw FatalEmulationError(s.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((readAddress & 0xfff) != myStream[myStreamNext].address)
|
|
||||||
{
|
|
||||||
ostringstream s;
|
|
||||||
s <<
|
|
||||||
"unexcpected cartridge read from 0x" << std::hex << std::setw(4) <<
|
|
||||||
std::setfill('0') << (readAddress & 0xfff) << " expected 0x" << myStream[myStreamNext].address;
|
|
||||||
|
|
||||||
throw FatalEmulationError(s.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
myIsYield = myStream[myStreamNext].yield && myStreamSize == 1;
|
|
||||||
const uInt8 value = myStream[myStreamNext++].value;
|
|
||||||
|
|
||||||
myStreamNext %= READ_STREAM_CAPACITY;
|
|
||||||
myStreamSize--;
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
|
@ -52,42 +52,56 @@ class CartridgeELF: public Cartridge {
|
||||||
|
|
||||||
string name() const override { return "CartridgeELF"; };
|
string name() const override { return "CartridgeELF"; };
|
||||||
|
|
||||||
|
uInt8 overdrivePeek(uInt16 address, uInt8 value) override;
|
||||||
|
|
||||||
|
uInt8 overdrivePoke(uInt16 address, uInt8 value) override;
|
||||||
|
|
||||||
|
bool doesBusStuffing() override { return true; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
uInt8 driveBus(uInt16 address, uInt8 value);
|
||||||
|
|
||||||
void vcsWrite5(uInt8 zpAddress, uInt8 value);
|
void vcsWrite5(uInt8 zpAddress, uInt8 value);
|
||||||
void vcsCopyOverblankToRiotRam();
|
void vcsCopyOverblankToRiotRam();
|
||||||
void vcsStartOverblank();
|
void vcsStartOverblank();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct ScheduledRead {
|
struct BusTransaction {
|
||||||
|
static BusTransaction transactionYield(uInt16 address);
|
||||||
|
static BusTransaction transactionDrive(uInt16 address, uInt8 value);
|
||||||
|
|
||||||
|
void setBusState(bool& drive, uInt8& value);
|
||||||
|
|
||||||
uInt16 address;
|
uInt16 address;
|
||||||
uInt8 value;
|
uInt8 value;
|
||||||
bool yield;
|
bool yield;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ReadStream {
|
class BusTransactionQueue {
|
||||||
public:
|
public:
|
||||||
ReadStream();
|
BusTransactionQueue();
|
||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
void setNextPushAddress(uInt16 address);
|
void setNextPushAddress(uInt16 address);
|
||||||
void push(uInt8 value);
|
void injectROM(uInt8 value);
|
||||||
void push(uInt8 value, uInt16 address);
|
void injectROM(uInt8 value, uInt16 address);
|
||||||
|
|
||||||
void yield();
|
void yield(uInt16 address);
|
||||||
bool isYield() const;
|
|
||||||
|
|
||||||
bool hasPendingRead() const;
|
|
||||||
uInt8 pop(uInt16 readAddress);
|
bool hasPendingTransaction() const;
|
||||||
|
BusTransaction* getNextTransaction(uInt16 address);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
unique_ptr<ScheduledRead[]> myStream;
|
void push(const BusTransaction& transaction);
|
||||||
size_t myStreamNext{0};
|
|
||||||
size_t myStreamSize{0};
|
|
||||||
|
|
||||||
uInt16 myNextPushAddress{0};
|
private:
|
||||||
|
unique_ptr<BusTransaction[]> myQueue;
|
||||||
|
size_t myQueueNext{0};
|
||||||
|
size_t myQueueSize{0};
|
||||||
|
|
||||||
bool myIsYield{true};
|
uInt16 myNextInjectAddress{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -97,7 +111,10 @@ class CartridgeELF: public Cartridge {
|
||||||
System* mySystem{nullptr};
|
System* mySystem{nullptr};
|
||||||
|
|
||||||
unique_ptr<uint8_t[]> myLastPeekResult;
|
unique_ptr<uint8_t[]> myLastPeekResult;
|
||||||
ReadStream myReadStream;
|
BusTransactionQueue myTransactionQueue;
|
||||||
|
|
||||||
|
bool myIsBusDriven{false};
|
||||||
|
uInt8 myDriveBusValue{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CARTRIDGE_ELF
|
#endif // CARTRIDGE_ELF
|
||||||
|
|
|
@ -40,6 +40,8 @@ System::System(Random& random, M6502& m6502, M6532& m6532,
|
||||||
|
|
||||||
// Bus starts out unlocked (in other words, peek() changes myDataBusState)
|
// Bus starts out unlocked (in other words, peek() changes myDataBusState)
|
||||||
myDataBusLocked = false;
|
myDataBusLocked = false;
|
||||||
|
|
||||||
|
myCartridgeDoesBusStuffing = myCart.doesBusStuffing();
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
@ -119,10 +121,12 @@ uInt8 System::peekImpl(uInt16 addr, Device::AccessFlags flags)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// See if this page uses direct accessing or not
|
// See if this page uses direct accessing or not
|
||||||
const uInt8 result = access.directPeekBase
|
uInt8 result = access.directPeekBase
|
||||||
? *(access.directPeekBase + (addr & PAGE_MASK))
|
? *(access.directPeekBase + (addr & PAGE_MASK))
|
||||||
: (oob ? access.device->peekOob(addr) : access.device->peek(addr));
|
: (oob ? access.device->peekOob(addr) : access.device->peek(addr));
|
||||||
|
|
||||||
|
if (!oob && myCartridgeDoesBusStuffing) result = myCart.overdrivePeek(addr, result);
|
||||||
|
|
||||||
#ifdef DEBUGGER_SUPPORT
|
#ifdef DEBUGGER_SUPPORT
|
||||||
if(!myDataBusLocked)
|
if(!myDataBusLocked)
|
||||||
#endif
|
#endif
|
||||||
|
@ -141,6 +145,8 @@ uInt8 System::peekImpl<false>(uInt16 addr, Device::AccessFlags flags);
|
||||||
template<bool oob>
|
template<bool oob>
|
||||||
void System::pokeImpl(uInt16 addr, uInt8 value, Device::AccessFlags flags)
|
void System::pokeImpl(uInt16 addr, uInt8 value, Device::AccessFlags flags)
|
||||||
{
|
{
|
||||||
|
if (!oob && myCartridgeDoesBusStuffing) value = myCart.overdrivePoke(addr, value);
|
||||||
|
|
||||||
const uInt16 page = (addr & ADDRESS_MASK) >> PAGE_SHIFT;
|
const uInt16 page = (addr & ADDRESS_MASK) >> PAGE_SHIFT;
|
||||||
const PageAccess& access = myPageAccessTable[page];
|
const PageAccess& access = myPageAccessTable[page];
|
||||||
|
|
||||||
|
|
|
@ -445,6 +445,8 @@ class System : public Serializable
|
||||||
// Some parts of the codebase need to act differently in such a case
|
// Some parts of the codebase need to act differently in such a case
|
||||||
bool mySystemInAutodetect{false};
|
bool mySystemInAutodetect{false};
|
||||||
|
|
||||||
|
bool myCartridgeDoesBusStuffing{false};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Following constructors and assignment operators not supported
|
// Following constructors and assignment operators not supported
|
||||||
System() = delete;
|
System() = delete;
|
||||||
|
|
Loading…
Reference in New Issue