From 73549eb38c5834fe15d1ecfef4590a4416ab8d13 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 10 Jan 2023 14:40:03 -0500 Subject: [PATCH 01/34] Document quite a bit more, to refresh my memory. --- Machines/MSX/MSX.cpp | 44 +++++++++++++++++++++++++++++---- Machines/MSX/ROMSlotHandler.hpp | 4 +-- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 6bc06bda0..fc47cad21 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -384,7 +384,9 @@ class ConcreteMachine: assert(size_t(destination_address) + length <= 65536); for(std::size_t c = 0; c < (length >> 13); ++c) { - if(memory_slots_[slot].wrapping_strategy == ROMSlotHandler::WrappingStrategy::Repeat) source_address %= memory_slots_[slot].source.size(); + 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; @@ -764,27 +766,59 @@ class ConcreteMachine: i8255PortHandler i8255_port_handler_; AYPortHandler ay_port_handler_; + /// The current primary slot selection; retains whatever value was written + /// last to the 8255 PPI via port A8. uint8_t paged_memory_ = 0; + + // Divides the current 64kb address space into 8kb chunks. + // 8kb resolution is used by some cartride titles. uint8_t *read_pointers_[8]; uint8_t *write_pointers_[8]; - struct MemorySlots { + /// Optionally attaches non-default logic to any of the four things selectable + /// via the primary slot register. + /// + /// Tracks secondary and game-specific paging within each slot. + /// + /// Standard MSX assignments: + /// 0 = ROM; + /// 3 = RAM. + /// + /// Additional assignments customarily used by this emulator: + /// 1 = any inserted cartridge; + /// 2 = the disk ROM, if present. + struct MemorySlot { + // Each slot may be visible for any number of quarters of the memory map; + // these are the read and write pointers that should appear in the main + // memory map if this slot is visible there. uint8_t *read_pointers[8]; uint8_t *write_pointers[8]; + /// Sets the handler that will be provided with @c run_for and @c read or @c write calls + /// for memory accesses **anywhere it has left memory unmapped**. Anywhere that can be + /// described just in terms of standard memory access read and write destinations should be, + /// ideally to avoid virtual dispatch. void set_handler(ROMSlotHandler *slot_handler) { handler.reset(slot_handler); wrapping_strategy = handler->wrapping_strategy(); } - std::unique_ptr handler; - std::vector source; HalfCycles cycles_since_update; + std::unique_ptr handler; ROMSlotHandler::WrappingStrategy wrapping_strategy = ROMSlotHandler::WrappingStrategy::Repeat; - } memory_slots_[4]; + /// Per-slot storage, as a convenience. + std::vector source; + }; + MemorySlot memory_slots_[4]; + + /// Base RAM. uint8_t ram_[65536]; + + /// A never-read area that writes for unmapped regions can be diverted to. uint8_t scratch_[8192]; + + /// A never-written area that reads for unmapped regions can be sourced from. uint8_t unpopulated_[8192]; HalfCycles time_since_ay_update_; diff --git a/Machines/MSX/ROMSlotHandler.hpp b/Machines/MSX/ROMSlotHandler.hpp index f8c5d6f76..ab29b404e 100644 --- a/Machines/MSX/ROMSlotHandler.hpp +++ b/Machines/MSX/ROMSlotHandler.hpp @@ -56,9 +56,9 @@ class ROMSlotHandler { virtual uint8_t read([[maybe_unused]] uint16_t address) { return 0xff; } enum class WrappingStrategy { - /// Repeat causes all accesses to be modulo the size of the ROM. + /// All accesses are modulo the size of the ROM. Repeat, - /// Empty causes all out-of-bounds accesses to read a vacant bus. + /// Out-of-bounds accesses read a vacant bus. Empty }; From 6e0f260478982a378f7b1ce7e21ba4a6142d9905 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 10 Jan 2023 14:52:09 -0500 Subject: [PATCH 02/34] Add a model field. --- Analyser/Static/MSX/Target.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Analyser/Static/MSX/Target.hpp b/Analyser/Static/MSX/Target.hpp index f0d7a9f6a..06e590f41 100644 --- a/Analyser/Static/MSX/Target.hpp +++ b/Analyser/Static/MSX/Target.hpp @@ -22,6 +22,12 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl< bool has_disk_drive = false; std::string loading_command; + ReflectableEnum(Model, + MSX1, + MSX2 + ); + Model model = Model::MSX1; + ReflectableEnum(Region, Japan, USA, @@ -34,6 +40,8 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl< DeclareField(has_disk_drive); DeclareField(region); AnnounceEnum(Region); + DeclareField(model); + AnnounceEnum(Model); } } }; From 53bb17c848a0d2ca1fa9fb4930f804a282a9b846 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 10 Jan 2023 14:55:57 -0500 Subject: [PATCH 03/34] Use model as a compile-time MSX configurator. --- Machines/MSX/MSX.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index fc47cad21..cc45ac21f 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -128,6 +128,9 @@ class AYPortHandler: public GI::AY38910::PortHandler { }; }; +using Target = Analyser::Static::MSX::Target; + +template class ConcreteMachine: public Machine, public CPU::Z80::BusHandler, @@ -142,8 +145,6 @@ class ConcreteMachine: public ClockingHint::Observer, public Activity::Source { public: - using Target = Analyser::Static::MSX::Target; - ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher): z80_(*this), i8255_(i8255_port_handler_), @@ -508,7 +509,7 @@ class ConcreteMachine: *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()); + memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.template flush()); *cycle.value = memory_slots_[slot_hit].handler->read(address); } break; @@ -519,7 +520,7 @@ class ConcreteMachine: int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3; if(memory_slots_[slot_hit].handler) { update_audio(); - memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush()); + memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.template flush()); memory_slots_[slot_hit].handler->write(address, *cycle.value, read_pointers_[pc_address_ >> 13] != memory_slots_[0].read_pointers[pc_address_ >> 13]); } } break; @@ -839,9 +840,12 @@ class ConcreteMachine: using namespace MSX; Machine *Machine::MSX(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { - using Target = Analyser::Static::MSX::Target; - const Target *const msx_target = dynamic_cast(target); - return new ConcreteMachine(*msx_target, rom_fetcher); + const auto msx_target = dynamic_cast(target); + switch(msx_target->model) { + default: return nullptr; + case Target::Model::MSX1: return new ConcreteMachine(*msx_target, rom_fetcher); + case Target::Model::MSX2: return new ConcreteMachine(*msx_target, rom_fetcher); + } } Machine::~Machine() {} From 6bd261b2224394afadc6d96b908e7d5a1044c3ff Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 10 Jan 2023 18:07:31 -0500 Subject: [PATCH 04/34] Add storage for secondary paging. --- Machines/MSX/MSX.cpp | 52 +++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index cc45ac21f..e1186ff3b 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -253,7 +253,7 @@ class ConcreteMachine: } map(0, 0, 0, 32768); - page_memory(0); + page_primary(0); // Add a disk cartridge if any disks were supplied. if(target.has_disk_drive) { @@ -393,7 +393,7 @@ class ConcreteMachine: source_address += 8192; } - page_memory(paged_memory_); + update_paging(); } void unmap(int slot, uint16_t destination_address, std::size_t length) final { @@ -405,18 +405,31 @@ class ConcreteMachine: memory_slots_[slot].read_pointers[(destination_address >> 13) + c] = nullptr; } - page_memory(paged_memory_); + update_paging(); } - // MARK: Ordinary paging. - void page_memory(uint8_t value) { - paged_memory_ = value; + // MARK: Memory paging. + void page_primary(uint8_t value) { + paging_.primary = value; + update_paging(); + } + + void page_secondary(uint8_t value) { + paging_.secondary = value; + update_paging(); + } + + void update_paging() { + uint8_t primary = paging_.primary; + for(std::size_t c = 0; c < 8; c += 2) { - read_pointers_[c] = memory_slots_[value & 3].read_pointers[c]; - write_pointers_[c] = memory_slots_[value & 3].write_pointers[c]; - read_pointers_[c+1] = memory_slots_[value & 3].read_pointers[c+1]; - write_pointers_[c+1] = memory_slots_[value & 3].write_pointers[c+1]; - value >>= 2; + const MemorySlot &slot = memory_slots_[primary & 3]; + primary >>= 2; + + read_pointers_[c] = slot.read_pointers[c]; + write_pointers_[c] = slot.write_pointers[c]; + read_pointers_[c+1] = slot.read_pointers[c+1]; + write_pointers_[c+1] = slot.write_pointers[c+1]; } set_use_fast_tape(); } @@ -508,7 +521,7 @@ class ConcreteMachine: if(read_pointers_[address >> 13]) { *cycle.value = read_pointers_[address >> 13][address & 8191]; } else { - int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3; + int slot_hit = (paging_.primary >> ((address >> 14) * 2)) & 3; memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.template flush()); *cycle.value = memory_slots_[slot_hit].handler->read(address); } @@ -517,7 +530,7 @@ class ConcreteMachine: case CPU::Z80::PartialMachineCycle::Write: { write_pointers_[address >> 13][address & 8191] = *cycle.value; - int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3; + int slot_hit = (paging_.primary >> ((address >> 14) * 2)) & 3; if(memory_slots_[slot_hit].handler) { update_audio(); memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.template flush()); @@ -699,7 +712,7 @@ class ConcreteMachine: void set_value(int port, uint8_t value) { switch(port) { - case 0: machine_.page_memory(value); break; + case 0: machine_.page_primary(value); break; case 2: { // TODO: // b6 caps lock LED @@ -761,15 +774,18 @@ class ConcreteMachine: bool allow_fast_tape_ = false; bool use_fast_tape_ = false; void set_use_fast_tape() { - use_fast_tape_ = !tape_player_is_sleeping_ && allow_fast_tape_ && tape_player_.has_tape() && !(paged_memory_&3); + use_fast_tape_ = !tape_player_is_sleeping_ && allow_fast_tape_ && tape_player_.has_tape() && !(paging_.primary&3) && !(paging_.secondary&3); } i8255PortHandler i8255_port_handler_; AYPortHandler ay_port_handler_; - /// The current primary slot selection; retains whatever value was written - /// last to the 8255 PPI via port A8. - uint8_t paged_memory_ = 0; + /// The current primary and secondary slot selections; the former retains whatever was written + /// last to the 8255 PPI via port A8 and the latter — if enabled — captures 0xffff. + struct { + uint8_t primary = 0; + uint8_t secondary = 0; + } paging_; // Divides the current 64kb address space into 8kb chunks. // 8kb resolution is used by some cartride titles. From ae5b81c0ab49e7367292dd7fbe249cc0768e67ff Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 10 Jan 2023 18:17:17 -0500 Subject: [PATCH 05/34] Add MSX 2 to the ROM catalogue. --- Machines/Utility/ROMCatalogue.cpp | 4 +++- Machines/Utility/ROMCatalogue.hpp | 2 ++ ROMImages/MSX/readme.txt | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Machines/Utility/ROMCatalogue.cpp b/Machines/Utility/ROMCatalogue.cpp index 35585c902..19cd9c581 100644 --- a/Machines/Utility/ROMCatalogue.cpp +++ b/Machines/Utility/ROMCatalogue.cpp @@ -565,12 +565,14 @@ Description::Description(Name name) { case Name::OricMicrodisc: *this = Description(name, "Oric", "the Oric Microdisc ROM", "microdisc.rom", 8*1024, 0xa9664a9cu); break; case Name::Oric8DOSBoot: *this = Description(name, "Oric", "the 8DOS boot ROM", "8dos.rom", 512, 0x49a74c06u); break; - case Name::MSXGenericBIOS: *this = Description(name, "MSX", "any MSX BIOS", "msx.rom", 32*1024, 0x94ee12f3u); break; + case Name::MSXGenericBIOS: *this = Description(name, "MSX", "a generix MSX BIOS", "msx.rom", 32*1024, 0x94ee12f3u); break; case Name::MSXJapaneseBIOS: *this = Description(name, "MSX", "a Japanese MSX BIOS", "msx-japanese.rom", 32*1024, 0xee229390u); break; case Name::MSXAmericanBIOS: *this = Description(name, "MSX", "an American MSX BIOS", "msx-american.rom", 32*1024, 0u); break; case Name::MSXEuropeanBIOS: *this = Description(name, "MSX", "a European MSX BIOS", "msx-european.rom", 32*1024, 0u); break; case Name::MSXDOS: *this = Description(name, "MSX", "the MSX-DOS ROM", "disk.rom", 16*1024, 0x721f61dfu); break; + case Name::MSX2GenericBIOS: *this = Description(name, "MSX", "a generic MSX2 BIOS", "msx2.rom", 32*1024, 0x6cdaf3a5u); break; + case Name::SinclairQLJS: *this = Description(name, "SinclairQL", "the Sinclair QL 'JS' ROM", "js.rom", 48*1024, 0x0f95aab5u); break; diff --git a/Machines/Utility/ROMCatalogue.hpp b/Machines/Utility/ROMCatalogue.hpp index 6619710ef..b9e5fc3a4 100644 --- a/Machines/Utility/ROMCatalogue.hpp +++ b/Machines/Utility/ROMCatalogue.hpp @@ -117,6 +117,8 @@ enum Name { MSXEuropeanBIOS, MSXDOS, + MSX2GenericBIOS, + // Oric. OricColourROM, OricBASIC10, diff --git a/ROMImages/MSX/readme.txt b/ROMImages/MSX/readme.txt index f97c4d102..da327ecbb 100644 --- a/ROMImages/MSX/readme.txt +++ b/ROMImages/MSX/readme.txt @@ -3,6 +3,7 @@ ROMs for the MSX go here; the copyright status of these is uncertain so they hav Minimum expected files: msx.rom +msx2.rom disk.rom These names match those offered for download at http://fms.komkon.org/fMSX/ (albeit in lowercase), and the emulator has been tested against those images. From 520ae7f2b246e7e90f4258e9556b2b6ce68786d2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 10 Jan 2023 22:15:01 -0500 Subject: [PATCH 06/34] Pick generic BIOS based on machine type. --- Machines/MSX/MSX.cpp | 26 ++++++++++++++++---------- Machines/Utility/ROMCatalogue.cpp | 1 + 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index e1186ff3b..64bca0713 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -169,10 +169,8 @@ class ConcreteMachine: // Install the proper TV standard and select an ideal BIOS name. const std::string machine_name = "MSX"; - ROM::Request bios_request = ROM::Request(ROM::Name::MSXGenericBIOS); -// std::vector required_roms = { -// {machine_name, "any MSX BIOS", "msx.rom", 32*1024, 0x94ee12f3u} -// }; + constexpr ROM::Name bios_name = model == Target::Model::MSX1 ? ROM::Name::MSXGenericBIOS : ROM::Name::MSX2GenericBIOS; + ROM::Request bios_request = ROM::Request(bios_name); bool is_ntsc = true; uint8_t character_generator = 1; /* 0 = Japan, 1 = USA, etc, 2 = USSR */ @@ -180,11 +178,12 @@ class ConcreteMachine: uint8_t keyboard = 1; /* 0 = Japan, 1 = USA, 2 = France, 3 = UK, 4 = Germany, 5 = USSR, 6 = Spain */ ROM::Name regional_bios_name; - // TODO: CRCs below are incomplete, at best. switch(target.region) { default: case Target::Region::Japan: - regional_bios_name = ROM::Name::MSXJapaneseBIOS; + if constexpr (model == Target::Model::MSX1) { + regional_bios_name = ROM::Name::MSXJapaneseBIOS; + } vdp_->set_tv_standard(TI::TMS::TVStandard::NTSC); is_ntsc = true; @@ -192,7 +191,9 @@ class ConcreteMachine: date_format = 0; break; case Target::Region::USA: - regional_bios_name = ROM::Name::MSXAmericanBIOS; + if constexpr (model == Target::Model::MSX1) { + regional_bios_name = ROM::Name::MSXAmericanBIOS; + } vdp_->set_tv_standard(TI::TMS::TVStandard::NTSC); is_ntsc = true; @@ -200,7 +201,9 @@ class ConcreteMachine: date_format = 1; break; case Target::Region::Europe: - regional_bios_name = ROM::Name::MSXEuropeanBIOS; + if constexpr (model == Target::Model::MSX1) { + regional_bios_name = ROM::Name::MSXEuropeanBIOS; + } vdp_->set_tv_standard(TI::TMS::TVStandard::PAL); is_ntsc = false; @@ -208,7 +211,9 @@ class ConcreteMachine: date_format = 2; break; } - bios_request = bios_request || ROM::Request(regional_bios_name); + if constexpr (model == Target::Model::MSX1) { + bios_request = bios_request || ROM::Request(regional_bios_name); + } // Fetch the necessary ROMs; try the region-specific ROM first, // but failing that fall back on patching the main one. @@ -231,9 +236,10 @@ class ConcreteMachine: memory_slots_[0].source = std::move(regional_bios->second); memory_slots_[0].source.resize(32768); } else { - memory_slots_[0].source = std::move(roms.find(ROM::Name::MSXGenericBIOS)->second); + memory_slots_[0].source = std::move(roms.find(bios_name)->second); memory_slots_[0].source.resize(32768); + // Modify the generic ROM to reflect the selected region, date format, etc. memory_slots_[0].source[0x2b] = uint8_t( (is_ntsc ? 0x00 : 0x80) | (date_format << 4) | diff --git a/Machines/Utility/ROMCatalogue.cpp b/Machines/Utility/ROMCatalogue.cpp index 19cd9c581..7f7862848 100644 --- a/Machines/Utility/ROMCatalogue.cpp +++ b/Machines/Utility/ROMCatalogue.cpp @@ -565,6 +565,7 @@ Description::Description(Name name) { case Name::OricMicrodisc: *this = Description(name, "Oric", "the Oric Microdisc ROM", "microdisc.rom", 8*1024, 0xa9664a9cu); break; case Name::Oric8DOSBoot: *this = Description(name, "Oric", "the 8DOS boot ROM", "8dos.rom", 512, 0x49a74c06u); break; + // TODO: CRCs below are incomplete, at best. case Name::MSXGenericBIOS: *this = Description(name, "MSX", "a generix MSX BIOS", "msx.rom", 32*1024, 0x94ee12f3u); break; case Name::MSXJapaneseBIOS: *this = Description(name, "MSX", "a Japanese MSX BIOS", "msx-japanese.rom", 32*1024, 0xee229390u); break; case Name::MSXAmericanBIOS: *this = Description(name, "MSX", "an American MSX BIOS", "msx-american.rom", 32*1024, 0u); break; From 483ee8a74f701d1d2cf5e84070865eec6200df88 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 10 Jan 2023 22:24:40 -0500 Subject: [PATCH 07/34] Add a catch for the secondary paging register. --- Machines/MSX/MSX.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 64bca0713..e02d2499d 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -440,6 +440,11 @@ class ConcreteMachine: set_use_fast_tape(); } + // For now: support secondary paging on the MSX 2 only. + constexpr static bool supports_secondary_paging() { + return model == Target::Model::MSX2; + } + // MARK: Z80::BusHandler forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { // Per the best information I currently have, the MSX inserts an extra cycle into each opcode read, @@ -524,6 +529,13 @@ class ConcreteMachine: [[fallthrough]]; case CPU::Z80::PartialMachineCycle::Read: + if constexpr (supports_secondary_paging()) { + if(address == 0xffff) { + *cycle.value = paging_.secondary ^ 0xff;; + break; + } + } + if(read_pointers_[address >> 13]) { *cycle.value = read_pointers_[address >> 13][address & 8191]; } else { @@ -534,6 +546,14 @@ class ConcreteMachine: break; case CPU::Z80::PartialMachineCycle::Write: { + if constexpr (supports_secondary_paging()) { + if(address == 0xffff) { + paging_.secondary = *cycle.value; + update_paging(); + break; + } + } + write_pointers_[address >> 13][address & 8191] = *cycle.value; int slot_hit = (paging_.primary >> ((address >> 14) * 2)) & 3; From 76ad465030ee5ef784dcd871a47faaa60453369e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 11 Jan 2023 12:56:09 -0500 Subject: [PATCH 08/34] Also seek the extension ROM for the MSX 2. --- Machines/MSX/MSX.cpp | 4 ++++ Machines/Utility/ROMCatalogue.cpp | 1 + Machines/Utility/ROMCatalogue.hpp | 1 + 3 files changed, 6 insertions(+) diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index e02d2499d..2b564f087 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -170,7 +170,11 @@ class ConcreteMachine: // Install the proper TV standard and select an ideal BIOS name. const std::string machine_name = "MSX"; constexpr ROM::Name bios_name = model == Target::Model::MSX1 ? ROM::Name::MSXGenericBIOS : ROM::Name::MSX2GenericBIOS; + ROM::Request bios_request = ROM::Request(bios_name); + if constexpr (model == Target::Model::MSX2) { + bios_request = bios_request || ROM::Request(ROM::Name::MSX2Extension); + } bool is_ntsc = true; uint8_t character_generator = 1; /* 0 = Japan, 1 = USA, etc, 2 = USSR */ diff --git a/Machines/Utility/ROMCatalogue.cpp b/Machines/Utility/ROMCatalogue.cpp index 7f7862848..b50c2dfd9 100644 --- a/Machines/Utility/ROMCatalogue.cpp +++ b/Machines/Utility/ROMCatalogue.cpp @@ -573,6 +573,7 @@ Description::Description(Name name) { case Name::MSXDOS: *this = Description(name, "MSX", "the MSX-DOS ROM", "disk.rom", 16*1024, 0x721f61dfu); break; case Name::MSX2GenericBIOS: *this = Description(name, "MSX", "a generic MSX2 BIOS", "msx2.rom", 32*1024, 0x6cdaf3a5u); break; + case Name::MSX2Extension: x*this = Description(name, "MSX", "the MSX2 extension ROM", "msx2ext.rom", 16*1024, 0x66237ecfu); break; case Name::SinclairQLJS: *this = Description(name, "SinclairQL", "the Sinclair QL 'JS' ROM", "js.rom", 48*1024, 0x0f95aab5u); diff --git a/Machines/Utility/ROMCatalogue.hpp b/Machines/Utility/ROMCatalogue.hpp index b9e5fc3a4..e085cbf09 100644 --- a/Machines/Utility/ROMCatalogue.hpp +++ b/Machines/Utility/ROMCatalogue.hpp @@ -118,6 +118,7 @@ enum Name { MSXDOS, MSX2GenericBIOS, + MSX2Extension, // Oric. OricColourROM, From fee82d3baa7d78c5c8a461664ea2a7baa40e9053 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 11 Jan 2023 13:14:42 -0500 Subject: [PATCH 09/34] Fix typo. --- Machines/Utility/ROMCatalogue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/Utility/ROMCatalogue.cpp b/Machines/Utility/ROMCatalogue.cpp index b50c2dfd9..f2edb50f2 100644 --- a/Machines/Utility/ROMCatalogue.cpp +++ b/Machines/Utility/ROMCatalogue.cpp @@ -573,7 +573,7 @@ Description::Description(Name name) { case Name::MSXDOS: *this = Description(name, "MSX", "the MSX-DOS ROM", "disk.rom", 16*1024, 0x721f61dfu); break; case Name::MSX2GenericBIOS: *this = Description(name, "MSX", "a generic MSX2 BIOS", "msx2.rom", 32*1024, 0x6cdaf3a5u); break; - case Name::MSX2Extension: x*this = Description(name, "MSX", "the MSX2 extension ROM", "msx2ext.rom", 16*1024, 0x66237ecfu); break; + case Name::MSX2Extension: *this = Description(name, "MSX", "the MSX2 extension ROM", "msx2ext.rom", 16*1024, 0x66237ecfu); break; case Name::SinclairQLJS: *this = Description(name, "SinclairQL", "the Sinclair QL 'JS' ROM", "js.rom", 48*1024, 0x0f95aab5u); From 0d8c014099bc49c2a9e7f5a031cec301c52c9cd3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 11 Jan 2023 13:15:00 -0500 Subject: [PATCH 10/34] Secondary slot selections are per primary slot. --- Machines/MSX/MSX.cpp | 62 +++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 2b564f087..8ff69f387 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -420,18 +420,16 @@ class ConcreteMachine: // MARK: Memory paging. void page_primary(uint8_t value) { - paging_.primary = value; - update_paging(); - } - - void page_secondary(uint8_t value) { - paging_.secondary = value; + primary_slots_ = value; update_paging(); } void update_paging() { - uint8_t primary = paging_.primary; + uint8_t primary = primary_slots_; + final_slot_ = &memory_slots_[primary >> 6]; + + // TODO: factor in secondary slot selection below. for(std::size_t c = 0; c < 8; c += 2) { const MemorySlot &slot = memory_slots_[primary & 3]; primary >>= 2; @@ -444,11 +442,6 @@ class ConcreteMachine: set_use_fast_tape(); } - // For now: support secondary paging on the MSX 2 only. - constexpr static bool supports_secondary_paging() { - return model == Target::Model::MSX2; - } - // MARK: Z80::BusHandler forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { // Per the best information I currently have, the MSX inserts an extra cycle into each opcode read, @@ -533,38 +526,34 @@ class ConcreteMachine: [[fallthrough]]; case CPU::Z80::PartialMachineCycle::Read: - if constexpr (supports_secondary_paging()) { - if(address == 0xffff) { - *cycle.value = paging_.secondary ^ 0xff;; - break; - } + if(address == 0xffff && final_slot_->supports_secondary_paging) { + *cycle.value = final_slot_->secondary_paging ^ 0xff; + break; } if(read_pointers_[address >> 13]) { *cycle.value = read_pointers_[address >> 13][address & 8191]; } else { - int slot_hit = (paging_.primary >> ((address >> 14) * 2)) & 3; + const int slot_hit = (primary_slots_ >> ((address >> 14) * 2)) & 3; memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.template flush()); *cycle.value = memory_slots_[slot_hit].handler->read(address); } break; case CPU::Z80::PartialMachineCycle::Write: { - if constexpr (supports_secondary_paging()) { - if(address == 0xffff) { - paging_.secondary = *cycle.value; - update_paging(); - break; - } + if(address == 0xffff && final_slot_->supports_secondary_paging) { + final_slot_->secondary_paging = *cycle.value; + update_paging(); + break; } - write_pointers_[address >> 13][address & 8191] = *cycle.value; - - int slot_hit = (paging_.primary >> ((address >> 14) * 2)) & 3; + const int slot_hit = (primary_slots_ >> ((address >> 14) * 2)) & 3; if(memory_slots_[slot_hit].handler) { update_audio(); memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.template flush()); memory_slots_[slot_hit].handler->write(address, *cycle.value, read_pointers_[pc_address_ >> 13] != memory_slots_[0].read_pointers[pc_address_ >> 13]); + } else { + write_pointers_[address >> 13][address & 8191] = *cycle.value; } } break; @@ -804,18 +793,20 @@ class ConcreteMachine: bool allow_fast_tape_ = false; bool use_fast_tape_ = false; void set_use_fast_tape() { - use_fast_tape_ = !tape_player_is_sleeping_ && allow_fast_tape_ && tape_player_.has_tape() && !(paging_.primary&3) && !(paging_.secondary&3); + use_fast_tape_ = + !tape_player_is_sleeping_ && + allow_fast_tape_ && + tape_player_.has_tape() && + !(primary_slots_ & 3) && + !(memory_slots_[0].secondary_paging & 3); } i8255PortHandler i8255_port_handler_; AYPortHandler ay_port_handler_; /// The current primary and secondary slot selections; the former retains whatever was written - /// last to the 8255 PPI via port A8 and the latter — if enabled — captures 0xffff. - struct { - uint8_t primary = 0; - uint8_t secondary = 0; - } paging_; + /// last to the 8255 PPI via port A8 and the latter — if enabled — captures 0xffff on a per-slot basis. + uint8_t primary_slots_ = 0; // Divides the current 64kb address space into 8kb chunks. // 8kb resolution is used by some cartride titles. @@ -852,12 +843,17 @@ class ConcreteMachine: HalfCycles cycles_since_update; std::unique_ptr handler; + ROMSlotHandler::WrappingStrategy wrapping_strategy = ROMSlotHandler::WrappingStrategy::Repeat; + bool supports_secondary_paging = false; + uint8_t secondary_paging = 0x00; + /// Per-slot storage, as a convenience. std::vector source; }; MemorySlot memory_slots_[4]; + MemorySlot *final_slot_ = nullptr; /// Base RAM. uint8_t ram_[65536]; From 2e7e5ea12ba20d0e9cd999ef74a94b3b8e6c7fa1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 12 Jan 2023 23:01:11 -0500 Subject: [PATCH 11/34] Fleshes out most of a cleaner memory slot layout. --- Machines/MSX/Cartridges/ASCII16kb.hpp | 12 +- Machines/MSX/Cartridges/ASCII8kb.hpp | 16 +- Machines/MSX/Cartridges/Konami.hpp | 14 +- Machines/MSX/Cartridges/KonamiWithSCC.hpp | 19 ++- Machines/MSX/DiskROM.cpp | 4 +- Machines/MSX/DiskROM.hpp | 4 +- Machines/MSX/MSX.cpp | 150 ++++++------------ Machines/MSX/MemorySlotHandler.cpp | 79 +++++++++ Machines/MSX/MemorySlotHandler.hpp | 120 ++++++++++++++ Machines/MSX/ROMSlotHandler.hpp | 85 ---------- .../Clock Signal.xcodeproj/project.pbxproj | 10 +- 11 files changed, 283 insertions(+), 230 deletions(-) create mode 100644 Machines/MSX/MemorySlotHandler.cpp create mode 100644 Machines/MSX/MemorySlotHandler.hpp delete mode 100644 Machines/MSX/ROMSlotHandler.hpp diff --git a/Machines/MSX/Cartridges/ASCII16kb.hpp b/Machines/MSX/Cartridges/ASCII16kb.hpp index 848402708..500782ba8 100644 --- a/Machines/MSX/Cartridges/ASCII16kb.hpp +++ b/Machines/MSX/Cartridges/ASCII16kb.hpp @@ -9,15 +9,14 @@ #ifndef ASCII16kb_hpp #define ASCII16kb_hpp -#include "../ROMSlotHandler.hpp" +#include "../MemorySlotHandler.hpp" namespace MSX { namespace Cartridge { class ASCII16kbROMSlotHandler: public ROMSlotHandler { public: - ASCII16kbROMSlotHandler(MSX::MemoryMap &map, int slot) : - map_(map), slot_(slot) {} + ASCII16kbROMSlotHandler(MSX::MemorySlot &slot) : slot_(slot) {} void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final { switch(address >> 11) { @@ -28,13 +27,13 @@ class ASCII16kbROMSlotHandler: public ROMSlotHandler { if(pc_is_outside_bios) { if(address == 0x6000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); } - map_.map(slot_, value * 0x4000, 0x4000, 0x4000); + slot_.map(0, value * 0x4000, 0x4000, 0x4000); break; case 0xe: if(pc_is_outside_bios) { if(address == 0x7000 || address == 0x77ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); } - map_.map(slot_, value * 0x4000, 0x8000, 0x4000); + slot_.map(0, value * 0x4000, 0x8000, 0x4000); break; } } @@ -44,8 +43,7 @@ class ASCII16kbROMSlotHandler: public ROMSlotHandler { } private: - MSX::MemoryMap &map_; - int slot_; + MSX::MemorySlot &slot_; }; } diff --git a/Machines/MSX/Cartridges/ASCII8kb.hpp b/Machines/MSX/Cartridges/ASCII8kb.hpp index 6a851d5f8..9ae4a72f0 100644 --- a/Machines/MSX/Cartridges/ASCII8kb.hpp +++ b/Machines/MSX/Cartridges/ASCII8kb.hpp @@ -9,15 +9,14 @@ #ifndef ASCII8kb_hpp #define ASCII8kb_hpp -#include "../ROMSlotHandler.hpp" +#include "../MemorySlotHandler.hpp" namespace MSX { namespace Cartridge { class ASCII8kbROMSlotHandler: public ROMSlotHandler { public: - ASCII8kbROMSlotHandler(MSX::MemoryMap &map, int slot) : - map_(map), slot_(slot) {} + ASCII8kbROMSlotHandler(MSX::MemorySlot &slot) : slot_(slot) {} void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final { switch(address >> 11) { @@ -28,25 +27,25 @@ class ASCII8kbROMSlotHandler: public ROMSlotHandler { if(pc_is_outside_bios) { if(address == 0x6000 || address == 0x60ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); } - map_.map(slot_, value * 0x2000, 0x4000, 0x2000); + slot_.map(0, value * 0x2000, 0x4000, 0x2000); break; case 0xd: if(pc_is_outside_bios) { if(address == 0x6800 || address == 0x68ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); } - map_.map(slot_, value * 0x2000, 0x6000, 0x2000); + slot_.map(0, value * 0x2000, 0x6000, 0x2000); break; case 0xe: if(pc_is_outside_bios) { if(address == 0x7000 || address == 0x70ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); } - map_.map(slot_, value * 0x2000, 0x8000, 0x2000); + slot_.map(0, value * 0x2000, 0x8000, 0x2000); break; case 0xf: if(pc_is_outside_bios) { if(address == 0x7800 || address == 0x78ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); } - map_.map(slot_, value * 0x2000, 0xa000, 0x2000); + slot_.map(0, value * 0x2000, 0xa000, 0x2000); break; } } @@ -56,8 +55,7 @@ class ASCII8kbROMSlotHandler: public ROMSlotHandler { } private: - MSX::MemoryMap &map_; - int slot_; + MSX::MemorySlot &slot_; }; } diff --git a/Machines/MSX/Cartridges/Konami.hpp b/Machines/MSX/Cartridges/Konami.hpp index 1a6dbbc18..16e28394d 100644 --- a/Machines/MSX/Cartridges/Konami.hpp +++ b/Machines/MSX/Cartridges/Konami.hpp @@ -9,15 +9,14 @@ #ifndef Konami_hpp #define Konami_hpp -#include "../ROMSlotHandler.hpp" +#include "../MemorySlotHandler.hpp" namespace MSX { namespace Cartridge { class KonamiROMSlotHandler: public ROMSlotHandler { public: - KonamiROMSlotHandler(MSX::MemoryMap &map, int slot) : - map_(map), slot_(slot) {} + KonamiROMSlotHandler(MSX::MemorySlot &slot) : slot_(slot) {} void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final { switch(address >> 13) { @@ -28,19 +27,19 @@ class KonamiROMSlotHandler: public ROMSlotHandler { if(pc_is_outside_bios) { if(address == 0x6000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); } - map_.map(slot_, value * 0x2000, 0x6000, 0x2000); + slot_.map(0, value * 0x2000, 0x6000, 0x2000); break; case 4: if(pc_is_outside_bios) { if(address == 0x8000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); } - map_.map(slot_, value * 0x2000, 0x8000, 0x2000); + slot_.map(0, value * 0x2000, 0x8000, 0x2000); break; case 5: if(pc_is_outside_bios) { if(address == 0xa000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); } - map_.map(slot_, value * 0x2000, 0xa000, 0x2000); + slot_.map(0, value * 0x2000, 0xa000, 0x2000); break; } } @@ -49,8 +48,7 @@ class KonamiROMSlotHandler: public ROMSlotHandler { return "K"; } private: - MSX::MemoryMap &map_; - int slot_; + MSX::MemorySlot &slot_; }; } diff --git a/Machines/MSX/Cartridges/KonamiWithSCC.hpp b/Machines/MSX/Cartridges/KonamiWithSCC.hpp index 6a0e1b8c3..9d7acc97d 100644 --- a/Machines/MSX/Cartridges/KonamiWithSCC.hpp +++ b/Machines/MSX/Cartridges/KonamiWithSCC.hpp @@ -9,7 +9,7 @@ #ifndef KonamiWithSCC_hpp #define KonamiWithSCC_hpp -#include "../ROMSlotHandler.hpp" +#include "../MemorySlotHandler.hpp" #include "../../../Components/KonamiSCC/KonamiSCC.hpp" namespace MSX { @@ -17,8 +17,8 @@ namespace Cartridge { class KonamiWithSCCROMSlotHandler: public ROMSlotHandler { public: - KonamiWithSCCROMSlotHandler(MSX::MemoryMap &map, int slot, Konami::SCC &scc) : - map_(map), slot_(slot), scc_(scc) {} + KonamiWithSCCROMSlotHandler(MSX::MemorySlot &slot, Konami::SCC &scc) : + slot_(slot), scc_(scc) {} void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final { switch(address >> 11) { @@ -29,13 +29,13 @@ class KonamiWithSCCROMSlotHandler: public ROMSlotHandler { if(pc_is_outside_bios) { if(address == 0x5000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); } - map_.map(slot_, value * 0x2000, 0x4000, 0x2000); + slot_.map(0, value * 0x2000, 0x4000, 0x2000); break; case 0x0e: if(pc_is_outside_bios) { if(address == 0x7000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); } - map_.map(slot_, value * 0x2000, 0x6000, 0x2000); + slot_.map(0, value * 0x2000, 0x6000, 0x2000); break; case 0x12: if(pc_is_outside_bios) { @@ -43,10 +43,10 @@ class KonamiWithSCCROMSlotHandler: public ROMSlotHandler { } if((value&0x3f) == 0x3f) { scc_is_visible_ = true; - map_.unmap(slot_, 0x8000, 0x2000); + slot_.unmap(0, 0x8000, 0x2000); } else { scc_is_visible_ = false; - map_.map(slot_, value * 0x2000, 0x8000, 0x2000); + slot_.map(0, value * 0x2000, 0x8000, 0x2000); } break; case 0x13: @@ -61,7 +61,7 @@ class KonamiWithSCCROMSlotHandler: public ROMSlotHandler { if(pc_is_outside_bios) { if(address == 0xb000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); } - map_.map(slot_, value * 0x2000, 0xa000, 0x2000); + slot_.map(0, value * 0x2000, 0xa000, 0x2000); break; } } @@ -80,8 +80,7 @@ class KonamiWithSCCROMSlotHandler: public ROMSlotHandler { } private: - MSX::MemoryMap &map_; - int slot_; + MSX::MemorySlot &slot_; Konami::SCC &scc_; bool scc_is_visible_ = false; }; diff --git a/Machines/MSX/DiskROM.cpp b/Machines/MSX/DiskROM.cpp index b5df05b8b..97d7a57aa 100644 --- a/Machines/MSX/DiskROM.cpp +++ b/Machines/MSX/DiskROM.cpp @@ -10,9 +10,9 @@ using namespace MSX; -DiskROM::DiskROM(const std::vector &rom) : +DiskROM::DiskROM(const MSX::MemorySlot &slot) : WD1770(P1793), - rom_(rom) { + rom_(slot.source()) { emplace_drives(2, 8000000, 300, 2); set_is_double_density(true); } diff --git a/Machines/MSX/DiskROM.hpp b/Machines/MSX/DiskROM.hpp index 8227982af..24cba9d91 100644 --- a/Machines/MSX/DiskROM.hpp +++ b/Machines/MSX/DiskROM.hpp @@ -9,7 +9,7 @@ #ifndef DiskROM_hpp #define DiskROM_hpp -#include "ROMSlotHandler.hpp" +#include "MemorySlotHandler.hpp" #include "../../Activity/Source.hpp" #include "../../Components/1770/1770.hpp" @@ -23,7 +23,7 @@ namespace MSX { class DiskROM: public ROMSlotHandler, public WD::WD1770 { public: - DiskROM(const std::vector &rom); + DiskROM(const MSX::MemorySlot &slot); void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final; uint8_t read(uint16_t address) final; diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 8ff69f387..cdc575252 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -12,7 +12,7 @@ #include "DiskROM.hpp" #include "Keyboard.hpp" -#include "ROMSlotHandler.hpp" +#include "MemorySlotHandler.hpp" #include "../../Analyser/Static/MSX/Cartridge.hpp" #include "Cartridges/ASCII8kb.hpp" @@ -141,7 +141,6 @@ class ConcreteMachine: public MachineTypes::MappedKeyboardMachine, public MachineTypes::JoystickMachine, public Configurable::Device, - public MemoryMap, public ClockingHint::Observer, public Activity::Source { public: @@ -237,42 +236,37 @@ class ConcreteMachine: // one appropriately patched. const auto regional_bios = roms.find(regional_bios_name); if(regional_bios != roms.end()) { - memory_slots_[0].source = std::move(regional_bios->second); - memory_slots_[0].source.resize(32768); + regional_bios->second.resize(32768); + memory_slots_[0].set_source(regional_bios->second); } else { - memory_slots_[0].source = std::move(roms.find(bios_name)->second); - memory_slots_[0].source.resize(32768); + std::vector &bios = roms.find(bios_name)->second; + + bios.resize(32768); // Modify the generic ROM to reflect the selected region, date format, etc. - memory_slots_[0].source[0x2b] = uint8_t( + bios[0x2b] = uint8_t( (is_ntsc ? 0x00 : 0x80) | (date_format << 4) | character_generator ); - memory_slots_[0].source[0x2c] = keyboard; + bios[0x2c] = keyboard; + + memory_slots_[0].set_source(bios); } - for(size_t c = 0; c < 8; ++c) { - for(size_t slot = 0; slot < 3; ++slot) { - memory_slots_[slot].read_pointers[c] = unpopulated_; - memory_slots_[slot].write_pointers[c] = scratch_; - } - - memory_slots_[3].read_pointers[c] = - memory_slots_[3].write_pointers[c] = &ram_[c * 8192]; - } - - map(0, 0, 0, 32768); - page_primary(0); + memory_slots_[0].map(0, 0, 0, 32768); + memory_slots_[3].map(0, 0, 0, 65536); // Add a disk cartridge if any disks were supplied. if(target.has_disk_drive) { - memory_slots_[2].set_handler(new DiskROM(memory_slots_[2].source)); - memory_slots_[2].source = std::move(roms.find(ROM::Name::MSXDOS)->second); - memory_slots_[2].source.resize(16384); + memory_slots_[2].handler = std::make_unique(memory_slots_[2]); - map(2, 0, 0x4000, 0x2000); - unmap(2, 0x6000, 0x2000); + std::vector &dos = roms.find(ROM::Name::MSXDOS)->second; + dos.resize(16384); + memory_slots_[2].set_source(dos); + + memory_slots_[2].map(0, 0, 0x4000, 0x2000); + memory_slots_[2].unmap(0, 0x6000, 0x2000); } // Insert the media. @@ -282,6 +276,9 @@ class ConcreteMachine: if(!target.loading_command.empty()) { type_string(target.loading_command); } + + // Establish default paging. + page_primary(0); } ~ConcreteMachine() { @@ -330,24 +327,26 @@ class ConcreteMachine: bool insert_media(const Analyser::Static::Media &media) final { if(!media.cartridges.empty()) { const auto &segment = media.cartridges.front()->get_segments().front(); - memory_slots_[1].source = segment.data; - map(1, 0, uint16_t(segment.start_address), std::min(segment.data.size(), 65536 - segment.start_address)); + auto &slot = memory_slots_[1]; + + slot.set_source(segment.data); + slot.map(0, 0, uint16_t(segment.start_address), std::min(segment.data.size(), 65536 - segment.start_address)); auto msx_cartridge = dynamic_cast(media.cartridges.front().get()); if(msx_cartridge) { switch(msx_cartridge->type) { default: break; case Analyser::Static::MSX::Cartridge::Konami: - memory_slots_[1].set_handler(new Cartridge::KonamiROMSlotHandler(*this, 1)); + slot.handler = std::make_unique(static_cast(slot)); break; case Analyser::Static::MSX::Cartridge::KonamiWithSCC: - memory_slots_[1].set_handler(new Cartridge::KonamiWithSCCROMSlotHandler(*this, 1, scc_)); + slot.handler = std::make_unique(static_cast(slot), scc_); break; case Analyser::Static::MSX::Cartridge::ASCII8kb: - memory_slots_[1].set_handler(new Cartridge::ASCII8kbROMSlotHandler(*this, 1)); + slot.handler = std::make_unique(static_cast(slot)); break; case Analyser::Static::MSX::Cartridge::ASCII16kb: - memory_slots_[1].set_handler(new Cartridge::ASCII16kbROMSlotHandler(*this, 1)); + slot.handler = std::make_unique(static_cast(slot)); break; } } @@ -388,36 +387,6 @@ class ConcreteMachine: return c >= 32 && c < 127; } - // MARK: MSX::MemoryMap - void map(int slot, std::size_t source_address, uint16_t destination_address, std::size_t length) final { - assert(!(destination_address & 8191)); - assert(!(length & 8191)); - assert(size_t(destination_address) + length <= 65536); - - for(std::size_t c = 0; c < (length >> 13); ++c) { - 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; - } - - update_paging(); - } - - void unmap(int slot, uint16_t destination_address, std::size_t length) final { - assert(!(destination_address & 8191)); - assert(!(length & 8191)); - assert(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; - } - - update_paging(); - } - // MARK: Memory paging. void page_primary(uint8_t value) { primary_slots_ = value; @@ -427,17 +396,18 @@ class ConcreteMachine: void update_paging() { uint8_t primary = primary_slots_; + // Update final slot; this direct pointer will be used for + // secondary slot communication. final_slot_ = &memory_slots_[primary >> 6]; - // TODO: factor in secondary slot selection below. for(std::size_t c = 0; c < 8; c += 2) { const MemorySlot &slot = memory_slots_[primary & 3]; primary >>= 2; - read_pointers_[c] = slot.read_pointers[c]; - write_pointers_[c] = slot.write_pointers[c]; - read_pointers_[c+1] = slot.read_pointers[c+1]; - write_pointers_[c+1] = slot.write_pointers[c+1]; + read_pointers_[c] = slot.read_pointer(c); + write_pointers_[c] = slot.write_pointer(c); + read_pointers_[c+1] = slot.read_pointer(c+1); + write_pointers_[c+1] = slot.write_pointer(c+1); } set_use_fast_tape(); } @@ -527,7 +497,7 @@ class ConcreteMachine: case CPU::Z80::PartialMachineCycle::Read: if(address == 0xffff && final_slot_->supports_secondary_paging) { - *cycle.value = final_slot_->secondary_paging ^ 0xff; + *cycle.value = final_slot_->secondary_paging() ^ 0xff; break; } @@ -542,7 +512,7 @@ class ConcreteMachine: case CPU::Z80::PartialMachineCycle::Write: { if(address == 0xffff && final_slot_->supports_secondary_paging) { - final_slot_->secondary_paging = *cycle.value; + final_slot_->set_secondary_paging(*cycle.value); update_paging(); break; } @@ -551,7 +521,10 @@ class ConcreteMachine: if(memory_slots_[slot_hit].handler) { update_audio(); memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.template flush()); - memory_slots_[slot_hit].handler->write(address, *cycle.value, read_pointers_[pc_address_ >> 13] != memory_slots_[0].read_pointers[pc_address_ >> 13]); + memory_slots_[slot_hit].handler->write( + address, + *cycle.value, + read_pointers_[pc_address_ >> 13] != memory_slots_[0].read_pointer(pc_address_ >> 13)); } else { write_pointers_[address >> 13][address & 8191] = *cycle.value; } @@ -798,7 +771,7 @@ class ConcreteMachine: allow_fast_tape_ && tape_player_.has_tape() && !(primary_slots_ & 3) && - !(memory_slots_[0].secondary_paging & 3); + !(memory_slots_[0].secondary_paging() & 3); } i8255PortHandler i8255_port_handler_; @@ -810,47 +783,14 @@ class ConcreteMachine: // Divides the current 64kb address space into 8kb chunks. // 8kb resolution is used by some cartride titles. - uint8_t *read_pointers_[8]; + const uint8_t *read_pointers_[8]; uint8_t *write_pointers_[8]; /// Optionally attaches non-default logic to any of the four things selectable /// via the primary slot register. - /// - /// Tracks secondary and game-specific paging within each slot. - /// - /// Standard MSX assignments: - /// 0 = ROM; - /// 3 = RAM. - /// - /// Additional assignments customarily used by this emulator: - /// 1 = any inserted cartridge; - /// 2 = the disk ROM, if present. - struct MemorySlot { - // Each slot may be visible for any number of quarters of the memory map; - // these are the read and write pointers that should appear in the main - // memory map if this slot is visible there. - uint8_t *read_pointers[8]; - uint8_t *write_pointers[8]; - - /// Sets the handler that will be provided with @c run_for and @c read or @c write calls - /// for memory accesses **anywhere it has left memory unmapped**. Anywhere that can be - /// described just in terms of standard memory access read and write destinations should be, - /// ideally to avoid virtual dispatch. - void set_handler(ROMSlotHandler *slot_handler) { - handler.reset(slot_handler); - wrapping_strategy = handler->wrapping_strategy(); - } - + struct MemorySlot: public MSX::MemorySlot { HalfCycles cycles_since_update; std::unique_ptr handler; - - ROMSlotHandler::WrappingStrategy wrapping_strategy = ROMSlotHandler::WrappingStrategy::Repeat; - - bool supports_secondary_paging = false; - uint8_t secondary_paging = 0x00; - - /// Per-slot storage, as a convenience. - std::vector source; }; MemorySlot memory_slots_[4]; MemorySlot *final_slot_ = nullptr; diff --git a/Machines/MSX/MemorySlotHandler.cpp b/Machines/MSX/MemorySlotHandler.cpp new file mode 100644 index 000000000..4ab1985a4 --- /dev/null +++ b/Machines/MSX/MemorySlotHandler.cpp @@ -0,0 +1,79 @@ +// +// MemorySlotHandler.cpp +// Clock Signal +// +// Created by Thomas Harte on 12/01/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#include "MemorySlotHandler.hpp" + +using namespace MSX; + +MemorySlot::MemorySlot() { + for(int subslot = 0; subslot < 4; subslot++) { + for(int region = 0; region < 8; region++) { + read_pointers_[subslot][region] = unmapped.data(); + write_pointers_[subslot][region] = scratch.data(); + } + } +} + +void MemorySlot::set_secondary_paging(uint8_t value) { + secondary_paging_ = value; +} + +uint8_t MemorySlot::secondary_paging() const { + return secondary_paging_; +} + +const uint8_t *MemorySlot::read_pointer(int segment) const { + const int subslot = (secondary_paging_ >> (segment & ~1)) & 3; + return read_pointers_[subslot][segment]; +} + +uint8_t *MemorySlot::write_pointer(int segment) const { + const int subslot = (secondary_paging_ >> (segment & ~1)) & 3; + return write_pointers_[subslot][segment]; +} + +void MemorySlot::apply_mapping(uint8_t port, uint8_t value) { + // TODO. + (void)port; + (void)value; +} + +void MemorySlot::set_source(const std::vector &source) { + source_ = source; +} + +const std::vector &MemorySlot::source() const { + return source_; +} + +void MemorySlot::map(int subslot, std::size_t source_address, uint16_t destination_address, std::size_t length) { + assert(!(destination_address & 8191)); + assert(!(length & 8191)); + assert(size_t(destination_address) + length <= 65536); + + for(std::size_t c = 0; c < (length >> 13); ++c) { + source_address %= source_.size(); + read_pointers_[subslot][(destination_address >> 13) + c] = &source_[source_address]; + source_address += 8192; + } + + // TODO: allow write_pointers_ to be set. + // TODO: need to indicate that mapping changed. +} + +void MemorySlot::unmap(int subslot, uint16_t destination_address, std::size_t length) { + assert(!(destination_address & 8191)); + assert(!(length & 8191)); + assert(size_t(destination_address) + length <= 65536); + + for(std::size_t c = 0; c < (length >> 13); ++c) { + read_pointers_[subslot][(destination_address >> 13) + c] = nullptr; + } + + // TODO: need to indicate that mapping changed. +} diff --git a/Machines/MSX/MemorySlotHandler.hpp b/Machines/MSX/MemorySlotHandler.hpp new file mode 100644 index 000000000..3f0e54000 --- /dev/null +++ b/Machines/MSX/MemorySlotHandler.hpp @@ -0,0 +1,120 @@ +// +// MemorySlotHandler.hpp +// Clock Signal +// +// Created by Thomas Harte on 03/01/2018. +// Copyright 2018 Thomas Harte. All rights reserved. +// + +#ifndef MemorySlotHandler_hpp +#define MemorySlotHandler_hpp + +#include "../../ClockReceiver/ClockReceiver.hpp" +#include "../../Analyser/Dynamic/ConfidenceCounter.hpp" + +#include +#include +#include +#include +#include + +/* + 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 MemorySlot { + public: + MemorySlot(); + + /// Attempts to write the argument as the secondary paging selection. + void set_secondary_paging(uint8_t); + + /// @returns The value most recently provided to @c set_secondary_paging. + uint8_t secondary_paging() const; + + /// Indicates whether this slot supports secondary paging. + bool supports_secondary_paging = false; + + /// @returns A pointer to the area of memory currently underneath @c address that + /// should be read + const uint8_t *read_pointer(int segment) const; + + /// @returns A pointer to the area of memory currently underneath @c address. + uint8_t *write_pointer(int segment) const; + + /// Sets the value most-recently written to one of the standard + /// memory mapping ports, FC–FF. + void apply_mapping(uint8_t port, uint8_t value); + + /// Copies an underlying source buffer. + void set_source(const std::vector &source); + + /// Provides a reference to the internal source storage. + const std::vector &source() const; + + /// Maps the content from @c source_address in the buffer previously + /// supplied to @c set_source to the region indicated by + /// @c destination_address and @c length within @c subslot. + void map( + int subslot, + std::size_t source_address, + uint16_t destination_address, + std::size_t length); + + /// Marks the region indicated by @c destination_address and @c length + /// as unmapped. In practical terms that means that a @c ROMSlotHandler + /// will be used to field accesses to that area, allowing for areas that are not + /// backed by memory to be modelled. + void unmap( + int subslot, + uint16_t destination_address, + std::size_t length); + + private: + std::vector source_; + uint8_t *read_pointers_[4][8]; + uint8_t *write_pointers_[4][8]; + uint8_t secondary_paging_ = 0; + + using MemoryChunk = std::array; + inline static MemoryChunk unmapped{0xff}; + inline static MemoryChunk scratch; +}; + +class ROMSlotHandler { + public: + virtual ~ROMSlotHandler() {} + + /*! Advances time by @c half_cycles. */ + virtual void run_for([[maybe_unused]] HalfCycles half_cycles) {} + + /*! Announces an attempt to write @c value to @c address. */ + virtual void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) = 0; + + /*! Seeks the result of a read at @c address; this is used only if the area is unmapped. */ + virtual uint8_t read([[maybe_unused]] uint16_t address) { return 0xff; } + + /*! @returns The probability that this handler is correct for the data it owns. */ + float get_confidence() { + return confidence_counter_.get_confidence(); + } + + virtual std::string debug_type() { + return ""; + } + + protected: + Analyser::Dynamic::ConfidenceCounter confidence_counter_; +}; + +} + +#endif /* MemorySlotHandler_hpp */ diff --git a/Machines/MSX/ROMSlotHandler.hpp b/Machines/MSX/ROMSlotHandler.hpp deleted file mode 100644 index ab29b404e..000000000 --- a/Machines/MSX/ROMSlotHandler.hpp +++ /dev/null @@ -1,85 +0,0 @@ -// -// ROMSlotHandler.hpp -// Clock Signal -// -// Created by Thomas Harte on 03/01/2018. -// Copyright 2018 Thomas Harte. All rights reserved. -// - -#ifndef ROMSlotHandler_hpp -#define ROMSlotHandler_hpp - -#include "../../ClockReceiver/ClockReceiver.hpp" -#include "../../Analyser/Dynamic/ConfidenceCounter.hpp" - -#include -#include -#include - -/* - 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: - virtual ~ROMSlotHandler() {} - - /*! Advances time by @c half_cycles. */ - virtual void run_for([[maybe_unused]] HalfCycles half_cycles) {} - - /*! Announces an attempt to write @c value to @c address. */ - virtual void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) = 0; - - /*! Seeks the result of a read at @c address; this is used only if the area is unmapped. */ - virtual uint8_t read([[maybe_unused]] uint16_t address) { return 0xff; } - - enum class WrappingStrategy { - /// All accesses are modulo the size of the ROM. - Repeat, - /// Out-of-bounds accesses 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; - } - - /*! @returns The probability that this handler is correct for the data it owns. */ - float get_confidence() { - return confidence_counter_.get_confidence(); - } - - virtual std::string debug_type() { - return ""; - } - - protected: - Analyser::Dynamic::ConfidenceCounter confidence_counter_; -}; - -} - -#endif /* ROMSlotHandler_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index dcf95902c..996493baf 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1062,6 +1062,8 @@ 4BEEE6BD20DC72EB003723BF /* CompositeOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BEEE6BB20DC72EA003723BF /* CompositeOptions.xib */; }; 4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; }; 4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; }; + 4BF0BC68297108D600CCA2B5 /* MemorySlotHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF0BC67297108D100CCA2B5 /* MemorySlotHandler.cpp */; }; + 4BF0BC69297108D600CCA2B5 /* MemorySlotHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF0BC67297108D100CCA2B5 /* MemorySlotHandler.cpp */; }; 4BF437EE209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; }; 4BF437EF209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; }; 4BF701A026FFD32300996424 /* AmigaBlitterTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BF7019F26FFD32300996424 /* AmigaBlitterTests.mm */; }; @@ -1457,7 +1459,7 @@ 4B6FD0352923061300EC4760 /* HDV.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = HDV.hpp; sourceTree = ""; }; 4B7041271F92C26900735E45 /* JoystickMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = JoystickMachine.hpp; sourceTree = ""; }; 4B70412A1F92C2A700735E45 /* Joystick.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Joystick.hpp; sourceTree = ""; }; - 4B70EF6A1FFDCDF400A3494E /* ROMSlotHandler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ROMSlotHandler.hpp; sourceTree = ""; }; + 4B70EF6A1FFDCDF400A3494E /* MemorySlotHandler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MemorySlotHandler.hpp; sourceTree = ""; }; 4B7136841F78724F008B8ED9 /* Encoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Encoder.cpp; sourceTree = ""; }; 4B7136851F78724F008B8ED9 /* Encoder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Encoder.hpp; sourceTree = ""; }; 4B7136871F78725F008B8ED9 /* Shifter.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Shifter.cpp; sourceTree = ""; }; @@ -2208,6 +2210,7 @@ 4BEF6AA81D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DigitalPhaseLockedLoopBridge.h; sourceTree = ""; }; 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DigitalPhaseLockedLoopBridge.mm; sourceTree = ""; }; 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DPLLTests.swift; sourceTree = ""; }; + 4BF0BC67297108D100CCA2B5 /* MemorySlotHandler.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MemorySlotHandler.cpp; sourceTree = ""; }; 4BF40A5525424C770033EA39 /* LanguageCardSwitches.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LanguageCardSwitches.hpp; sourceTree = ""; }; 4BF40A5A254263140033EA39 /* AuxiliaryMemorySwitches.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AuxiliaryMemorySwitches.hpp; sourceTree = ""; }; 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SegmentParser.cpp; sourceTree = ""; }; @@ -3261,11 +3264,12 @@ children = ( 4BEBFB4F2002DB30000708CC /* DiskROM.cpp */, 4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */, + 4BF0BC67297108D100CCA2B5 /* MemorySlotHandler.cpp */, 4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */, 4BEBFB502002DB30000708CC /* DiskROM.hpp */, 4B12C0EC1FCFA98D005BFD93 /* Keyboard.hpp */, + 4B70EF6A1FFDCDF400A3494E /* MemorySlotHandler.hpp */, 4B79A5001FC913C900EEDAD5 /* MSX.hpp */, - 4B70EF6A1FFDCDF400A3494E /* ROMSlotHandler.hpp */, 4B1667F81FFF1E2900A16032 /* Cartridges */, ); path = MSX; @@ -5544,6 +5548,7 @@ 4B0E04F11FC9EA9500F43484 /* MSX.cpp in Sources */, 4B055AD51FAE9B0B0060FFFF /* Video.cpp in Sources */, 4B9EC0EB26B384080060A31F /* Keyboard.cpp in Sources */, + 4BF0BC69297108D600CCA2B5 /* MemorySlotHandler.cpp in Sources */, 4B894521201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4B8318B522D3E548006DB630 /* Macintosh.cpp in Sources */, 4B8DF4FA254E36AE00F3433C /* Video.cpp in Sources */, @@ -5802,6 +5807,7 @@ 4B4518A11F75FD1C00926311 /* D64.cpp in Sources */, 4B1558C01F844ECD006E9A97 /* BitReverse.cpp in Sources */, 4BCE0052227CE8CA000CA200 /* DiskIICard.cpp in Sources */, + 4BF0BC68297108D600CCA2B5 /* MemorySlotHandler.cpp in Sources */, 4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */, 4BD67DCB209BE4D700AB2146 /* StaticAnalyser.cpp in Sources */, 4BB4BFB922A4372F0069048D /* StaticAnalyser.cpp in Sources */, From ce440d52b3f141fa658289e6f0f28cb7f5dbf294 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 12 Jan 2023 23:02:24 -0500 Subject: [PATCH 12/34] Standardise name. --- Machines/MSX/Cartridges/ASCII16kb.hpp | 2 +- Machines/MSX/Cartridges/ASCII8kb.hpp | 2 +- Machines/MSX/Cartridges/Konami.hpp | 2 +- Machines/MSX/Cartridges/KonamiWithSCC.hpp | 2 +- Machines/MSX/DiskROM.hpp | 2 +- Machines/MSX/MSX.cpp | 2 +- Machines/MSX/MemorySlotHandler.hpp | 6 +++--- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Machines/MSX/Cartridges/ASCII16kb.hpp b/Machines/MSX/Cartridges/ASCII16kb.hpp index 500782ba8..5cbe2822a 100644 --- a/Machines/MSX/Cartridges/ASCII16kb.hpp +++ b/Machines/MSX/Cartridges/ASCII16kb.hpp @@ -14,7 +14,7 @@ namespace MSX { namespace Cartridge { -class ASCII16kbROMSlotHandler: public ROMSlotHandler { +class ASCII16kbROMSlotHandler: public MemorySlotHandler { public: ASCII16kbROMSlotHandler(MSX::MemorySlot &slot) : slot_(slot) {} diff --git a/Machines/MSX/Cartridges/ASCII8kb.hpp b/Machines/MSX/Cartridges/ASCII8kb.hpp index 9ae4a72f0..e1aac0b17 100644 --- a/Machines/MSX/Cartridges/ASCII8kb.hpp +++ b/Machines/MSX/Cartridges/ASCII8kb.hpp @@ -14,7 +14,7 @@ namespace MSX { namespace Cartridge { -class ASCII8kbROMSlotHandler: public ROMSlotHandler { +class ASCII8kbROMSlotHandler: public MemorySlotHandler { public: ASCII8kbROMSlotHandler(MSX::MemorySlot &slot) : slot_(slot) {} diff --git a/Machines/MSX/Cartridges/Konami.hpp b/Machines/MSX/Cartridges/Konami.hpp index 16e28394d..7422ad1da 100644 --- a/Machines/MSX/Cartridges/Konami.hpp +++ b/Machines/MSX/Cartridges/Konami.hpp @@ -14,7 +14,7 @@ namespace MSX { namespace Cartridge { -class KonamiROMSlotHandler: public ROMSlotHandler { +class KonamiROMSlotHandler: public MemorySlotHandler { public: KonamiROMSlotHandler(MSX::MemorySlot &slot) : slot_(slot) {} diff --git a/Machines/MSX/Cartridges/KonamiWithSCC.hpp b/Machines/MSX/Cartridges/KonamiWithSCC.hpp index 9d7acc97d..15da86b6f 100644 --- a/Machines/MSX/Cartridges/KonamiWithSCC.hpp +++ b/Machines/MSX/Cartridges/KonamiWithSCC.hpp @@ -15,7 +15,7 @@ namespace MSX { namespace Cartridge { -class KonamiWithSCCROMSlotHandler: public ROMSlotHandler { +class KonamiWithSCCROMSlotHandler: public MemorySlotHandler { public: KonamiWithSCCROMSlotHandler(MSX::MemorySlot &slot, Konami::SCC &scc) : slot_(slot), scc_(scc) {} diff --git a/Machines/MSX/DiskROM.hpp b/Machines/MSX/DiskROM.hpp index 24cba9d91..931668eb4 100644 --- a/Machines/MSX/DiskROM.hpp +++ b/Machines/MSX/DiskROM.hpp @@ -21,7 +21,7 @@ namespace MSX { -class DiskROM: public ROMSlotHandler, public WD::WD1770 { +class DiskROM: public MemorySlotHandler, public WD::WD1770 { public: DiskROM(const MSX::MemorySlot &slot); diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index cdc575252..a9a49d2fd 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -790,7 +790,7 @@ class ConcreteMachine: /// via the primary slot register. struct MemorySlot: public MSX::MemorySlot { HalfCycles cycles_since_update; - std::unique_ptr handler; + std::unique_ptr handler; }; MemorySlot memory_slots_[4]; MemorySlot *final_slot_ = nullptr; diff --git a/Machines/MSX/MemorySlotHandler.hpp b/Machines/MSX/MemorySlotHandler.hpp index 3f0e54000..e87fa1c16 100644 --- a/Machines/MSX/MemorySlotHandler.hpp +++ b/Machines/MSX/MemorySlotHandler.hpp @@ -70,7 +70,7 @@ class MemorySlot { std::size_t length); /// Marks the region indicated by @c destination_address and @c length - /// as unmapped. In practical terms that means that a @c ROMSlotHandler + /// as unmapped. In practical terms that means that a @c MemorySlotHandler /// will be used to field accesses to that area, allowing for areas that are not /// backed by memory to be modelled. void unmap( @@ -89,9 +89,9 @@ class MemorySlot { inline static MemoryChunk scratch; }; -class ROMSlotHandler { +class MemorySlotHandler { public: - virtual ~ROMSlotHandler() {} + virtual ~MemorySlotHandler() {} /*! Advances time by @c half_cycles. */ virtual void run_for([[maybe_unused]] HalfCycles half_cycles) {} From 78ce439b9b80a0533292e42c4a72d331a5b80b46 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 12 Jan 2023 23:08:01 -0500 Subject: [PATCH 13/34] Add missing header; correct type. --- Machines/MSX/MSX.cpp | 2 +- Machines/MSX/MemorySlotHandler.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index a9a49d2fd..ca02aa4d2 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -400,7 +400,7 @@ class ConcreteMachine: // secondary slot communication. final_slot_ = &memory_slots_[primary >> 6]; - for(std::size_t c = 0; c < 8; c += 2) { + for(int c = 0; c < 8; c += 2) { const MemorySlot &slot = memory_slots_[primary & 3]; primary >>= 2; diff --git a/Machines/MSX/MemorySlotHandler.cpp b/Machines/MSX/MemorySlotHandler.cpp index 4ab1985a4..4d6365be0 100644 --- a/Machines/MSX/MemorySlotHandler.cpp +++ b/Machines/MSX/MemorySlotHandler.cpp @@ -8,6 +8,8 @@ #include "MemorySlotHandler.hpp" +#include + using namespace MSX; MemorySlot::MemorySlot() { From 23ff3fc36627b84d454396e9049fd487548e0b57 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 13 Jan 2023 08:05:12 -0500 Subject: [PATCH 14/34] Ensure all routes go somewhere. --- Machines/MasterSystem/MasterSystem.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index 1a4c38be0..ad4761926 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -564,6 +564,7 @@ Machine *Machine::MasterSystem(const Analyser::Static::Target *target, const ROM case Target::Model::MasterSystem2: return new ConcreteMachine(*sega_target, rom_fetcher); default: assert(false); + return nullptr; } } From befc81743aeef8b820de6bc484368ca712f4ffeb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 13 Jan 2023 09:31:56 -0500 Subject: [PATCH 15/34] Fix base RAM mapping. --- Machines/MSX/MSX.cpp | 2 +- Machines/MSX/MemorySlotHandler.cpp | 12 +++++++++++- Machines/MSX/MemorySlotHandler.hpp | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index ca02aa4d2..7f71610be 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -255,7 +255,7 @@ class ConcreteMachine: } memory_slots_[0].map(0, 0, 0, 32768); - memory_slots_[3].map(0, 0, 0, 65536); + memory_slots_[3].template map(0, 0, 0, 65536); // Add a disk cartridge if any disks were supplied. if(target.has_disk_drive) { diff --git a/Machines/MSX/MemorySlotHandler.cpp b/Machines/MSX/MemorySlotHandler.cpp index 4d6365be0..5c039c8f1 100644 --- a/Machines/MSX/MemorySlotHandler.cpp +++ b/Machines/MSX/MemorySlotHandler.cpp @@ -53,6 +53,7 @@ const std::vector &MemorySlot::source() const { return source_; } +template void MemorySlot::map(int subslot, std::size_t source_address, uint16_t destination_address, std::size_t length) { assert(!(destination_address & 8191)); assert(!(length & 8191)); @@ -60,7 +61,13 @@ void MemorySlot::map(int subslot, std::size_t source_address, uint16_t destinati for(std::size_t c = 0; c < (length >> 13); ++c) { source_address %= source_.size(); - read_pointers_[subslot][(destination_address >> 13) + c] = &source_[source_address]; + + const int bank = int((destination_address >> 13) + c); + read_pointers_[subslot][bank] = &source_[source_address]; + if constexpr (type == AccessType::ReadWrite) { + write_pointers_[subslot][bank] = read_pointers_[subslot][bank]; + } + source_address += 8192; } @@ -79,3 +86,6 @@ void MemorySlot::unmap(int subslot, uint16_t destination_address, std::size_t le // TODO: need to indicate that mapping changed. } + +template void MemorySlot::map(int subslot, std::size_t source_address, uint16_t destination_address, std::size_t length); +template void MemorySlot::map(int subslot, std::size_t source_address, uint16_t destination_address, std::size_t length); diff --git a/Machines/MSX/MemorySlotHandler.hpp b/Machines/MSX/MemorySlotHandler.hpp index e87fa1c16..3a1eefa8f 100644 --- a/Machines/MSX/MemorySlotHandler.hpp +++ b/Machines/MSX/MemorySlotHandler.hpp @@ -60,10 +60,15 @@ class MemorySlot { /// Provides a reference to the internal source storage. const std::vector &source() const; + enum AccessType { + Read, + ReadWrite + }; + /// Maps the content from @c source_address in the buffer previously /// supplied to @c set_source to the region indicated by /// @c destination_address and @c length within @c subslot. - void map( + template void map( int subslot, std::size_t source_address, uint16_t destination_address, From 4190d25698d5b9ed12e29261d265f195764f027a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 13 Jan 2023 14:07:54 -0500 Subject: [PATCH 16/34] Ensure RAM is properly sized and available. --- Machines/MSX/DiskROM.cpp | 2 +- Machines/MSX/DiskROM.hpp | 2 +- Machines/MSX/MSX.cpp | 65 +++++++++++++++++------------- Machines/MSX/MemorySlotHandler.cpp | 15 +++---- Machines/MSX/MemorySlotHandler.hpp | 12 ++++-- 5 files changed, 55 insertions(+), 41 deletions(-) diff --git a/Machines/MSX/DiskROM.cpp b/Machines/MSX/DiskROM.cpp index 97d7a57aa..1311d99f0 100644 --- a/Machines/MSX/DiskROM.cpp +++ b/Machines/MSX/DiskROM.cpp @@ -10,7 +10,7 @@ using namespace MSX; -DiskROM::DiskROM(const MSX::MemorySlot &slot) : +DiskROM::DiskROM(MSX::MemorySlot &slot) : WD1770(P1793), rom_(slot.source()) { emplace_drives(2, 8000000, 300, 2); diff --git a/Machines/MSX/DiskROM.hpp b/Machines/MSX/DiskROM.hpp index 931668eb4..7d776b83f 100644 --- a/Machines/MSX/DiskROM.hpp +++ b/Machines/MSX/DiskROM.hpp @@ -23,7 +23,7 @@ namespace MSX { class DiskROM: public MemorySlotHandler, public WD::WD1770 { public: - DiskROM(const MSX::MemorySlot &slot); + DiskROM(MSX::MemorySlot &slot); void write(uint16_t address, uint8_t value, bool pc_is_outside_bios) final; uint8_t read(uint16_t address) final; diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 7f71610be..aeeb2b517 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -143,6 +143,11 @@ class ConcreteMachine: public Configurable::Device, public ClockingHint::Observer, public Activity::Source { + private: + static constexpr int RAMMemorySlot = 3; + static constexpr int RAMMemorySubSlot = 0; + + public: ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher): z80_(*this), @@ -156,7 +161,6 @@ class ConcreteMachine: i8255_port_handler_(*this, audio_toggle_, tape_player_), ay_port_handler_(tape_player_) { set_clock_rate(3579545); - std::memset(unpopulated_, 0xff, sizeof(unpopulated_)); clear_all_keys(); ay_.set_port_handler(&ay_port_handler_); @@ -234,11 +238,17 @@ class ConcreteMachine: // Figure out which BIOS to use, either a specific one or the generic // one appropriately patched. - const auto regional_bios = roms.find(regional_bios_name); - if(regional_bios != roms.end()) { - regional_bios->second.resize(32768); - memory_slots_[0].set_source(regional_bios->second); - } else { + bool has_bios = false; + if constexpr (model == Target::Model::MSX1) { + const auto regional_bios = roms.find(regional_bios_name); + if(regional_bios != roms.end()) { + regional_bios->second.resize(32768); + memory_slots_[0].set_source(regional_bios->second); + has_bios = true; + } + } + + if(!has_bios) { std::vector &bios = roms.find(bios_name)->second; bios.resize(32768); @@ -255,7 +265,9 @@ class ConcreteMachine: } memory_slots_[0].map(0, 0, 0, 32768); - memory_slots_[3].template map(0, 0, 0, 65536); + + memory_slots_[RAMMemorySlot].resize_source(65536); + memory_slots_[RAMMemorySlot].template map(RAMMemorySubSlot, 0, 0, 65536); // Add a disk cartridge if any disks were supplied. if(target.has_disk_drive) { @@ -447,8 +459,8 @@ class ConcreteMachine: using Parser = Storage::Tape::MSX::Parser; std::unique_ptr new_speed = Parser::find_header(tape_player_); if(new_speed) { - ram_[0xfca4] = new_speed->minimum_start_bit_duration; - ram_[0xfca5] = new_speed->low_high_disrimination_duration; + ram()[0xfca4] = new_speed->minimum_start_bit_duration; + ram()[0xfca5] = new_speed->low_high_disrimination_duration; z80_.set_value_of_register(CPU::Z80::Register::Flags, 0); } else { z80_.set_value_of_register(CPU::Z80::Register::Flags, 1); @@ -465,8 +477,8 @@ class ConcreteMachine: // Grab the current values of LOWLIM and WINWID. using Parser = Storage::Tape::MSX::Parser; Parser::FileSpeed tape_speed; - tape_speed.minimum_start_bit_duration = ram_[0xfca4]; - tape_speed.low_high_disrimination_duration = ram_[0xfca5]; + tape_speed.minimum_start_bit_duration = ram()[0xfca4]; + tape_speed.low_high_disrimination_duration = ram()[0xfca5]; // Ask the tape parser to grab a byte. int next_byte = Parser::get_byte(tape_speed, tape_player_); @@ -489,9 +501,12 @@ class ConcreteMachine: if(!address) { pc_zero_accesses_++; } - if(read_pointers_[address >> 13] == unpopulated_) { - performed_unmapped_access_ = true; - } + + // TODO: below relates to confidence measurements. Reinstate, somehow. +// if(is_unpopulated_[address >> 13] == unpopulated_) { +// performed_unmapped_access_ = true; +// } + pc_address_ = address; // This is retained so as to be able to name the source of an access to cartridge handlers. [[fallthrough]]; @@ -587,8 +602,8 @@ class ConcreteMachine: const int buffer_size = 40; // Also from the Red Book: GETPNT is at F3FAH and PUTPNT is at F3F8H. - int read_address = ram_[0xf3fa] | (ram_[0xf3fb] << 8); - int write_address = ram_[0xf3f8] | (ram_[0xf3f9] << 8); + int read_address = ram()[0xf3fa] | (ram()[0xf3fb] << 8); + int write_address = ram()[0xf3f8] | (ram()[0xf3f9] << 8); // Write until either the string is exhausted or the write_pointer is immediately // behind the read pointer; temporarily map write_address and read_address into @@ -599,7 +614,7 @@ class ConcreteMachine: while(characters_written < input_text_.size()) { const int next_write_address = (write_address + 1) % buffer_size; if(next_write_address == read_address) break; - ram_[write_address + buffer_start] = uint8_t(input_text_[characters_written]); + ram()[write_address + buffer_start] = uint8_t(input_text_[characters_written]); ++characters_written; write_address = next_write_address; } @@ -607,8 +622,8 @@ class ConcreteMachine: // Map the write address back into absolute terms and write it out again as PUTPNT. write_address += buffer_start; - ram_[0xf3f8] = uint8_t(write_address); - ram_[0xf3f9] = uint8_t(write_address >> 8); + ram()[0xf3f8] = uint8_t(write_address); + ram()[0xf3f9] = uint8_t(write_address >> 8); } break; @@ -690,6 +705,9 @@ class ConcreteMachine: } private: + uint8_t *ram() { + return memory_slots_[RAMMemorySlot].source().data(); + } DiskROM *get_disk_rom() { return dynamic_cast(memory_slots_[2].handler.get()); } @@ -795,15 +813,6 @@ class ConcreteMachine: MemorySlot memory_slots_[4]; MemorySlot *final_slot_ = nullptr; - /// Base RAM. - uint8_t ram_[65536]; - - /// A never-read area that writes for unmapped regions can be diverted to. - uint8_t scratch_[8192]; - - /// A never-written area that reads for unmapped regions can be sourced from. - uint8_t unpopulated_[8192]; - HalfCycles time_since_ay_update_; uint8_t key_states_[16]; diff --git a/Machines/MSX/MemorySlotHandler.cpp b/Machines/MSX/MemorySlotHandler.cpp index 5c039c8f1..28f160af1 100644 --- a/Machines/MSX/MemorySlotHandler.cpp +++ b/Machines/MSX/MemorySlotHandler.cpp @@ -39,16 +39,18 @@ uint8_t *MemorySlot::write_pointer(int segment) const { return write_pointers_[subslot][segment]; } -void MemorySlot::apply_mapping(uint8_t port, uint8_t value) { - // TODO. - (void)port; - (void)value; -} - void MemorySlot::set_source(const std::vector &source) { source_ = source; } +void MemorySlot::resize_source(std::size_t size) { + source_.resize(size); +} + +std::vector &MemorySlot::source() { + return source_; +} + const std::vector &MemorySlot::source() const { return source_; } @@ -71,7 +73,6 @@ void MemorySlot::map(int subslot, std::size_t source_address, uint16_t destinati source_address += 8192; } - // TODO: allow write_pointers_ to be set. // TODO: need to indicate that mapping changed. } diff --git a/Machines/MSX/MemorySlotHandler.hpp b/Machines/MSX/MemorySlotHandler.hpp index 3a1eefa8f..d20cba8a9 100644 --- a/Machines/MSX/MemorySlotHandler.hpp +++ b/Machines/MSX/MemorySlotHandler.hpp @@ -50,14 +50,14 @@ class MemorySlot { /// @returns A pointer to the area of memory currently underneath @c address. uint8_t *write_pointer(int segment) const; - /// Sets the value most-recently written to one of the standard - /// memory mapping ports, FC–FF. - void apply_mapping(uint8_t port, uint8_t value); - /// Copies an underlying source buffer. void set_source(const std::vector &source); + /// Sets the size of the underlying source buffer. + void resize_source(std::size_t); + /// Provides a reference to the internal source storage. + std::vector &source(); const std::vector &source() const; enum AccessType { @@ -107,6 +107,10 @@ class MemorySlotHandler { /*! Seeks the result of a read at @c address; this is used only if the area is unmapped. */ virtual uint8_t read([[maybe_unused]] uint16_t address) { return 0xff; } + /// Sets the value most-recently written to one of the standard + /// memory mapping ports, FC–FF. + virtual void apply_mapping([[maybe_unused]] uint8_t port, [[maybe_unused]] uint8_t value) {} + /*! @returns The probability that this handler is correct for the data it owns. */ float get_confidence() { return confidence_counter_.get_confidence(); From 9f450b3ccbb0c0a3b7235459118a5384d1a3af2b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 13 Jan 2023 14:16:12 -0500 Subject: [PATCH 17/34] Expose the extension ROM to an MSX 2. --- Machines/MSX/MSX.cpp | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index aeeb2b517..9b5c4b363 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -147,6 +147,10 @@ class ConcreteMachine: static constexpr int RAMMemorySlot = 3; static constexpr int RAMMemorySubSlot = 0; + static constexpr int ExtensionROMSubSlot = 1; + + // Provide 512kb of memory for an MSX 2; 64kb for an MSX 1. 'Slightly' arbitrary. + static constexpr size_t RAMSize = model == Target::Model::MSX2 ? 512 * 1024 : 64 * 1024; public: ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher): @@ -247,7 +251,6 @@ class ConcreteMachine: has_bios = true; } } - if(!has_bios) { std::vector &bios = roms.find(bios_name)->second; @@ -266,8 +269,24 @@ class ConcreteMachine: memory_slots_[0].map(0, 0, 0, 32768); - memory_slots_[RAMMemorySlot].resize_source(65536); - memory_slots_[RAMMemorySlot].template map(RAMMemorySubSlot, 0, 0, 65536); + if constexpr (model == Target::Model::MSX2) { + // If there's an extension ROM, add it as a subslot in the same slot as RAM. + std::vector ram_plus; + + ram_plus.resize(RAMSize); + + const auto extension = roms.find(ROM::Name::MSX2Extension); + std::copy(extension->second.begin(), extension->second.end(), std::back_inserter(ram_plus)); + + memory_slots_[RAMMemorySlot].supports_secondary_paging = true; + memory_slots_[RAMMemorySlot].set_source(ram_plus); + + memory_slots_[RAMMemorySlot].template map(RAMMemorySubSlot, 0, 0, 65536); + memory_slots_[RAMMemorySlot].map(ExtensionROMSubSlot, 0, 0, 32768); + } else { + memory_slots_[RAMMemorySlot].resize_source(65536); + memory_slots_[RAMMemorySlot].template map(RAMMemorySubSlot, RAMSize, 0, 65536); + } // Add a disk cartridge if any disks were supplied. if(target.has_disk_drive) { From 50b5122969fafda3c2be42f1d562d99705ba4a38 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 13 Jan 2023 14:18:39 -0500 Subject: [PATCH 18/34] For an MSX 2, the extension ROM is obligatory. --- Machines/MSX/MSX.cpp | 2 +- ROMImages/MSX/readme.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 9b5c4b363..43e910c8b 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -180,7 +180,7 @@ class ConcreteMachine: ROM::Request bios_request = ROM::Request(bios_name); if constexpr (model == Target::Model::MSX2) { - bios_request = bios_request || ROM::Request(ROM::Name::MSX2Extension); + bios_request = bios_request && ROM::Request(ROM::Name::MSX2Extension); } bool is_ntsc = true; diff --git a/ROMImages/MSX/readme.txt b/ROMImages/MSX/readme.txt index da327ecbb..b30d35fde 100644 --- a/ROMImages/MSX/readme.txt +++ b/ROMImages/MSX/readme.txt @@ -4,6 +4,7 @@ Minimum expected files: msx.rom msx2.rom +msx2ext.rom disk.rom These names match those offered for download at http://fms.komkon.org/fMSX/ (albeit in lowercase), and the emulator has been tested against those images. From fb0241cf6e887d3923281a8c7a1925b577dd8961 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 13 Jan 2023 14:30:17 -0500 Subject: [PATCH 19/34] Be overt about alignment. --- Numeric/RegisterSizes.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Numeric/RegisterSizes.hpp b/Numeric/RegisterSizes.hpp index 2c0363595..c0a86b92f 100644 --- a/Numeric/RegisterSizes.hpp +++ b/Numeric/RegisterSizes.hpp @@ -19,7 +19,7 @@ namespace CPU { /// Provides access to all intermediate parts of a larger int. -template union RegisterPair { +template union alignas(Full) RegisterPair { RegisterPair(Full v) : full(v) {} RegisterPair() {} From f0a4d1d8ec9e373e443dcaeb24110df457f70a3b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 13 Jan 2023 21:54:59 -0500 Subject: [PATCH 20/34] Wire up did-page notifications. --- Machines/MSX/MSX.cpp | 12 ++++++++++-- Machines/MSX/MemorySlotHandler.cpp | 6 +++--- Machines/MSX/MemorySlotHandler.hpp | 8 +++++++- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 43e910c8b..f1d0ce280 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -142,7 +142,8 @@ class ConcreteMachine: public MachineTypes::JoystickMachine, public Configurable::Device, public ClockingHint::Observer, - public Activity::Source { + public Activity::Source, + public MSX::MemorySlotChangeHandler { private: static constexpr int RAMMemorySlot = 3; static constexpr int RAMMemorySubSlot = 0; @@ -163,7 +164,8 @@ class ConcreteMachine: speaker_(mixer_), tape_player_(3579545 * 2), i8255_port_handler_(*this, audio_toggle_, tape_player_), - ay_port_handler_(tape_player_) { + ay_port_handler_(tape_player_), + memory_slots_{{*this}, {*this}, {*this}, {*this}} { set_clock_rate(3579545); clear_all_keys(); @@ -424,6 +426,10 @@ class ConcreteMachine: update_paging(); } + void did_page() final { + update_paging(); + } + void update_paging() { uint8_t primary = primary_slots_; @@ -826,6 +832,8 @@ class ConcreteMachine: /// Optionally attaches non-default logic to any of the four things selectable /// via the primary slot register. struct MemorySlot: public MSX::MemorySlot { + using MSX::MemorySlot::MemorySlot; + HalfCycles cycles_since_update; std::unique_ptr handler; }; diff --git a/Machines/MSX/MemorySlotHandler.cpp b/Machines/MSX/MemorySlotHandler.cpp index 28f160af1..6e5f503ae 100644 --- a/Machines/MSX/MemorySlotHandler.cpp +++ b/Machines/MSX/MemorySlotHandler.cpp @@ -12,7 +12,7 @@ using namespace MSX; -MemorySlot::MemorySlot() { +MemorySlot::MemorySlot(MemorySlotChangeHandler &handler) : handler_(handler) { for(int subslot = 0; subslot < 4; subslot++) { for(int region = 0; region < 8; region++) { read_pointers_[subslot][region] = unmapped.data(); @@ -73,7 +73,7 @@ void MemorySlot::map(int subslot, std::size_t source_address, uint16_t destinati source_address += 8192; } - // TODO: need to indicate that mapping changed. + handler_.did_page(); } void MemorySlot::unmap(int subslot, uint16_t destination_address, std::size_t length) { @@ -85,7 +85,7 @@ void MemorySlot::unmap(int subslot, uint16_t destination_address, std::size_t le read_pointers_[subslot][(destination_address >> 13) + c] = nullptr; } - // TODO: need to indicate that mapping changed. + handler_.did_page(); } template void MemorySlot::map(int subslot, std::size_t source_address, uint16_t destination_address, std::size_t length); diff --git a/Machines/MSX/MemorySlotHandler.hpp b/Machines/MSX/MemorySlotHandler.hpp index d20cba8a9..cac6ed032 100644 --- a/Machines/MSX/MemorySlotHandler.hpp +++ b/Machines/MSX/MemorySlotHandler.hpp @@ -30,9 +30,13 @@ */ namespace MSX { +struct MemorySlotChangeHandler { + virtual void did_page() = 0; +}; + class MemorySlot { public: - MemorySlot(); + MemorySlot(MemorySlotChangeHandler &); /// Attempts to write the argument as the secondary paging selection. void set_secondary_paging(uint8_t); @@ -89,6 +93,8 @@ class MemorySlot { uint8_t *write_pointers_[4][8]; uint8_t secondary_paging_ = 0; + MemorySlotChangeHandler &handler_; + using MemoryChunk = std::array; inline static MemoryChunk unmapped{0xff}; inline static MemoryChunk scratch; From 5f85074caafae4e04842e5e74e9f048c1eec5759 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 13 Jan 2023 22:02:15 -0500 Subject: [PATCH 21/34] Restore repeated lookup of timed machine. This restores culling of abandoned parallel machines during dynamic analysis. --- OSBindings/Mac/Clock Signal/Machine/CSMachine.mm | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm index ebb198f87..56368bc7b 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm @@ -43,10 +43,12 @@ struct MachineUpdater { // Top out at 1/20th of a second; this is a safeguard against a negative // feedback loop if emulation starts running slowly. const auto seconds = std::min(Time::seconds(duration), 0.05); - machine->run_for(seconds); + timed_machine = machine->timed_machine(); + timed_machine->run_for(seconds); } - MachineTypes::TimedMachine *machine = nullptr; + Machine::DynamicMachine *machine = nullptr; + MachineTypes::TimedMachine *timed_machine = nullptr; }; } @@ -148,7 +150,7 @@ struct ActivityObserver: public Activity::Observer { [missingROMs appendString:[NSString stringWithUTF8String:wstring_converter.to_bytes(description).c_str()]]; return nil; } - updater.performer.machine = _machine->timed_machine(); + updater.performer.machine = _machine.get(); if(updater.performer.machine) { updater.start(); } @@ -687,7 +689,7 @@ struct ActivityObserver: public Activity::Observer { updater.enqueue([weakSelf] { CSMachine *const strongSelf = weakSelf; if(strongSelf) { - strongSelf->updater.performer.machine->flush_output(MachineTypes::TimedMachine::Output::Audio); + strongSelf->updater.performer.timed_machine->flush_output(MachineTypes::TimedMachine::Output::Audio); } }); } @@ -703,7 +705,7 @@ struct ActivityObserver: public Activity::Observer { // Grab a pointer to the timed machine from somewhere where it has already // been dynamically cast, to avoid that cost here. - MachineTypes::TimedMachine *const timed_machine = strongSelf->updater.performer.machine; + MachineTypes::TimedMachine *const timed_machine = strongSelf->updater.performer.timed_machine; // Definitely update video; update audio too if that pipeline is looking a little dry. auto outputs = MachineTypes::TimedMachine::Output::Video; From 18def0c97de0342bea737b71caa4b93f610e43a0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 13 Jan 2023 22:22:58 -0500 Subject: [PATCH 22/34] Correct extension ROM visibility. --- Machines/MSX/MSX.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index f1d0ce280..8699517d2 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -284,10 +284,10 @@ class ConcreteMachine: memory_slots_[RAMMemorySlot].set_source(ram_plus); memory_slots_[RAMMemorySlot].template map(RAMMemorySubSlot, 0, 0, 65536); - memory_slots_[RAMMemorySlot].map(ExtensionROMSubSlot, 0, 0, 32768); + memory_slots_[RAMMemorySlot].map(ExtensionROMSubSlot, RAMSize, 0, 32768); } else { memory_slots_[RAMMemorySlot].resize_source(65536); - memory_slots_[RAMMemorySlot].template map(RAMMemorySubSlot, RAMSize, 0, 65536); + memory_slots_[RAMMemorySlot].template map(RAMMemorySubSlot, 0, 0, 65536); } // Add a disk cartridge if any disks were supplied. @@ -612,7 +612,9 @@ class ConcreteMachine: break; case 0xfc: case 0xfd: case 0xfe: case 0xff: - // printf("RAM banking %02x: %02x\n", port, *cycle.value); + // 1. Propagate to all handlers. + // 2. Apply to RAM. +// printf("RAM banking %02x: %02x\n", port, *cycle.value); break; } } break; From 3bc38d35c9f44b6c668f7423b40389e88c249202 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 14 Jan 2023 14:16:56 -0500 Subject: [PATCH 23/34] Fix include order. --- Components/68901/MFP68901.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Components/68901/MFP68901.hpp b/Components/68901/MFP68901.hpp index 6f320a214..f1bda90dd 100644 --- a/Components/68901/MFP68901.hpp +++ b/Components/68901/MFP68901.hpp @@ -9,10 +9,11 @@ #ifndef MFP68901_hpp #define MFP68901_hpp -#include #include "../../ClockReceiver/ClockReceiver.hpp" #include "../../ClockReceiver/ClockingHintSource.hpp" +#include + namespace Motorola { namespace MFP68901 { From 48a435559217fe984638c437831d3fd614c434cc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 14 Jan 2023 14:17:28 -0500 Subject: [PATCH 24/34] Start sketching out an RP5C01. --- Components/RP5C01/RP5C01.cpp | 100 ++++++++++++++++++ Components/RP5C01/RP5C01.hpp | 62 +++++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 16 +++ 3 files changed, 178 insertions(+) create mode 100644 Components/RP5C01/RP5C01.cpp create mode 100644 Components/RP5C01/RP5C01.hpp diff --git a/Components/RP5C01/RP5C01.cpp b/Components/RP5C01/RP5C01.cpp new file mode 100644 index 000000000..8932c6c6e --- /dev/null +++ b/Components/RP5C01/RP5C01.cpp @@ -0,0 +1,100 @@ +// +// RP5C01.cpp +// Clock Signal +// +// Created by Thomas Harte on 14/01/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#include "RP5C01.hpp" + +using namespace Ricoh::RP5C01; + +RP5C01::RP5C01(HalfCycles clock_rate) : clock_rate_(clock_rate) {} + +void RP5C01::run_for(HalfCycles cycles) { + sub_seconds_ += cycles; + + // Guess: this happens so rarely (i.e. once a second, ordinarily) that + // it's not worth worrying about the branch prediction consequences. + // + // ... and ditto all the conditionals below, which will be very rarely reached. + if(sub_seconds_ < clock_rate_) { + return; + } + const auto elapsed_seconds = int(sub_seconds_.as_integral() / clock_rate_.as_integral()); + sub_seconds_ %= clock_rate_; + + // Update time within day. + seconds_ += elapsed_seconds; + + constexpr int day_length = 60 * 60 * 24; + if(seconds_ < day_length) { + return; + } + const int elapsed_days = seconds_ / day_length; + seconds_ %= day_length; + + // Day of the week doesn't aggregate upwards. + day_of_the_week_ = (day_of_the_week_ + elapsed_days) % 7; + + // Assumed for now: day and month run from 0. + // A leap year count of 0 implies a leap year. + // TODO: verify. + day_ += elapsed_days; + while(true) { + int month_length = 1; + switch(month_) { + case 0: month_length = 31; break; + case 1: month_length = 28 + !leap_year_; break; + case 2: month_length = 31; break; + case 3: month_length = 30; break; + case 4: month_length = 31; break; + case 5: month_length = 30; break; + case 6: month_length = 31; break; + case 7: month_length = 31; break; + case 8: month_length = 30; break; + case 9: month_length = 31; break; + case 10: month_length = 30; break; + case 11: month_length = 31; break; + } + + if(day_ < month_length) { + return; + } + + day_ -= month_length; + ++month_; + + if(month_ == 12) { + month_ = 0; + ++year_; + leap_year_ = (leap_year_ + 1) & 3; + } + } +} + +/// Performs a write of @c value to @c address. +void RP5C01::write(int address, uint8_t value) { + // Registers D–F don't depend on the mode. + if(address >= 0xd) { + switch(address) { + default: break; + case 0xd: + timer_enabled_ = value & 0x8; + alarm_enabled_ = value & 0x4; + mode_ = value & 0x3; + break; + case 0xe: + // Test register; unclear what is supposed to happen. + break; + case 0xf: + one_hz_on_ = !(value & 0x8); + sixteen_hz_on_ = !(value & 0x4); + // TODO: timer reset on bit 1, alarm reset on bit 0 + break; + } + + return; + } +} diff --git a/Components/RP5C01/RP5C01.hpp b/Components/RP5C01/RP5C01.hpp new file mode 100644 index 000000000..24c1ad38b --- /dev/null +++ b/Components/RP5C01/RP5C01.hpp @@ -0,0 +1,62 @@ +// +// RP5C01.hpp +// Clock Signal +// +// Created by Thomas Harte on 14/01/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef RP5C01_hpp +#define RP5C01_hpp + +#include "../../ClockReceiver/ClockReceiver.hpp" + +#include +#include + +namespace Ricoh { +namespace RP5C01 { + +class RP5C01 { + public: + RP5C01(HalfCycles clock_rate); + + /// @returns the result of a read from @c address. + uint8_t read(int address); + + /// Performs a write of @c value to @c address. + void write(int address, uint8_t value); + + /// Advances time. + void run_for(HalfCycles); + + private: + std::array ram_; + + HalfCycles sub_seconds_; + const HalfCycles clock_rate_; + + // Contains the seconds, minutes and hours fields. + int seconds_ = 0; + + // Calendar entries. + int day_of_the_week_ = 0; + int day_ = 0; + int month_ = 0; + int year_ = 1988; + int leap_year_ = 0; + + // Other flags. + bool timer_enabled_ = false; + bool alarm_enabled_ = false; + int mode_ = 0; + bool one_hz_on_ = false; + bool sixteen_hz_on_ = false; +}; + +} +} + +#include + +#endif /* RP5C01_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 996493baf..3844079a6 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1064,6 +1064,8 @@ 4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; }; 4BF0BC68297108D600CCA2B5 /* MemorySlotHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF0BC67297108D100CCA2B5 /* MemorySlotHandler.cpp */; }; 4BF0BC69297108D600CCA2B5 /* MemorySlotHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF0BC67297108D100CCA2B5 /* MemorySlotHandler.cpp */; }; + 4BF0BC712973318E00CCA2B5 /* RP5C01.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF0BC6F2973318E00CCA2B5 /* RP5C01.cpp */; }; + 4BF0BC722973318E00CCA2B5 /* RP5C01.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF0BC6F2973318E00CCA2B5 /* RP5C01.cpp */; }; 4BF437EE209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; }; 4BF437EF209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; }; 4BF701A026FFD32300996424 /* AmigaBlitterTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BF7019F26FFD32300996424 /* AmigaBlitterTests.mm */; }; @@ -2211,6 +2213,8 @@ 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DigitalPhaseLockedLoopBridge.mm; sourceTree = ""; }; 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DPLLTests.swift; sourceTree = ""; }; 4BF0BC67297108D100CCA2B5 /* MemorySlotHandler.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MemorySlotHandler.cpp; sourceTree = ""; }; + 4BF0BC6F2973318E00CCA2B5 /* RP5C01.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RP5C01.cpp; sourceTree = ""; }; + 4BF0BC702973318E00CCA2B5 /* RP5C01.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RP5C01.hpp; sourceTree = ""; }; 4BF40A5525424C770033EA39 /* LanguageCardSwitches.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LanguageCardSwitches.hpp; sourceTree = ""; }; 4BF40A5A254263140033EA39 /* AuxiliaryMemorySwitches.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AuxiliaryMemorySwitches.hpp; sourceTree = ""; }; 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SegmentParser.cpp; sourceTree = ""; }; @@ -4579,6 +4583,7 @@ 4B302181208A550100773308 /* DiskII */, 4B4B1A39200198C900A0F866 /* KonamiSCC */, 4BC23A212467600E001A6030 /* OPx */, + 4BF0BC6E2973318E00CCA2B5 /* RP5C01 */, 4B0ACBFF237756EC008902D0 /* Serial */, 4BB0A6582044FD3000FB3688 /* SN76489 */, ); @@ -4948,6 +4953,15 @@ path = Formats; sourceTree = ""; }; + 4BF0BC6E2973318E00CCA2B5 /* RP5C01 */ = { + isa = PBXGroup; + children = ( + 4BF0BC6F2973318E00CCA2B5 /* RP5C01.cpp */, + 4BF0BC702973318E00CCA2B5 /* RP5C01.hpp */, + ); + path = RP5C01; + sourceTree = ""; + }; 4BF660691F281573002CB053 /* ClockReceiver */ = { isa = PBXGroup; children = ( @@ -5630,6 +5644,7 @@ 4B5B37322777C7FC0047F238 /* IPF.cpp in Sources */, 4B894519201967B4007DE474 /* ConfidenceCounter.cpp in Sources */, 4B055AEE1FAE9BBF0060FFFF /* Keyboard.cpp in Sources */, + 4BF0BC722973318E00CCA2B5 /* RP5C01.cpp in Sources */, 4B055AED1FAE9BA20060FFFF /* Z80Storage.cpp in Sources */, 4B1B88BC202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */, 4BC890D4230F86020025A55A /* DirectAccessDevice.cpp in Sources */, @@ -5983,6 +5998,7 @@ 4B83348A1F5DB94B0097E338 /* IRQDelegatePortHandler.cpp in Sources */, 4B8DD3862634D37E00B3C866 /* SNA.cpp in Sources */, 4B4DEC06252BFA56004583AC /* 65816Base.cpp in Sources */, + 4BF0BC712973318E00CCA2B5 /* RP5C01.cpp in Sources */, 4B894524201967B4007DE474 /* Tape.cpp in Sources */, 4B7136891F78725F008B8ED9 /* Shifter.cpp in Sources */, 4BDB61EB2032806E0048AF91 /* CSAtari2600.mm in Sources */, From f57c2a961faf3066b2feaf95195fb486926b8061 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 14 Jan 2023 14:20:14 -0500 Subject: [PATCH 25/34] Add to further project files. --- OSBindings/Qt/clksignal.pro | 2 ++ OSBindings/SDL/SConstruct | 1 + 2 files changed, 3 insertions(+) diff --git a/OSBindings/Qt/clksignal.pro b/OSBindings/Qt/clksignal.pro index a3c341a97..ffbbb5fba 100644 --- a/OSBindings/Qt/clksignal.pro +++ b/OSBindings/Qt/clksignal.pro @@ -73,6 +73,7 @@ SOURCES += \ $$SRC/Components/OPx/*.cpp \ $$SRC/Components/SN76489/*.cpp \ $$SRC/Components/Serial/*.cpp \ + $$SRC/Components/RP5C01/*.cpp \ \ $$SRC/Inputs/*.cpp \ \ @@ -202,6 +203,7 @@ HEADERS += \ $$SRC/Components/OPx/Implementation/*.hpp \ $$SRC/Components/Serial/*.hpp \ $$SRC/Components/SN76489/*.hpp \ + $$SRC/Components/RP5C01/*.hpp \ \ $$SRC/Concurrency/*.hpp \ \ diff --git a/OSBindings/SDL/SConstruct b/OSBindings/SDL/SConstruct index b4cb26add..6b1e6e849 100644 --- a/OSBindings/SDL/SConstruct +++ b/OSBindings/SDL/SConstruct @@ -55,6 +55,7 @@ SOURCES += glob.glob('../../Components/AY38910/*.cpp') SOURCES += glob.glob('../../Components/DiskII/*.cpp') SOURCES += glob.glob('../../Components/KonamiSCC/*.cpp') SOURCES += glob.glob('../../Components/OPx/*.cpp') +SOURCES += glob.glob('../../Components/RP5C01/*.cpp') SOURCES += glob.glob('../../Components/SN76489/*.cpp') SOURCES += glob.glob('../../Components/Serial/*.cpp') From 1e17fc71ab3bdeefc1e01d1590e5fd8e08a32ca3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 14 Jan 2023 14:52:07 -0500 Subject: [PATCH 26/34] Add an RP-5C01 to the MSX 2. --- Components/RP5C01/RP5C01.cpp | 13 +++++++++++ Machines/MSX/MSX.cpp | 44 ++++++++++++++++++++++++++++++++---- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/Components/RP5C01/RP5C01.cpp b/Components/RP5C01/RP5C01.cpp index 8932c6c6e..e0d4ee576 100644 --- a/Components/RP5C01/RP5C01.cpp +++ b/Components/RP5C01/RP5C01.cpp @@ -76,6 +76,8 @@ void RP5C01::run_for(HalfCycles cycles) { /// Performs a write of @c value to @c address. void RP5C01::write(int address, uint8_t value) { + address &= 0xf; + // Registers D–F don't depend on the mode. if(address >= 0xd) { switch(address) { @@ -97,4 +99,15 @@ void RP5C01::write(int address, uint8_t value) { return; } + + // TODO. + printf("RP-5C01 write of %d to %d in mode %d\n", value, address & 0xf, mode_); +} + +uint8_t RP5C01::read(int address) { + address &= 0xf; + + // TODO. + printf("RP-5C01 read from %d in mode %d\n", address & 0xf, mode_); + return 0xff; } diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 8699517d2..e67750033 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -23,10 +23,11 @@ #include "../../Processors/Z80/Z80.hpp" #include "../../Components/1770/1770.hpp" -#include "../../Components/9918/9918.hpp" #include "../../Components/8255/i8255.hpp" +#include "../../Components/9918/9918.hpp" #include "../../Components/AudioToggle/AudioToggle.hpp" #include "../../Components/AY38910/AY38910.hpp" +#include "../../Components/RP5C01/RP5C01.hpp" #include "../../Components/KonamiSCC/KonamiSCC.hpp" #include "../../Storage/Tape/Parsers/MSX.hpp" @@ -153,6 +154,8 @@ class ConcreteMachine: // Provide 512kb of memory for an MSX 2; 64kb for an MSX 1. 'Slightly' arbitrary. static constexpr size_t RAMSize = model == Target::Model::MSX2 ? 512 * 1024 : 64 * 1024; + static constexpr int ClockRate = 3579545; + public: ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher): z80_(*this), @@ -165,8 +168,9 @@ class ConcreteMachine: tape_player_(3579545 * 2), i8255_port_handler_(*this, audio_toggle_, tape_player_), ay_port_handler_(tape_player_), - memory_slots_{{*this}, {*this}, {*this}, {*this}} { - set_clock_rate(3579545); + memory_slots_{{*this}, {*this}, {*this}, {*this}}, + clock_(ClockRate) { + set_clock_rate(ClockRate); clear_all_keys(); ay_.set_port_handler(&ay_port_handler_); @@ -464,6 +468,10 @@ class ConcreteMachine: memory_slots_[2].cycles_since_update += total_length; memory_slots_[3].cycles_since_update += total_length; + if constexpr (model >= Target::Model::MSX2) { + clock_.run_for(total_length); + } + if(cycle.is_terminal()) { uint16_t address = cycle.address ? *cycle.address : 0x0000; switch(cycle.operation) { @@ -587,7 +595,15 @@ class ConcreteMachine: *cycle.value = i8255_.read(address); break; + case 0xb5: + if constexpr (model == Target::Model::MSX1) { + break; + } + *cycle.value = clock_.read(next_clock_register_); + break; + default: + printf("Unhandled read %02x\n", address & 0xff); *cycle.value = 0xff; break; } @@ -611,10 +627,27 @@ class ConcreteMachine: i8255_.write(address, *cycle.value); break; + case 0xb4: + if constexpr (model == Target::Model::MSX1) { + break; + } + next_clock_register_ = *cycle.value; + break; + case 0xb5: + if constexpr (model == Target::Model::MSX1) { + break; + } + clock_.write(next_clock_register_, *cycle.value); + break; + case 0xfc: case 0xfd: case 0xfe: case 0xff: // 1. Propagate to all handlers. // 2. Apply to RAM. -// printf("RAM banking %02x: %02x\n", port, *cycle.value); + printf("RAM banking %02x: %02x\n", port, *cycle.value); + break; + + default: + printf("Unhandled write %02x of %02x\n", address & 0xff, *cycle.value); break; } } break; @@ -853,6 +886,9 @@ class ConcreteMachine: int pc_zero_accesses_ = 0; bool performed_unmapped_access_ = false; uint16_t pc_address_; + + Ricoh::RP5C01::RP5C01 clock_; + int next_clock_register_ = 0; }; } From ced002125e98674a745d6aa417858e73f93fa6dd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 14 Jan 2023 14:58:12 -0500 Subject: [PATCH 27/34] Make a basic attempt at RAM. --- Components/RP5C01/RP5C01.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Components/RP5C01/RP5C01.cpp b/Components/RP5C01/RP5C01.cpp index e0d4ee576..88d8d2001 100644 --- a/Components/RP5C01/RP5C01.cpp +++ b/Components/RP5C01/RP5C01.cpp @@ -100,6 +100,15 @@ void RP5C01::write(int address, uint8_t value) { return; } + switch(mode_) { + case 3: + address += 13; + [[fallthrough]]; + case 2: + ram_[size_t(address)] = value & 0xf; + return; + } + // TODO. printf("RP-5C01 write of %d to %d in mode %d\n", value, address & 0xf, mode_); } @@ -107,6 +116,16 @@ void RP5C01::write(int address, uint8_t value) { uint8_t RP5C01::read(int address) { address &= 0xf; + if(address < 0xd) { + switch(mode_) { + case 3: + address += 13; + [[fallthrough]]; + case 2: + return 0xf0 | ram_[size_t(address)]; + } + } + // TODO. printf("RP-5C01 read from %d in mode %d\n", address & 0xf, mode_); return 0xff; From 68361913eecb69136ba57a6f581781fcd8e8c09e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 14 Jan 2023 22:05:59 -0500 Subject: [PATCH 28/34] Substitute VDP for the MSX 2. --- Machines/MSX/MSX.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index e67750033..8eafd8a7f 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -828,8 +828,15 @@ class ConcreteMachine: Activity::Observer *activity_observer_ = nullptr; }; + static constexpr TI::TMS::Personality vdp_model() { + switch(model) { + case Target::Model::MSX1: return TI::TMS::Personality::TMS9918A; + case Target::Model::MSX2: return TI::TMS::Personality::V9938; + } + } + CPU::Z80::Processor z80_; - JustInTimeActor> vdp_; + JustInTimeActor> vdp_; Intel::i8255::i8255 i8255_; Concurrency::AsyncTaskQueue audio_queue_; From 183cb519e79c18065bbe3c09044df1831b3465e8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 15 Jan 2023 22:51:17 -0500 Subject: [PATCH 29/34] Give autonomy to secondary slots. --- Machines/MSX/Cartridges/ASCII16kb.hpp | 4 +- Machines/MSX/Cartridges/ASCII8kb.hpp | 8 +- Machines/MSX/Cartridges/Konami.hpp | 6 +- Machines/MSX/Cartridges/KonamiWithSCC.hpp | 10 +- Machines/MSX/MSX.cpp | 126 ++++++++++++---------- Machines/MSX/MemorySlotHandler.cpp | 49 +++++---- Machines/MSX/MemorySlotHandler.hpp | 44 +++++--- 7 files changed, 146 insertions(+), 101 deletions(-) diff --git a/Machines/MSX/Cartridges/ASCII16kb.hpp b/Machines/MSX/Cartridges/ASCII16kb.hpp index 5cbe2822a..170cd3ac6 100644 --- a/Machines/MSX/Cartridges/ASCII16kb.hpp +++ b/Machines/MSX/Cartridges/ASCII16kb.hpp @@ -27,13 +27,13 @@ class ASCII16kbROMSlotHandler: public MemorySlotHandler { if(pc_is_outside_bios) { if(address == 0x6000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); } - slot_.map(0, value * 0x4000, 0x4000, 0x4000); + slot_.map(value * 0x4000, 0x4000, 0x4000); break; case 0xe: if(pc_is_outside_bios) { if(address == 0x7000 || address == 0x77ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); } - slot_.map(0, value * 0x4000, 0x8000, 0x4000); + slot_.map(value * 0x4000, 0x8000, 0x4000); break; } } diff --git a/Machines/MSX/Cartridges/ASCII8kb.hpp b/Machines/MSX/Cartridges/ASCII8kb.hpp index e1aac0b17..16deddbe3 100644 --- a/Machines/MSX/Cartridges/ASCII8kb.hpp +++ b/Machines/MSX/Cartridges/ASCII8kb.hpp @@ -27,25 +27,25 @@ class ASCII8kbROMSlotHandler: public MemorySlotHandler { if(pc_is_outside_bios) { if(address == 0x6000 || address == 0x60ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); } - slot_.map(0, value * 0x2000, 0x4000, 0x2000); + slot_.map(value * 0x2000, 0x4000, 0x2000); break; case 0xd: if(pc_is_outside_bios) { if(address == 0x6800 || address == 0x68ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); } - slot_.map(0, value * 0x2000, 0x6000, 0x2000); + slot_.map(value * 0x2000, 0x6000, 0x2000); break; case 0xe: if(pc_is_outside_bios) { if(address == 0x7000 || address == 0x70ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); } - slot_.map(0, value * 0x2000, 0x8000, 0x2000); + slot_.map(value * 0x2000, 0x8000, 0x2000); break; case 0xf: if(pc_is_outside_bios) { if(address == 0x7800 || address == 0x78ff) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); } - slot_.map(0, value * 0x2000, 0xa000, 0x2000); + slot_.map(value * 0x2000, 0xa000, 0x2000); break; } } diff --git a/Machines/MSX/Cartridges/Konami.hpp b/Machines/MSX/Cartridges/Konami.hpp index 7422ad1da..18837dc9e 100644 --- a/Machines/MSX/Cartridges/Konami.hpp +++ b/Machines/MSX/Cartridges/Konami.hpp @@ -27,19 +27,19 @@ class KonamiROMSlotHandler: public MemorySlotHandler { if(pc_is_outside_bios) { if(address == 0x6000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); } - slot_.map(0, value * 0x2000, 0x6000, 0x2000); + slot_.map(value * 0x2000, 0x6000, 0x2000); break; case 4: if(pc_is_outside_bios) { if(address == 0x8000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); } - slot_.map(0, value * 0x2000, 0x8000, 0x2000); + slot_.map(value * 0x2000, 0x8000, 0x2000); break; case 5: if(pc_is_outside_bios) { if(address == 0xa000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); } - slot_.map(0, value * 0x2000, 0xa000, 0x2000); + slot_.map(value * 0x2000, 0xa000, 0x2000); break; } } diff --git a/Machines/MSX/Cartridges/KonamiWithSCC.hpp b/Machines/MSX/Cartridges/KonamiWithSCC.hpp index 15da86b6f..00bb7141e 100644 --- a/Machines/MSX/Cartridges/KonamiWithSCC.hpp +++ b/Machines/MSX/Cartridges/KonamiWithSCC.hpp @@ -29,13 +29,13 @@ class KonamiWithSCCROMSlotHandler: public MemorySlotHandler { if(pc_is_outside_bios) { if(address == 0x5000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); } - slot_.map(0, value * 0x2000, 0x4000, 0x2000); + slot_.map(value * 0x2000, 0x4000, 0x2000); break; case 0x0e: if(pc_is_outside_bios) { if(address == 0x7000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); } - slot_.map(0, value * 0x2000, 0x6000, 0x2000); + slot_.map(value * 0x2000, 0x6000, 0x2000); break; case 0x12: if(pc_is_outside_bios) { @@ -43,10 +43,10 @@ class KonamiWithSCCROMSlotHandler: public MemorySlotHandler { } if((value&0x3f) == 0x3f) { scc_is_visible_ = true; - slot_.unmap(0, 0x8000, 0x2000); + slot_.unmap(0x8000, 0x2000); } else { scc_is_visible_ = false; - slot_.map(0, value * 0x2000, 0x8000, 0x2000); + slot_.map(value * 0x2000, 0x8000, 0x2000); } break; case 0x13: @@ -61,7 +61,7 @@ class KonamiWithSCCROMSlotHandler: public MemorySlotHandler { if(pc_is_outside_bios) { if(address == 0xb000) confidence_counter_.add_hit(); else confidence_counter_.add_equivocal(); } - slot_.map(0, value * 0x2000, 0xa000, 0x2000); + slot_.map(value * 0x2000, 0xa000, 0x2000); break; } } diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 8eafd8a7f..b0a98b64f 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -146,11 +146,6 @@ class ConcreteMachine: public Activity::Source, public MSX::MemorySlotChangeHandler { private: - static constexpr int RAMMemorySlot = 3; - static constexpr int RAMMemorySubSlot = 0; - - static constexpr int ExtensionROMSubSlot = 1; - // Provide 512kb of memory for an MSX 2; 64kb for an MSX 1. 'Slightly' arbitrary. static constexpr size_t RAMSize = model == Target::Model::MSX2 ? 512 * 1024 : 64 * 1024; @@ -253,7 +248,7 @@ class ConcreteMachine: const auto regional_bios = roms.find(regional_bios_name); if(regional_bios != roms.end()) { regional_bios->second.resize(32768); - memory_slots_[0].set_source(regional_bios->second); + bios_slot().set_source(regional_bios->second); has_bios = true; } } @@ -270,40 +265,33 @@ class ConcreteMachine: ); bios[0x2c] = keyboard; - memory_slots_[0].set_source(bios); + bios_slot().set_source(bios); } - memory_slots_[0].map(0, 0, 0, 32768); + bios_slot().map(0, 0, 32768); + + ram_slot().resize_source(RAMSize); + ram_slot().template map(0, 0, 65536); if constexpr (model == Target::Model::MSX2) { - // If there's an extension ROM, add it as a subslot in the same slot as RAM. - std::vector ram_plus; - - ram_plus.resize(RAMSize); + memory_slots_[3].supports_secondary_paging = true; const auto extension = roms.find(ROM::Name::MSX2Extension); - std::copy(extension->second.begin(), extension->second.end(), std::back_inserter(ram_plus)); - - memory_slots_[RAMMemorySlot].supports_secondary_paging = true; - memory_slots_[RAMMemorySlot].set_source(ram_plus); - - memory_slots_[RAMMemorySlot].template map(RAMMemorySubSlot, 0, 0, 65536); - memory_slots_[RAMMemorySlot].map(ExtensionROMSubSlot, RAMSize, 0, 32768); - } else { - memory_slots_[RAMMemorySlot].resize_source(65536); - memory_slots_[RAMMemorySlot].template map(RAMMemorySubSlot, 0, 0, 65536); + extension->second.resize(32768); + extension_rom_slot().set_source(extension->second); + extension_rom_slot().map(0, 0, 32768); } // Add a disk cartridge if any disks were supplied. if(target.has_disk_drive) { - memory_slots_[2].handler = std::make_unique(memory_slots_[2]); + disk_primary().handler = std::make_unique(disk_slot()); std::vector &dos = roms.find(ROM::Name::MSXDOS)->second; dos.resize(16384); - memory_slots_[2].set_source(dos); + disk_slot().set_source(dos); - memory_slots_[2].map(0, 0, 0x4000, 0x2000); - memory_slots_[2].unmap(0, 0x6000, 0x2000); + disk_slot().map(0, 0x4000, 0x2000); + disk_slot().unmap(0x6000, 0x2000); } // Insert the media. @@ -348,15 +336,15 @@ class ConcreteMachine: float get_confidence() final { if(performed_unmapped_access_ || pc_zero_accesses_ > 1) return 0.0f; - if(memory_slots_[1].handler) { - return memory_slots_[1].handler->get_confidence(); + if(cartridge_primary().handler) { + return cartridge_primary().handler->get_confidence(); } return 0.5f; } std::string debug_type() final { - if(memory_slots_[1].handler) { - return "MSX:" + memory_slots_[1].handler->debug_type(); + if(cartridge_primary().handler) { + return "MSX:" + cartridge_primary().handler->debug_type(); } return "MSX"; } @@ -364,26 +352,26 @@ class ConcreteMachine: bool insert_media(const Analyser::Static::Media &media) final { if(!media.cartridges.empty()) { const auto &segment = media.cartridges.front()->get_segments().front(); - auto &slot = memory_slots_[1]; + auto &slot = cartridge_slot(); slot.set_source(segment.data); - slot.map(0, 0, uint16_t(segment.start_address), std::min(segment.data.size(), 65536 - segment.start_address)); + slot.map(0, uint16_t(segment.start_address), std::min(segment.data.size(), 65536 - segment.start_address)); auto msx_cartridge = dynamic_cast(media.cartridges.front().get()); if(msx_cartridge) { switch(msx_cartridge->type) { default: break; case Analyser::Static::MSX::Cartridge::Konami: - slot.handler = std::make_unique(static_cast(slot)); + cartridge_primary().handler = std::make_unique(static_cast(slot)); break; case Analyser::Static::MSX::Cartridge::KonamiWithSCC: - slot.handler = std::make_unique(static_cast(slot), scc_); + cartridge_primary().handler = std::make_unique(static_cast(slot), scc_); break; case Analyser::Static::MSX::Cartridge::ASCII8kb: - slot.handler = std::make_unique(static_cast(slot)); + cartridge_primary().handler = std::make_unique(static_cast(slot)); break; case Analyser::Static::MSX::Cartridge::ASCII16kb: - slot.handler = std::make_unique(static_cast(slot)); + cartridge_primary().handler = std::make_unique(static_cast(slot)); break; } } @@ -394,11 +382,11 @@ class ConcreteMachine: } if(!media.disks.empty()) { - DiskROM *disk_rom = get_disk_rom(); - if(disk_rom) { + DiskROM *const handler = disk_handler(); + if(handler) { size_t drive = 0; for(auto &disk : media.disks) { - disk_rom->set_disk(disk, drive); + handler->set_disk(disk, drive); drive++; if(drive == 2) break; } @@ -442,7 +430,7 @@ class ConcreteMachine: final_slot_ = &memory_slots_[primary >> 6]; for(int c = 0; c < 8; c += 2) { - const MemorySlot &slot = memory_slots_[primary & 3]; + const PrimarySlot &slot = memory_slots_[primary & 3]; primary >>= 2; read_pointers_[c] = slot.read_pointer(c); @@ -752,9 +740,9 @@ class ConcreteMachine: // MARK: - Activity::Source void set_activity_observer(Activity::Observer *observer) final { - DiskROM *disk_rom = get_disk_rom(); - if(disk_rom) { - disk_rom->set_activity_observer(observer); + DiskROM *handler = disk_handler(); + if(handler) { + handler->set_activity_observer(observer); } i8255_port_handler_.set_activity_observer(observer); } @@ -765,12 +753,6 @@ class ConcreteMachine: } private: - uint8_t *ram() { - return memory_slots_[RAMMemorySlot].source().data(); - } - DiskROM *get_disk_rom() { - return dynamic_cast(memory_slots_[2].handler.get()); - } void update_audio() { speaker_.run_for(audio_queue_, time_since_ay_update_.divide_cycles(Cycles(2))); } @@ -873,14 +855,15 @@ class ConcreteMachine: /// Optionally attaches non-default logic to any of the four things selectable /// via the primary slot register. - struct MemorySlot: public MSX::MemorySlot { - using MSX::MemorySlot::MemorySlot; - + struct PrimarySlot: public MSX::PrimarySlot { + using MSX::PrimarySlot::PrimarySlot; HalfCycles cycles_since_update; + + /// Storage for a slot-specialised handler. std::unique_ptr handler; }; - MemorySlot memory_slots_[4]; - MemorySlot *final_slot_ = nullptr; + PrimarySlot memory_slots_[4]; + PrimarySlot *final_slot_ = nullptr; HalfCycles time_since_ay_update_; @@ -896,7 +879,40 @@ class ConcreteMachine: Ricoh::RP5C01::RP5C01 clock_; int next_clock_register_ = 0; -}; + + // + // Various helpers that dictate the slot arrangement used by this emulator. + // + MemorySlot &bios_slot() { + return memory_slots_[0].subslot(0); + } + MemorySlot &ram_slot() { + return memory_slots_[3].subslot(0); + } + MemorySlot &extension_rom_slot() { + return memory_slots_[3].subslot(1); + } + + MemorySlot &cartridge_slot() { + return cartridge_primary().subslot(0); + } + MemorySlot &disk_slot() { + return disk_primary().subslot(0); + } + + PrimarySlot &cartridge_primary() { + return memory_slots_[1]; + } + PrimarySlot &disk_primary() { + return memory_slots_[2]; + } + + uint8_t *ram() { + return ram_slot().source().data(); + } + DiskROM *disk_handler() { + return dynamic_cast(disk_primary().handler.get()); + }}; } diff --git a/Machines/MSX/MemorySlotHandler.cpp b/Machines/MSX/MemorySlotHandler.cpp index 6e5f503ae..7fb280603 100644 --- a/Machines/MSX/MemorySlotHandler.cpp +++ b/Machines/MSX/MemorySlotHandler.cpp @@ -12,31 +12,40 @@ using namespace MSX; +PrimarySlot::PrimarySlot(MemorySlotChangeHandler &handler) : + subslots_{handler, handler, handler, handler} {} + MemorySlot::MemorySlot(MemorySlotChangeHandler &handler) : handler_(handler) { - for(int subslot = 0; subslot < 4; subslot++) { - for(int region = 0; region < 8; region++) { - read_pointers_[subslot][region] = unmapped.data(); - write_pointers_[subslot][region] = scratch.data(); - } + for(int region = 0; region < 8; region++) { + read_pointers_[region] = unmapped.data(); + write_pointers_[region] = scratch.data(); } } -void MemorySlot::set_secondary_paging(uint8_t value) { +void PrimarySlot::set_secondary_paging(uint8_t value) { secondary_paging_ = value; } -uint8_t MemorySlot::secondary_paging() const { +uint8_t PrimarySlot::secondary_paging() const { return secondary_paging_; } -const uint8_t *MemorySlot::read_pointer(int segment) const { +const uint8_t *PrimarySlot::read_pointer(int segment) const { const int subslot = (secondary_paging_ >> (segment & ~1)) & 3; - return read_pointers_[subslot][segment]; + return subslots_[subslot].read_pointer(segment); +} + +uint8_t *PrimarySlot::write_pointer(int segment) const { + const int subslot = (secondary_paging_ >> (segment & ~1)) & 3; + return subslots_[subslot].write_pointer(segment); +} + +const uint8_t *MemorySlot::read_pointer(int segment) const { + return read_pointers_[segment]; } uint8_t *MemorySlot::write_pointer(int segment) const { - const int subslot = (secondary_paging_ >> (segment & ~1)) & 3; - return write_pointers_[subslot][segment]; + return write_pointers_[segment]; } void MemorySlot::set_source(const std::vector &source) { @@ -56,7 +65,7 @@ const std::vector &MemorySlot::source() const { } template -void MemorySlot::map(int subslot, std::size_t source_address, uint16_t destination_address, std::size_t length) { +void MemorySlot::map(std::size_t source_address, uint16_t destination_address, std::size_t length) { assert(!(destination_address & 8191)); assert(!(length & 8191)); assert(size_t(destination_address) + length <= 65536); @@ -65,9 +74,9 @@ void MemorySlot::map(int subslot, std::size_t source_address, uint16_t destinati source_address %= source_.size(); const int bank = int((destination_address >> 13) + c); - read_pointers_[subslot][bank] = &source_[source_address]; + read_pointers_[bank] = &source_[source_address]; if constexpr (type == AccessType::ReadWrite) { - write_pointers_[subslot][bank] = read_pointers_[subslot][bank]; + write_pointers_[bank] = read_pointers_[bank]; } source_address += 8192; @@ -76,17 +85,21 @@ void MemorySlot::map(int subslot, std::size_t source_address, uint16_t destinati handler_.did_page(); } -void MemorySlot::unmap(int subslot, uint16_t destination_address, std::size_t length) { +void MemorySlot::unmap(uint16_t destination_address, std::size_t length) { assert(!(destination_address & 8191)); assert(!(length & 8191)); assert(size_t(destination_address) + length <= 65536); for(std::size_t c = 0; c < (length >> 13); ++c) { - read_pointers_[subslot][(destination_address >> 13) + c] = nullptr; + read_pointers_[(destination_address >> 13) + c] = nullptr; } handler_.did_page(); } -template void MemorySlot::map(int subslot, std::size_t source_address, uint16_t destination_address, std::size_t length); -template void MemorySlot::map(int subslot, std::size_t source_address, uint16_t destination_address, std::size_t length); +MemorySlot &PrimarySlot::subslot(int slot) { + return subslots_[slot]; +} + +template void MemorySlot::map(std::size_t source_address, uint16_t destination_address, std::size_t length); +template void MemorySlot::map(std::size_t source_address, uint16_t destination_address, std::size_t length); diff --git a/Machines/MSX/MemorySlotHandler.hpp b/Machines/MSX/MemorySlotHandler.hpp index cac6ed032..cc046e78c 100644 --- a/Machines/MSX/MemorySlotHandler.hpp +++ b/Machines/MSX/MemorySlotHandler.hpp @@ -38,15 +38,6 @@ class MemorySlot { public: MemorySlot(MemorySlotChangeHandler &); - /// Attempts to write the argument as the secondary paging selection. - void set_secondary_paging(uint8_t); - - /// @returns The value most recently provided to @c set_secondary_paging. - uint8_t secondary_paging() const; - - /// Indicates whether this slot supports secondary paging. - bool supports_secondary_paging = false; - /// @returns A pointer to the area of memory currently underneath @c address that /// should be read const uint8_t *read_pointer(int segment) const; @@ -73,7 +64,6 @@ class MemorySlot { /// supplied to @c set_source to the region indicated by /// @c destination_address and @c length within @c subslot. template void map( - int subslot, std::size_t source_address, uint16_t destination_address, std::size_t length); @@ -83,15 +73,13 @@ class MemorySlot { /// will be used to field accesses to that area, allowing for areas that are not /// backed by memory to be modelled. void unmap( - int subslot, uint16_t destination_address, std::size_t length); private: std::vector source_; - uint8_t *read_pointers_[4][8]; - uint8_t *write_pointers_[4][8]; - uint8_t secondary_paging_ = 0; + uint8_t *read_pointers_[8]; + uint8_t *write_pointers_[8]; MemorySlotChangeHandler &handler_; @@ -100,6 +88,34 @@ class MemorySlot { inline static MemoryChunk scratch; }; +class PrimarySlot { + public: + PrimarySlot(MemorySlotChangeHandler &); + + /// @returns A pointer to the area of memory currently underneath @c address that + /// should be read + const uint8_t *read_pointer(int segment) const; + + /// @returns A pointer to the area of memory currently underneath @c address. + uint8_t *write_pointer(int segment) const; + + /// Attempts to write the argument as the secondary paging selection. + void set_secondary_paging(uint8_t); + + /// @returns The value most recently provided to @c set_secondary_paging. + uint8_t secondary_paging() const; + + /// Indicates whether this slot supports secondary paging. + bool supports_secondary_paging = false; + + /// Provides the subslot at the specified index. + MemorySlot &subslot(int); + + private: + MemorySlot subslots_[4]; + uint8_t secondary_paging_ = 0; +}; + class MemorySlotHandler { public: virtual ~MemorySlotHandler() {} From 1a58ddaa67665ad2b4750fef4e03e48aae2e70fd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 15 Jan 2023 23:12:36 -0500 Subject: [PATCH 30/34] Increase notes for future self. --- Machines/MSX/MSX.cpp | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index b0a98b64f..eae1c7cc1 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -855,12 +855,18 @@ class ConcreteMachine: /// Optionally attaches non-default logic to any of the four things selectable /// via the primary slot register. + /// + /// In principle one might want to attach a handler to a secondary slot rather + /// than a primary, but in practice that isn't required in the slot allocation used + /// by this emulator. struct PrimarySlot: public MSX::PrimarySlot { using MSX::PrimarySlot::PrimarySlot; - HalfCycles cycles_since_update; /// Storage for a slot-specialised handler. std::unique_ptr handler; + + /// The handler is updated just-in-time. + HalfCycles cycles_since_update; }; PrimarySlot memory_slots_[4]; PrimarySlot *final_slot_ = nullptr; @@ -883,6 +889,23 @@ class ConcreteMachine: // // Various helpers that dictate the slot arrangement used by this emulator. // + // That arrangement is: + // + // Slot 0 is the BIOS, and does not support secondary paging. + // Slot 1 holds a [game, probably] cartridge, if inserted. No secondary paging. + // Slot 2 holds the disk cartridge, if inserted. + // + // On an MSX 1, Slot 3 holds 64kb of RAM. + // + // On an MSX 2: + // + // Slot 3-0 holds a larger amount of RAM (cf. RAMSize) that is subject to the + // FC-FF paging selections. + // + // Slot 3-1 holds the BIOS extension ROM. + // + // [Slot 3-2 will likely hold MSX-MUSIC, but that's TODO] + // MemorySlot &bios_slot() { return memory_slots_[0].subslot(0); } From 1769c245312cf8816ca8e44f1b7bf3db700abd1e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 16 Jan 2023 11:43:43 -0500 Subject: [PATCH 31/34] Avoid ambiguous naming. --- Machines/MSX/MSX.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index eae1c7cc1..2f4d8e2b3 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -430,7 +430,7 @@ class ConcreteMachine: final_slot_ = &memory_slots_[primary >> 6]; for(int c = 0; c < 8; c += 2) { - const PrimarySlot &slot = memory_slots_[primary & 3]; + const HandledSlot &slot = memory_slots_[primary & 3]; primary >>= 2; read_pointers_[c] = slot.read_pointer(c); @@ -859,7 +859,7 @@ class ConcreteMachine: /// In principle one might want to attach a handler to a secondary slot rather /// than a primary, but in practice that isn't required in the slot allocation used /// by this emulator. - struct PrimarySlot: public MSX::PrimarySlot { + struct HandledSlot: public MSX::PrimarySlot { using MSX::PrimarySlot::PrimarySlot; /// Storage for a slot-specialised handler. @@ -868,8 +868,8 @@ class ConcreteMachine: /// The handler is updated just-in-time. HalfCycles cycles_since_update; }; - PrimarySlot memory_slots_[4]; - PrimarySlot *final_slot_ = nullptr; + HandledSlot memory_slots_[4]; + HandledSlot *final_slot_ = nullptr; HalfCycles time_since_ay_update_; @@ -923,10 +923,10 @@ class ConcreteMachine: return disk_primary().subslot(0); } - PrimarySlot &cartridge_primary() { + HandledSlot &cartridge_primary() { return memory_slots_[1]; } - PrimarySlot &disk_primary() { + HandledSlot &disk_primary() { return memory_slots_[2]; } From eb51ff5cdf6213e3f73ba993e8510344a2a62664 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 16 Jan 2023 11:52:08 -0500 Subject: [PATCH 32/34] Add RAM paging. --- Machines/MSX/MSX.cpp | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 2f4d8e2b3..9298d35cd 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -628,11 +628,24 @@ class ConcreteMachine: clock_.write(next_clock_register_, *cycle.value); break; - case 0xfc: case 0xfd: case 0xfe: case 0xff: - // 1. Propagate to all handlers. - // 2. Apply to RAM. - printf("RAM banking %02x: %02x\n", port, *cycle.value); - break; + case 0xfc: case 0xfd: case 0xfe: case 0xff: { + if constexpr (model == Target::Model::MSX1) { + break; + } + + // TODO: Propagate to all handlers. + + // Apply to RAM. + const uint16_t region = uint16_t((port - 0xfc) << 14); + const size_t base = size_t(*cycle.value) << 14; + if(base < RAMSize) { + ram_slot().template map(base, region, 0x4000); + } else { + ram_slot().unmap(region, 0x4000); + } + + update_paging(); + } break; default: printf("Unhandled write %02x of %02x\n", address & 0xff, *cycle.value); From a5b9bdc18cef37bd3621a865095ceb1a39886d22 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 16 Jan 2023 11:53:04 -0500 Subject: [PATCH 33/34] Eliminate speculative `apply_mapping`. --- Machines/MSX/MSX.cpp | 6 ++++-- Machines/MSX/MemorySlotHandler.hpp | 4 ---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 9298d35cd..fd5b9e972 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -633,9 +633,11 @@ class ConcreteMachine: break; } - // TODO: Propagate to all handlers. - // Apply to RAM. + // + // On a real MSX this may also affect other slots. + // I've not yet needed it to propagate further, so + // have not implemented any onward route. const uint16_t region = uint16_t((port - 0xfc) << 14); const size_t base = size_t(*cycle.value) << 14; if(base < RAMSize) { diff --git a/Machines/MSX/MemorySlotHandler.hpp b/Machines/MSX/MemorySlotHandler.hpp index cc046e78c..874b76b4f 100644 --- a/Machines/MSX/MemorySlotHandler.hpp +++ b/Machines/MSX/MemorySlotHandler.hpp @@ -129,10 +129,6 @@ class MemorySlotHandler { /*! Seeks the result of a read at @c address; this is used only if the area is unmapped. */ virtual uint8_t read([[maybe_unused]] uint16_t address) { return 0xff; } - /// Sets the value most-recently written to one of the standard - /// memory mapping ports, FC–FF. - virtual void apply_mapping([[maybe_unused]] uint8_t port, [[maybe_unused]] uint8_t value) {} - /*! @returns The probability that this handler is correct for the data it owns. */ float get_confidence() { return confidence_counter_.get_confidence(); From 055e9cdf8de176ca3f9f598c7fe3d19af60ba243 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 16 Jan 2023 19:52:40 -0500 Subject: [PATCH 34/34] Differentiate unmapped and mapped-for-handler. --- Machines/MSX/Cartridges/KonamiWithSCC.hpp | 2 +- Machines/MSX/MSX.cpp | 2 +- Machines/MSX/MemorySlotHandler.cpp | 20 +++++++++++++++----- Machines/MSX/MemorySlotHandler.hpp | 10 +++++++--- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/Machines/MSX/Cartridges/KonamiWithSCC.hpp b/Machines/MSX/Cartridges/KonamiWithSCC.hpp index 00bb7141e..7f1e8dc16 100644 --- a/Machines/MSX/Cartridges/KonamiWithSCC.hpp +++ b/Machines/MSX/Cartridges/KonamiWithSCC.hpp @@ -43,7 +43,7 @@ class KonamiWithSCCROMSlotHandler: public MemorySlotHandler { } if((value&0x3f) == 0x3f) { scc_is_visible_ = true; - slot_.unmap(0x8000, 0x2000); + slot_.map_handler(0x8000, 0x2000); } else { scc_is_visible_ = false; slot_.map(value * 0x2000, 0x8000, 0x2000); diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index fd5b9e972..52af973ef 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -291,7 +291,7 @@ class ConcreteMachine: disk_slot().set_source(dos); disk_slot().map(0, 0x4000, 0x2000); - disk_slot().unmap(0x6000, 0x2000); + disk_slot().map_handler(0x6000, 0x2000); } // Insert the media. diff --git a/Machines/MSX/MemorySlotHandler.cpp b/Machines/MSX/MemorySlotHandler.cpp index 7fb280603..79d4c69e3 100644 --- a/Machines/MSX/MemorySlotHandler.cpp +++ b/Machines/MSX/MemorySlotHandler.cpp @@ -16,10 +16,7 @@ PrimarySlot::PrimarySlot(MemorySlotChangeHandler &handler) : subslots_{handler, handler, handler, handler} {} MemorySlot::MemorySlot(MemorySlotChangeHandler &handler) : handler_(handler) { - for(int region = 0; region < 8; region++) { - read_pointers_[region] = unmapped.data(); - write_pointers_[region] = scratch.data(); - } + unmap(0x0000, 0x10000); } void PrimarySlot::set_secondary_paging(uint8_t value) { @@ -85,7 +82,7 @@ void MemorySlot::map(std::size_t source_address, uint16_t destination_address, s handler_.did_page(); } -void MemorySlot::unmap(uint16_t destination_address, std::size_t length) { +void MemorySlot::map_handler(uint16_t destination_address, std::size_t length) { assert(!(destination_address & 8191)); assert(!(length & 8191)); assert(size_t(destination_address) + length <= 65536); @@ -97,6 +94,19 @@ void MemorySlot::unmap(uint16_t destination_address, std::size_t length) { handler_.did_page(); } +void MemorySlot::unmap(uint16_t destination_address, std::size_t length) { + assert(!(destination_address & 8191)); + assert(!(length & 8191)); + assert(size_t(destination_address) + length <= 65536); + + for(std::size_t c = 0; c < (length >> 13); ++c) { + read_pointers_[(destination_address >> 13) + c] = unmapped.data(); + write_pointers_[(destination_address >> 13) + c] = scratch.data(); + } + + handler_.did_page(); +} + MemorySlot &PrimarySlot::subslot(int slot) { return subslots_[slot]; } diff --git a/Machines/MSX/MemorySlotHandler.hpp b/Machines/MSX/MemorySlotHandler.hpp index 874b76b4f..49f58ab09 100644 --- a/Machines/MSX/MemorySlotHandler.hpp +++ b/Machines/MSX/MemorySlotHandler.hpp @@ -69,9 +69,13 @@ class MemorySlot { std::size_t length); /// Marks the region indicated by @c destination_address and @c length - /// as unmapped. In practical terms that means that a @c MemorySlotHandler - /// will be used to field accesses to that area, allowing for areas that are not - /// backed by memory to be modelled. + /// as requiring calls into this slot's MemorySlotHandler. + void map_handler( + uint16_t destination_address, + std::size_t length); + + /// Marks the region indicated by @c destination_address and @c length + /// as unoccupied. void unmap( uint16_t destination_address, std::size_t length);