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

View File

@ -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<ScheduledRead[]>(READ_STREAM_CAPACITY);
myQueue = make_unique<BusTransaction[]>(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;
}

View File

@ -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<ScheduledRead[]> myStream;
size_t myStreamNext{0};
size_t myStreamSize{0};
void push(const BusTransaction& transaction);
uInt16 myNextPushAddress{0};
private:
unique_ptr<BusTransaction[]> 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<uint8_t[]> myLastPeekResult;
ReadStream myReadStream;
BusTransactionQueue myTransactionQueue;
bool myIsBusDriven{false};
uInt8 myDriveBusValue{0};
};
#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)
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<false>(uInt16 addr, Device::AccessFlags flags);
template<bool oob>
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];

View File

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