diff --git a/src/emucore/Cart.hxx b/src/emucore/Cart.hxx index a859631b5..93f842c21 100644 --- a/src/emucore/Cart.hxx +++ b/src/emucore/Cart.hxx @@ -315,6 +315,12 @@ class Cartridge : public Device */ 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 /** Get optional debugger widget responsible for displaying info about the cart. diff --git a/src/emucore/CartELF.cxx b/src/emucore/CartELF.cxx index 85b8d0a6b..482992e15 100644 --- a/src/emucore/CartELF.cxx +++ b/src/emucore/CartELF.cxx @@ -23,7 +23,7 @@ #include "CartELF.hxx" namespace { - constexpr size_t READ_STREAM_CAPACITY = 1024; + constexpr size_t TRANSACTION_QUEUE_CAPACITY = 16384; constexpr uInt8 OVERBLANK_PROGRAM[] = { 0xa0,0x00, // ldy #0 @@ -82,11 +82,13 @@ CartridgeELF::~CartridgeELF() {} void CartridgeELF::reset() { std::fill_n(myLastPeekResult.get(), 0x1000, 0); + myIsBusDriven = false; + myDriveBusValue = 0; - myReadStream.reset(); - myReadStream.push(0x00, 0x0ffc); - myReadStream.push(0x10); - myReadStream.setNextPushAddress(0); + myTransactionQueue.reset(); + myTransactionQueue.injectROM(0x00, 0x1ffc); + myTransactionQueue.injectROM(0x10); + myTransactionQueue.setNextPushAddress(0x1000); vcsCopyOverblankToRiotRam(); vcsStartOverblank(); @@ -121,10 +123,8 @@ bool CartridgeELF::load(Serializer& in) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt8 CartridgeELF::peek(uInt16 address) { - if (myReadStream.isYield()) return mySystem->getDataBusState(); - - myLastPeekResult[address & 0xfff] = myReadStream.pop(address); - return myLastPeekResult[address & 0xfff]; + // The actual handling happens in overdrivePeek + return 0; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -146,14 +146,41 @@ const ByteBuffer& CartridgeELF::getImage(size_t& size) const 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) { - myReadStream.push(0xa9); - myReadStream.push(value); - myReadStream.push(0x85); - myReadStream.push(zpAddress); - myReadStream.yield(); + myTransactionQueue.injectROM(0xa9); + myTransactionQueue.injectROM(value); + myTransactionQueue.injectROM(0x85); + myTransactionQueue.injectROM(zpAddress); + myTransactionQueue.yield(zpAddress); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -165,101 +192,111 @@ void CartridgeELF::vcsCopyOverblankToRiotRam() void CartridgeELF::vcsStartOverblank() { - myReadStream.push(0x4c); - myReadStream.push(0x80); - myReadStream.push(0x00); - myReadStream.yield(); + myTransactionQueue.injectROM(0x4c); + myTransactionQueue.injectROM(0x80); + myTransactionQueue.injectROM(0x00); + 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(READ_STREAM_CAPACITY); + myQueue = make_unique(TRANSACTION_QUEUE_CAPACITY); reset(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void CartridgeELF::ReadStream::reset() +void CartridgeELF::BusTransactionQueue::reset() { - myStreamNext = myStreamSize = myNextPushAddress = 0; - myIsYield = true; + myQueueNext = myQueueSize = 0; + 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) - throw FatalEmulationError("read stream pointer overflow"); - - push(value, myNextPushAddress); + injectROM(value, myNextInjectAddress); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -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"); - address &= 0xfff; - - myStream[(myStreamNext + myStreamSize++) % READ_STREAM_CAPACITY] = - {.address = address, .value = value, .yield = false}; - - myNextPushAddress = address + 1; - myIsYield = false; + myQueue[(myQueueNext + myQueueSize++) % TRANSACTION_QUEUE_CAPACITY] = transaction; } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -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; -} diff --git a/src/emucore/CartELF.hxx b/src/emucore/CartELF.hxx index 5b1d9ad7a..b771ff8a9 100644 --- a/src/emucore/CartELF.hxx +++ b/src/emucore/CartELF.hxx @@ -52,42 +52,56 @@ class CartridgeELF: public Cartridge { 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: + uInt8 driveBus(uInt16 address, uInt8 value); + void vcsWrite5(uInt8 zpAddress, uInt8 value); void vcsCopyOverblankToRiotRam(); void vcsStartOverblank(); 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; uInt8 value; bool yield; }; - class ReadStream { + class BusTransactionQueue { public: - ReadStream(); + BusTransactionQueue(); void reset(); void setNextPushAddress(uInt16 address); - void push(uInt8 value); - void push(uInt8 value, uInt16 address); + void injectROM(uInt8 value); + void injectROM(uInt8 value, uInt16 address); - void yield(); - bool isYield() const; + void yield(uInt16 address); - bool hasPendingRead() const; - uInt8 pop(uInt16 readAddress); + + bool hasPendingTransaction() const; + BusTransaction* getNextTransaction(uInt16 address); private: - unique_ptr myStream; - size_t myStreamNext{0}; - size_t myStreamSize{0}; + void push(const BusTransaction& transaction); - uInt16 myNextPushAddress{0}; + private: + unique_ptr myQueue; + size_t myQueueNext{0}; + size_t myQueueSize{0}; - bool myIsYield{true}; + uInt16 myNextInjectAddress{0}; }; private: @@ -97,7 +111,10 @@ class CartridgeELF: public Cartridge { System* mySystem{nullptr}; unique_ptr myLastPeekResult; - ReadStream myReadStream; + BusTransactionQueue myTransactionQueue; + + bool myIsBusDriven{false}; + uInt8 myDriveBusValue{0}; }; #endif // CARTRIDGE_ELF diff --git a/src/emucore/System.cxx b/src/emucore/System.cxx index 6764ed414..1557b89cf 100644 --- a/src/emucore/System.cxx +++ b/src/emucore/System.cxx @@ -40,6 +40,8 @@ System::System(Random& random, M6502& m6502, M6532& m6532, // Bus starts out unlocked (in other words, peek() changes myDataBusState) myDataBusLocked = false; + + myCartridgeDoesBusStuffing = myCart.doesBusStuffing(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -119,10 +121,12 @@ uInt8 System::peekImpl(uInt16 addr, Device::AccessFlags flags) #endif // See if this page uses direct accessing or not - const uInt8 result = access.directPeekBase + uInt8 result = access.directPeekBase ? *(access.directPeekBase + (addr & PAGE_MASK)) : (oob ? access.device->peekOob(addr) : access.device->peek(addr)); + if (!oob && myCartridgeDoesBusStuffing) result = myCart.overdrivePeek(addr, result); + #ifdef DEBUGGER_SUPPORT if(!myDataBusLocked) #endif @@ -141,6 +145,8 @@ uInt8 System::peekImpl(uInt16 addr, Device::AccessFlags flags); template 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 PageAccess& access = myPageAccessTable[page]; diff --git a/src/emucore/System.hxx b/src/emucore/System.hxx index f58f2077c..81afa1a75 100644 --- a/src/emucore/System.hxx +++ b/src/emucore/System.hxx @@ -445,6 +445,8 @@ class System : public Serializable // Some parts of the codebase need to act differently in such a case bool mySystemInAutodetect{false}; + bool myCartridgeDoesBusStuffing{false}; + private: // Following constructors and assignment operators not supported System() = delete;