Refactoring, properly model bus activity.

This commit is contained in:
Christian Speckner 2024-07-01 00:08:36 +02:00
parent fe6e67de32
commit 4e6d009435
5 changed files with 170 additions and 102 deletions

View File

@ -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.

View File

@ -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;
}

View File

@ -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

View File

@ -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];

View File

@ -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;