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 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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue