mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-27 06:35:04 +00:00
Expands and documents MSX::MemoryMap and MSX::ROMSlotHandler.
Hopefully to cover all intended use cases.
This commit is contained in:
parent
ed564cb810
commit
185cd3c123
@ -163,17 +163,17 @@ class ConcreteMachine:
|
||||
switch(target.msx.cartridge_type) {
|
||||
default: break;
|
||||
case StaticAnalyser::MSXCartridgeType::Konami:
|
||||
memory_slots_[1].handler.reset(new Cartridge::KonamiROMSlotHandler(*this, 1));
|
||||
memory_slots_[1].set_handler(new Cartridge::KonamiROMSlotHandler(*this, 1));
|
||||
break;
|
||||
case StaticAnalyser::MSXCartridgeType::KonamiWithSCC:
|
||||
// TODO: enable an SCC.
|
||||
memory_slots_[1].handler.reset(new Cartridge::KonamiWithSCCROMSlotHandler(*this, 1));
|
||||
memory_slots_[1].set_handler(new Cartridge::KonamiWithSCCROMSlotHandler(*this, 1));
|
||||
break;
|
||||
case StaticAnalyser::MSXCartridgeType::ASCII8kb:
|
||||
memory_slots_[1].handler.reset(new Cartridge::ASCII8kbROMSlotHandler(*this, 1));
|
||||
memory_slots_[1].set_handler(new Cartridge::ASCII8kbROMSlotHandler(*this, 1));
|
||||
break;
|
||||
case StaticAnalyser::MSXCartridgeType::ASCII16kb:
|
||||
memory_slots_[1].handler.reset(new Cartridge::ASCII16kbROMSlotHandler(*this, 1));
|
||||
memory_slots_[1].set_handler(new Cartridge::ASCII16kbROMSlotHandler(*this, 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -196,20 +196,35 @@ class ConcreteMachine:
|
||||
input_text_ += string;
|
||||
}
|
||||
|
||||
// MARK: MSX::MemoryMap
|
||||
void map(int slot, std::size_t source_address, uint16_t destination_address, std::size_t length) override {
|
||||
// TODO: deal with out-of-bounds pages.
|
||||
assert(!(destination_address & 8191));
|
||||
assert(!(length & 8191));
|
||||
assert(static_cast<std::size_t>(destination_address) + length <= 65536);
|
||||
|
||||
for(std::size_t c = 0; c < (length >> 13); ++c) {
|
||||
memory_slots_[slot].read_pointers[(destination_address >> 13) + c] = &memory_slots_[slot].source[source_address];
|
||||
if(memory_slots_[slot].wrapping_strategy == ROMSlotHandler::WrappingStrategy::Repeat) source_address %= memory_slots_[slot].source.size();
|
||||
memory_slots_[slot].read_pointers[(destination_address >> 13) + c] =
|
||||
(source_address < memory_slots_[slot].source.size()) ? &memory_slots_[slot].source[source_address] : unpopulated_;
|
||||
source_address += 8192;
|
||||
}
|
||||
|
||||
page_memory(paged_memory_);
|
||||
}
|
||||
|
||||
void unmap(int slot, uint16_t destination_address, std::size_t length) override {
|
||||
assert(!(destination_address & 8191));
|
||||
assert(!(length & 8191));
|
||||
assert(static_cast<std::size_t>(destination_address) + length <= 65536);
|
||||
|
||||
for(std::size_t c = 0; c < (length >> 13); ++c) {
|
||||
memory_slots_[slot].read_pointers[(destination_address >> 13) + c] = nullptr;
|
||||
}
|
||||
|
||||
page_memory(paged_memory_);
|
||||
}
|
||||
|
||||
// MARK: Ordinary paging.
|
||||
void page_memory(uint8_t value) {
|
||||
paged_memory_ = value;
|
||||
for(std::size_t c = 0; c < 8; c += 2) {
|
||||
@ -221,6 +236,7 @@ class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Z80::BusHandler
|
||||
HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
|
||||
if(time_until_interrupt_ > 0) {
|
||||
time_until_interrupt_ -= cycle.length;
|
||||
@ -287,15 +303,23 @@ class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
case CPU::Z80::PartialMachineCycle::Read:
|
||||
*cycle.value = read_pointers_[address >> 13][address & 8191];
|
||||
if(read_pointers_[address >> 13]) {
|
||||
*cycle.value = read_pointers_[address >> 13][address & 8191];
|
||||
} else {
|
||||
int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3;
|
||||
memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush());
|
||||
*cycle.value = memory_slots_[slot_hit].handler->read(address);
|
||||
}
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Write: {
|
||||
write_pointers_[address >> 13][address & 8191] = *cycle.value;
|
||||
|
||||
int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3;
|
||||
if(memory_slots_[slot_hit].handler)
|
||||
if(memory_slots_[slot_hit].handler) {
|
||||
memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush());
|
||||
memory_slots_[slot_hit].handler->write(address, *cycle.value);
|
||||
}
|
||||
} break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Input:
|
||||
@ -394,6 +418,10 @@ class ConcreteMachine:
|
||||
HalfCycles addition((cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) ? 2 : 0);
|
||||
time_since_vdp_update_ += cycle.length + addition;
|
||||
time_since_ay_update_ += cycle.length + addition;
|
||||
memory_slots_[0].cycles_since_update += cycle.length + addition;
|
||||
memory_slots_[1].cycles_since_update += cycle.length + addition;
|
||||
memory_slots_[2].cycles_since_update += cycle.length + addition;
|
||||
memory_slots_[3].cycles_since_update += cycle.length + addition;
|
||||
return addition;
|
||||
}
|
||||
|
||||
@ -557,8 +585,15 @@ class ConcreteMachine:
|
||||
uint8_t *read_pointers[8];
|
||||
uint8_t *write_pointers[8];
|
||||
|
||||
void set_handler(ROMSlotHandler *slot_handler) {
|
||||
handler.reset(slot_handler);
|
||||
wrapping_strategy = handler->wrapping_strategy();
|
||||
}
|
||||
|
||||
std::unique_ptr<ROMSlotHandler> handler;
|
||||
std::vector<uint8_t> source;
|
||||
HalfCycles cycles_since_update;
|
||||
ROMSlotHandler::WrappingStrategy wrapping_strategy = ROMSlotHandler::WrappingStrategy::Repeat;
|
||||
} memory_slots_[4];
|
||||
|
||||
uint8_t ram_[65536];
|
||||
|
@ -15,20 +15,54 @@
|
||||
#include <cstdint>
|
||||
|
||||
/*
|
||||
Design assumption in this file: to-ROM writes and paging events are 'rare',
|
||||
so virtual call costs aren't worrisome.
|
||||
Design assumptions:
|
||||
|
||||
- to-ROM writes and paging events are 'rare', so virtual call costs aren't worrisome;
|
||||
- ROM type variety is sufficiently slender that most of it can be built into the MSX.
|
||||
|
||||
Part of the motivation is also that the MSX has four logical slots, the ROM, RAM plus two
|
||||
things plugged in. So even if the base class were templated to remove the virtual call,
|
||||
there'd just be a switch on what to call.
|
||||
*/
|
||||
namespace MSX {
|
||||
|
||||
class MemoryMap {
|
||||
public:
|
||||
/*!
|
||||
Maps source data from the ROM's source to the given address range.
|
||||
*/
|
||||
virtual void map(int slot, std::size_t source_address, uint16_t destination_address, std::size_t length) = 0;
|
||||
|
||||
/*!
|
||||
Unmaps source data from the given address range; the slot handler's read function will be used
|
||||
to respond to queries in that range.
|
||||
*/
|
||||
virtual void unmap(int slot, uint16_t destination_address, std::size_t length) = 0;
|
||||
};
|
||||
|
||||
class ROMSlotHandler {
|
||||
public:
|
||||
/*! Advances time by @c half_cycles. */
|
||||
virtual void run_for(HalfCycles half_cycles) {}
|
||||
|
||||
/*! Announces an attempt to write @c value to @c address. */
|
||||
virtual void write(uint16_t address, uint8_t value) = 0;
|
||||
|
||||
/*! Seeks the result of a read at @c address; this is used only if the area is unmapped. */
|
||||
virtual uint8_t read(uint16_t address) { return 0xff; }
|
||||
|
||||
enum class WrappingStrategy {
|
||||
/// Repeat causes all accesses to be modulo the size of the ROM.
|
||||
Repeat,
|
||||
/// Empty causes all out-of-bounds accesses to read a vacant bus.
|
||||
Empty
|
||||
};
|
||||
/*!
|
||||
Returns the wrapping strategy to apply to mapping requests from this ROM slot.
|
||||
*/
|
||||
virtual WrappingStrategy wrapping_strategy() const {
|
||||
return WrappingStrategy::Repeat;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user