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); } } }; 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 { diff --git a/Components/RP5C01/RP5C01.cpp b/Components/RP5C01/RP5C01.cpp new file mode 100644 index 000000000..88d8d2001 --- /dev/null +++ b/Components/RP5C01/RP5C01.cpp @@ -0,0 +1,132 @@ +// +// 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) { + address &= 0xf; + + // 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; + } + + 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_); +} + +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; +} 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/Machines/MSX/Cartridges/ASCII16kb.hpp b/Machines/MSX/Cartridges/ASCII16kb.hpp index 848402708..170cd3ac6 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 { +class ASCII16kbROMSlotHandler: public MemorySlotHandler { 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(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(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..16deddbe3 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 { +class ASCII8kbROMSlotHandler: public MemorySlotHandler { 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(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(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(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(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..18837dc9e 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 { +class KonamiROMSlotHandler: public MemorySlotHandler { 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(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(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(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..7f1e8dc16 100644 --- a/Machines/MSX/Cartridges/KonamiWithSCC.hpp +++ b/Machines/MSX/Cartridges/KonamiWithSCC.hpp @@ -9,16 +9,16 @@ #ifndef KonamiWithSCC_hpp #define KonamiWithSCC_hpp -#include "../ROMSlotHandler.hpp" +#include "../MemorySlotHandler.hpp" #include "../../../Components/KonamiSCC/KonamiSCC.hpp" namespace MSX { namespace Cartridge { -class KonamiWithSCCROMSlotHandler: public ROMSlotHandler { +class KonamiWithSCCROMSlotHandler: public MemorySlotHandler { 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(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(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_.map_handler(0x8000, 0x2000); } else { scc_is_visible_ = false; - map_.map(slot_, value * 0x2000, 0x8000, 0x2000); + slot_.map(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(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..1311d99f0 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(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..7d776b83f 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" @@ -21,9 +21,9 @@ namespace MSX { -class DiskROM: public ROMSlotHandler, public WD::WD1770 { +class DiskROM: public MemorySlotHandler, public WD::WD1770 { public: - DiskROM(const std::vector &rom); + 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 6bc06bda0..52af973ef 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" @@ -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" @@ -128,6 +129,9 @@ class AYPortHandler: public GI::AY38910::PortHandler { }; }; +using Target = Analyser::Static::MSX::Target; + +template class ConcreteMachine: public Machine, public CPU::Z80::BusHandler, @@ -138,12 +142,16 @@ class ConcreteMachine: public MachineTypes::MappedKeyboardMachine, public MachineTypes::JoystickMachine, public Configurable::Device, - public MemoryMap, public ClockingHint::Observer, - public Activity::Source { - public: - using Target = Analyser::Static::MSX::Target; + public Activity::Source, + public MSX::MemorySlotChangeHandler { + private: + // 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), i8255_(i8255_port_handler_), @@ -154,9 +162,10 @@ class ConcreteMachine: speaker_(mixer_), tape_player_(3579545 * 2), i8255_port_handler_(*this, audio_toggle_, tape_player_), - ay_port_handler_(tape_player_) { - set_clock_rate(3579545); - std::memset(unpopulated_, 0xff, sizeof(unpopulated_)); + ay_port_handler_(tape_player_), + memory_slots_{{*this}, {*this}, {*this}, {*this}}, + clock_(ClockRate) { + set_clock_rate(ClockRate); clear_all_keys(); ay_.set_port_handler(&ay_port_handler_); @@ -168,10 +177,12 @@ 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); + 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 */ @@ -179,11 +190,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; @@ -191,7 +203,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; @@ -199,7 +213,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; @@ -207,7 +223,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. @@ -225,43 +243,55 @@ 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()) { - 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.resize(32768); + 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); + bios_slot().set_source(regional_bios->second); + has_bios = true; + } + } + if(!has_bios) { + std::vector &bios = roms.find(bios_name)->second; - memory_slots_[0].source[0x2b] = uint8_t( + bios.resize(32768); + + // Modify the generic ROM to reflect the selected region, date format, etc. + bios[0x2b] = uint8_t( (is_ntsc ? 0x00 : 0x80) | (date_format << 4) | character_generator ); - memory_slots_[0].source[0x2c] = keyboard; + bios[0x2c] = keyboard; + + bios_slot().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_; - } + bios_slot().map(0, 0, 32768); - memory_slots_[3].read_pointers[c] = - memory_slots_[3].write_pointers[c] = &ram_[c * 8192]; + ram_slot().resize_source(RAMSize); + ram_slot().template map(0, 0, 65536); + + if constexpr (model == Target::Model::MSX2) { + memory_slots_[3].supports_secondary_paging = true; + + const auto extension = roms.find(ROM::Name::MSX2Extension); + extension->second.resize(32768); + extension_rom_slot().set_source(extension->second); + extension_rom_slot().map(0, 0, 32768); } - map(0, 0, 0, 32768); - page_memory(0); - // 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); + disk_primary().handler = std::make_unique(disk_slot()); - map(2, 0, 0x4000, 0x2000); - unmap(2, 0x6000, 0x2000); + std::vector &dos = roms.find(ROM::Name::MSXDOS)->second; + dos.resize(16384); + disk_slot().set_source(dos); + + disk_slot().map(0, 0x4000, 0x2000); + disk_slot().map_handler(0x6000, 0x2000); } // Insert the media. @@ -271,6 +301,9 @@ class ConcreteMachine: if(!target.loading_command.empty()) { type_string(target.loading_command); } + + // Establish default paging. + page_primary(0); } ~ConcreteMachine() { @@ -303,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"; } @@ -319,24 +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(); - 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 = cartridge_slot(); + + slot.set_source(segment.data); + 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: - memory_slots_[1].set_handler(new Cartridge::KonamiROMSlotHandler(*this, 1)); + cartridge_primary().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_)); + cartridge_primary().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)); + cartridge_primary().handler = std::make_unique(static_cast(slot)); break; case Analyser::Static::MSX::Cartridge::ASCII16kb: - memory_slots_[1].set_handler(new Cartridge::ASCII16kbROMSlotHandler(*this, 1)); + cartridge_primary().handler = std::make_unique(static_cast(slot)); break; } } @@ -347,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; } @@ -377,43 +412,31 @@ 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; - } - - page_memory(paged_memory_); + // MARK: Memory paging. + void page_primary(uint8_t value) { + primary_slots_ = value; + 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; - } - - page_memory(paged_memory_); + void did_page() final { + update_paging(); } - // MARK: Ordinary paging. - void page_memory(uint8_t value) { - paged_memory_ = value; - 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; + 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]; + + for(int c = 0; c < 8; c += 2) { + const HandledSlot &slot = memory_slots_[primary & 3]; + primary >>= 2; + + 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(); } @@ -433,6 +456,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) { @@ -453,8 +480,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); @@ -471,8 +498,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_); @@ -495,30 +522,47 @@ 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]]; case CPU::Z80::PartialMachineCycle::Read: + 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 = (paged_memory_ >> ((address >> 14) * 2)) & 3; - memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush()); + 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: { - write_pointers_[address >> 13][address & 8191] = *cycle.value; + if(address == 0xffff && final_slot_->supports_secondary_paging) { + final_slot_->set_secondary_paging(*cycle.value); + update_paging(); + break; + } - int slot_hit = (paged_memory_ >> ((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.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->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_pointer(pc_address_ >> 13)); + } else { + write_pointers_[address >> 13][address & 8191] = *cycle.value; } } break; @@ -539,7 +583,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; } @@ -563,8 +615,42 @@ class ConcreteMachine: i8255_.write(address, *cycle.value); break; - case 0xfc: case 0xfd: case 0xfe: case 0xff: - // printf("RAM banking %02x: %02x\n", port, *cycle.value); + 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: { + if constexpr (model == Target::Model::MSX1) { + break; + } + + // 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) { + 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); break; } } break; @@ -579,8 +665,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 @@ -591,7 +677,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; } @@ -599,8 +685,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; @@ -669,9 +755,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); } @@ -682,9 +768,6 @@ class ConcreteMachine: } private: - 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))); } @@ -696,7 +779,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 @@ -742,8 +825,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_; @@ -758,34 +848,43 @@ 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() && + !(primary_slots_ & 3) && + !(memory_slots_[0].secondary_paging() & 3); } i8255PortHandler i8255_port_handler_; AYPortHandler ay_port_handler_; - uint8_t paged_memory_ = 0; - uint8_t *read_pointers_[8]; + /// 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 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. + const uint8_t *read_pointers_[8]; uint8_t *write_pointers_[8]; - struct MemorySlots { - 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. + /// + /// 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 HandledSlot: public MSX::PrimarySlot { + using MSX::PrimarySlot::PrimarySlot; - void set_handler(ROMSlotHandler *slot_handler) { - handler.reset(slot_handler); - wrapping_strategy = handler->wrapping_strategy(); - } + /// Storage for a slot-specialised handler. + std::unique_ptr handler; - std::unique_ptr handler; - std::vector source; + /// The handler is updated just-in-time. HalfCycles cycles_since_update; - ROMSlotHandler::WrappingStrategy wrapping_strategy = ROMSlotHandler::WrappingStrategy::Repeat; - } memory_slots_[4]; - - uint8_t ram_[65536]; - uint8_t scratch_[8192]; - uint8_t unpopulated_[8192]; + }; + HandledSlot memory_slots_[4]; + HandledSlot *final_slot_ = nullptr; HalfCycles time_since_ay_update_; @@ -798,16 +897,72 @@ 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; + + // + // 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); + } + 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); + } + + HandledSlot &cartridge_primary() { + return memory_slots_[1]; + } + HandledSlot &disk_primary() { + return memory_slots_[2]; + } + + uint8_t *ram() { + return ram_slot().source().data(); + } + DiskROM *disk_handler() { + return dynamic_cast(disk_primary().handler.get()); + }}; } 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() {} diff --git a/Machines/MSX/MemorySlotHandler.cpp b/Machines/MSX/MemorySlotHandler.cpp new file mode 100644 index 000000000..79d4c69e3 --- /dev/null +++ b/Machines/MSX/MemorySlotHandler.cpp @@ -0,0 +1,115 @@ +// +// MemorySlotHandler.cpp +// Clock Signal +// +// Created by Thomas Harte on 12/01/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#include "MemorySlotHandler.hpp" + +#include + +using namespace MSX; + +PrimarySlot::PrimarySlot(MemorySlotChangeHandler &handler) : + subslots_{handler, handler, handler, handler} {} + +MemorySlot::MemorySlot(MemorySlotChangeHandler &handler) : handler_(handler) { + unmap(0x0000, 0x10000); +} + +void PrimarySlot::set_secondary_paging(uint8_t value) { + secondary_paging_ = value; +} + +uint8_t PrimarySlot::secondary_paging() const { + return secondary_paging_; +} + +const uint8_t *PrimarySlot::read_pointer(int segment) const { + const int subslot = (secondary_paging_ >> (segment & ~1)) & 3; + 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 { + return write_pointers_[segment]; +} + +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_; +} + +template +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); + + for(std::size_t c = 0; c < (length >> 13); ++c) { + source_address %= source_.size(); + + const int bank = int((destination_address >> 13) + c); + read_pointers_[bank] = &source_[source_address]; + if constexpr (type == AccessType::ReadWrite) { + write_pointers_[bank] = read_pointers_[bank]; + } + + source_address += 8192; + } + + handler_.did_page(); +} + +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); + + for(std::size_t c = 0; c < (length >> 13); ++c) { + read_pointers_[(destination_address >> 13) + c] = nullptr; + } + + 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]; +} + +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 new file mode 100644 index 000000000..49f58ab09 --- /dev/null +++ b/Machines/MSX/MemorySlotHandler.hpp @@ -0,0 +1,151 @@ +// +// 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 { + +struct MemorySlotChangeHandler { + virtual void did_page() = 0; +}; + +class MemorySlot { + public: + MemorySlot(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; + + /// 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 { + 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. + template void map( + 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 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); + + private: + std::vector source_; + uint8_t *read_pointers_[8]; + uint8_t *write_pointers_[8]; + + MemorySlotChangeHandler &handler_; + + using MemoryChunk = std::array; + inline static MemoryChunk unmapped{0xff}; + 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() {} + + /*! 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 f8c5d6f76..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 { - /// Repeat causes all accesses to be modulo the size of the ROM. - Repeat, - /// Empty causes all out-of-bounds accesses to read a vacant bus. - Empty - }; - - /*! @returns The wrapping strategy to apply to mapping requests from this ROM slot. */ - virtual WrappingStrategy wrapping_strategy() const { - return WrappingStrategy::Repeat; - } - - /*! @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/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; } } diff --git a/Machines/Utility/ROMCatalogue.cpp b/Machines/Utility/ROMCatalogue.cpp index 35585c902..f2edb50f2 100644 --- a/Machines/Utility/ROMCatalogue.cpp +++ b/Machines/Utility/ROMCatalogue.cpp @@ -565,12 +565,16 @@ 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; + // 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; 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::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); break; diff --git a/Machines/Utility/ROMCatalogue.hpp b/Machines/Utility/ROMCatalogue.hpp index 6619710ef..e085cbf09 100644 --- a/Machines/Utility/ROMCatalogue.hpp +++ b/Machines/Utility/ROMCatalogue.hpp @@ -117,6 +117,9 @@ enum Name { MSXEuropeanBIOS, MSXDOS, + MSX2GenericBIOS, + MSX2Extension, + // Oric. OricColourROM, OricBASIC10, 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() {} diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index dcf95902c..3844079a6 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1062,6 +1062,10 @@ 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 */; }; + 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 */; }; @@ -1457,7 +1461,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 +2212,9 @@ 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 = ""; }; + 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 = ""; }; @@ -3261,11 +3268,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; @@ -4575,6 +4583,7 @@ 4B302181208A550100773308 /* DiskII */, 4B4B1A39200198C900A0F866 /* KonamiSCC */, 4BC23A212467600E001A6030 /* OPx */, + 4BF0BC6E2973318E00CCA2B5 /* RP5C01 */, 4B0ACBFF237756EC008902D0 /* Serial */, 4BB0A6582044FD3000FB3688 /* SN76489 */, ); @@ -4944,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 = ( @@ -5544,6 +5562,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 */, @@ -5625,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 */, @@ -5802,6 +5822,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 */, @@ -5977,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 */, 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') diff --git a/ROMImages/MSX/readme.txt b/ROMImages/MSX/readme.txt index f97c4d102..b30d35fde 100644 --- a/ROMImages/MSX/readme.txt +++ b/ROMImages/MSX/readme.txt @@ -3,6 +3,8 @@ ROMs for the MSX go here; the copyright status of these is uncertain so they hav 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.