diff --git a/Analyser/Static/ZXSpectrum/Target.hpp b/Analyser/Static/ZXSpectrum/Target.hpp index e2711ec55..4996d21fa 100644 --- a/Analyser/Static/ZXSpectrum/Target.hpp +++ b/Analyser/Static/ZXSpectrum/Target.hpp @@ -19,6 +19,10 @@ namespace ZXSpectrum { struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> { ReflectableEnum(Model, + SixteenK, + FortyEightK, + OneTwoEightK, + Plus2, Plus2a, Plus3, ); diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp index 371697511..4270815dc 100644 --- a/Machines/Sinclair/ZXSpectrum/Video.hpp +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -18,7 +18,9 @@ namespace Sinclair { namespace ZXSpectrum { enum class VideoTiming { - Plus3 + FortyEightK, + OneTwoEightK, + Plus3, }; /* diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 717026ee4..0947312b2 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -81,8 +81,29 @@ template<Model model> class ConcreteMachine: // With only the +2a and +3 currently supported, the +3 ROM is always // the one required. - const auto roms = - rom_fetcher({ {"ZXSpectrum", "the +2a/+3 ROM", "plus3.rom", 64 * 1024, 0x96e3c17a} }); + std::vector<ROMMachine::ROM> rom_names; + const std::string machine = "ZXSpectrum"; + switch(model) { + case Model::SixteenK: + case Model::FortyEightK: + rom_names.emplace_back(machine, "the 48kb ROM", "48.rom", 16 * 1024, 0xddee531f); + break; + + case Model::OneTwoEightK: + rom_names.emplace_back(machine, "the 128kb ROM", "128.rom", 32 * 1024, 0x2cbe8995); + break; + + case Model::Plus2: + rom_names.emplace_back(machine, "the +2 ROM", "plus2.rom", 32 * 1024, 0xe7a517dc); + break; + + case Model::Plus2a: + case Model::Plus3: { + const std::initializer_list<uint32_t> crc32s = { 0x96e3c17a, 0xbe0d9ec4 }; + rom_names.emplace_back(machine, "the +2a/+3 ROM", "plus3.rom", 64 * 1024, crc32s); + } break; + } + const auto roms = rom_fetcher(rom_names); if(!roms[0]) throw ROMMachine::Error::MissingROMs; memcpy(rom_.data(), roms[0]->data(), std::min(rom_.size(), roms[0]->size())); @@ -110,7 +131,7 @@ template<Model model> class ConcreteMachine: } static constexpr unsigned int clock_rate() { -// constexpr unsigned int ClockRate = 3'500'000; + constexpr unsigned int OriginalClockRate = 3'500'000; constexpr unsigned int Plus3ClockRate = 3'546'875; // See notes below; this is a guess. // Notes on timing for the +2a and +3: @@ -137,7 +158,7 @@ template<Model model> class ConcreteMachine: // the Spectrum is a PAL machine with a fixed colour phase relationship. For // this emulator's world, that's a first! - return Plus3ClockRate; + return model < Model::OneTwoEightK ? OriginalClockRate : Plus3ClockRate; } // MARK: - TimedMachine. @@ -189,18 +210,22 @@ template<Model model> class ConcreteMachine: const uint16_t address = cycle.address ? *cycle.address : 0x0000; // Apply contention if necessary. - if( - is_contended_[address >> 14] && - cycle.operation >= PartialMachineCycle::ReadOpcodeStart && - cycle.operation <= PartialMachineCycle::WriteStart) { - // Assumption here: the trigger for the ULA inserting a delay is the falling edge - // of MREQ, which is always half a cycle into a read or write. - // - // TODO: somehow provide that information in the PartialMachineCycle? + if constexpr (model >= Model::Plus2a) { + if( + is_contended_[address >> 14] && + cycle.operation >= PartialMachineCycle::ReadOpcodeStart && + cycle.operation <= PartialMachineCycle::WriteStart) { + // Assumption here: the trigger for the ULA inserting a delay is the falling edge + // of MREQ, which is always half a cycle into a read or write. + // + // TODO: somehow provide that information in the PartialMachineCycle? - const HalfCycles delay = video_.last_valid()->access_delay(video_.time_since_flush() + HalfCycles(1)); - advance(cycle.length + delay); - return delay; + const HalfCycles delay = video_.last_valid()->access_delay(video_.time_since_flush() + HalfCycles(1)); + advance(cycle.length + delay); + return delay; + } + } else { + // TODO. } // For all other machine cycles, model the action as happening at the end of the machine cycle; @@ -263,39 +288,45 @@ template<Model model> class ConcreteMachine: } // Test for classic 128kb paging register (i.e. port 7ffd). - if((address & 0xc002) == 0x4000) { - port7ffd_ = *cycle.value; - update_memory_map(); + if constexpr (model >= Model::OneTwoEightK) { + if((address & 0xc002) == 0x4000) { + port7ffd_ = *cycle.value; + update_memory_map(); - // Set the proper video base pointer. - set_video_address(); + // Set the proper video base pointer. + set_video_address(); - // Potentially lock paging, _after_ the current - // port values have taken effect. - disable_paging_ |= *cycle.value & 0x20; - } - - // Test for +2a/+3 paging (i.e. port 1ffd). - if((address & 0xf002) == 0x1000) { - port1ffd_ = *cycle.value; - update_memory_map(); - update_video_base(); - - if constexpr (model == Model::Plus3) { - fdc_->set_motor_on(*cycle.value & 0x08); + // Potentially lock paging, _after_ the current + // port values have taken effect. + disable_paging_ |= *cycle.value & 0x20; } } - if((address & 0xc002) == 0xc000) { - // Select AY register. - update_audio(); - GI::AY38910::Utility::select_register(ay_, *cycle.value); + // Test for +2a/+3 paging (i.e. port 1ffd). + if constexpr (model >= Model::Plus2a) { + if((address & 0xf002) == 0x1000) { + port1ffd_ = *cycle.value; + update_memory_map(); + update_video_base(); + + if constexpr (model == Model::Plus3) { + fdc_->set_motor_on(*cycle.value & 0x08); + } + } } - if((address & 0xc002) == 0x8000) { - // Write to AY register. - update_audio(); - GI::AY38910::Utility::write_data(ay_, *cycle.value); + if constexpr (model >= Model::OneTwoEightK) { + if((address & 0xc002) == 0xc000) { + // Select AY register. + update_audio(); + GI::AY38910::Utility::select_register(ay_, *cycle.value); + } + + if((address & 0xc002) == 0x8000) { + // Write to AY register. + update_audio(); + GI::AY38910::Utility::write_data(ay_, *cycle.value); + } } if constexpr (model == Model::Plus3) { @@ -574,7 +605,7 @@ template<Model model> class ConcreteMachine: is_contended_[bank] = (source >= 4 && source < 8); pages_[bank] = source; - uint8_t *read = (source < 0x80) ? &ram_[source * 16384] : &rom_[(source & 0x7f) * 16384]; + uint8_t *const read = (source < 0x80) ? &ram_[source * 16384] : &rom_[(source & 0x7f) * 16384]; const auto offset = bank*16384; read_pointers_[bank] = read - offset; @@ -712,8 +743,12 @@ Machine *Machine::ZXSpectrum(const Analyser::Static::Target *target, const ROMMa const auto zx_target = dynamic_cast<const Analyser::Static::ZXSpectrum::Target *>(target); switch(zx_target->model) { - case Model::Plus2a: return new ConcreteMachine<Model::Plus2a>(*zx_target, rom_fetcher); - case Model::Plus3: return new ConcreteMachine<Model::Plus3>(*zx_target, rom_fetcher); + case Model::SixteenK: return new ConcreteMachine<Model::SixteenK>(*zx_target, rom_fetcher); + case Model::FortyEightK: return new ConcreteMachine<Model::FortyEightK>(*zx_target, rom_fetcher); + case Model::OneTwoEightK: return new ConcreteMachine<Model::OneTwoEightK>(*zx_target, rom_fetcher); + case Model::Plus2: return new ConcreteMachine<Model::Plus2>(*zx_target, rom_fetcher); + case Model::Plus2a: return new ConcreteMachine<Model::Plus2a>(*zx_target, rom_fetcher); + case Model::Plus3: return new ConcreteMachine<Model::Plus3>(*zx_target, rom_fetcher); } return nullptr; diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h index 1193281f4..62df779b9 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h @@ -63,6 +63,10 @@ typedef NS_ENUM(NSInteger, CSMachineOricDiskInterface) { }; typedef NS_ENUM(NSInteger, CSMachineSpectrumModel) { + CSMachineSpectrumModelSixteenK, + CSMachineSpectrumModelFortyEightK, + CSMachineSpectrumModelOneTwoEightK, + CSMachineSpectrumModelPlus2, CSMachineSpectrumModelPlus2a, CSMachineSpectrumModelPlus3, }; diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm index 993a5c047..8f4c693b4 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm @@ -194,8 +194,12 @@ using Target = Analyser::Static::ZXSpectrum::Target; auto target = std::make_unique<Target>(); switch(model) { - case CSMachineSpectrumModelPlus2a: target->model = Target::Model::Plus2a; break; - case CSMachineSpectrumModelPlus3: target->model = Target::Model::Plus3; break; + case CSMachineSpectrumModelSixteenK: target->model = Target::Model::SixteenK; break; + case CSMachineSpectrumModelFortyEightK: target->model = Target::Model::FortyEightK; break; + case CSMachineSpectrumModelOneTwoEightK: target->model = Target::Model::OneTwoEightK; break; + case CSMachineSpectrumModelPlus2: target->model = Target::Model::Plus2; break; + case CSMachineSpectrumModelPlus2a: target->model = Target::Model::Plus2a; break; + case CSMachineSpectrumModelPlus3: target->model = Target::Model::Plus3; break; } _targets.push_back(std::move(target)); } diff --git a/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib b/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib index 31e1925f8..cc68c2170 100644 --- a/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib +++ b/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib @@ -18,7 +18,7 @@ <windowStyleMask key="styleMask" titled="YES" documentModal="YES"/> <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> <rect key="contentRect" x="196" y="240" width="590" height="316"/> - <rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/> + <rect key="screenRect" x="0.0" y="0.0" width="2560" height="1440"/> <view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ"> <rect key="frame" x="0.0" y="0.0" width="590" height="316"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> @@ -584,11 +584,11 @@ Gw </tabViewItem> <tabViewItem label="ZX81" identifier="zx81" id="Wnn-nQ-gZ6"> <view key="view" id="bmd-gL-gzT"> - <rect key="frame" x="10" y="7" width="400" height="76"/> + <rect key="frame" x="10" y="7" width="400" height="186"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5aO-UX-HnX"> - <rect key="frame" x="108" y="47" width="116" height="25"/> + <rect key="frame" x="108" y="157" width="116" height="25"/> <popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="7QC-Ij-hES" id="d3W-Gl-3Mf"> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> <font key="font" metaFont="menu"/> @@ -602,7 +602,7 @@ Gw </popUpButtonCell> </popUpButton> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8tU-73-XEE"> - <rect key="frame" x="18" y="53" width="87" height="16"/> + <rect key="frame" x="18" y="163" width="87" height="16"/> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="z4b-oR-Yl2"> <font key="font" metaFont="system"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> @@ -622,24 +622,28 @@ Gw </tabViewItem> <tabViewItem label="ZX Spectrum" identifier="spectrum" id="HQv-oF-k8b"> <view key="view" id="bMx-F6-JUb"> - <rect key="frame" x="10" y="7" width="400" height="76"/> + <rect key="frame" x="10" y="7" width="400" height="186"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gFZ-d4-WFv"> - <rect key="frame" x="67" y="47" width="62" height="25"/> - <popUpButtonCell key="cell" type="push" title="+2a" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="21" imageScaling="axesIndependently" inset="2" selectedItem="Fo7-NL-Kv5" id="tYs-sA-oek"> + <rect key="frame" x="67" y="157" width="76" height="25"/> + <popUpButtonCell key="cell" type="push" title="16kb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" tag="16" imageScaling="axesIndependently" inset="2" selectedItem="Fo7-NL-Kv5" id="tYs-sA-oek"> <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> <font key="font" metaFont="menu"/> <menu key="menu" id="8lt-dk-zPr"> <items> - <menuItem title="+2a" state="on" tag="21" id="Fo7-NL-Kv5"/> + <menuItem title="16kb" tag="16" id="Fo7-NL-Kv5"/> + <menuItem title="48kb" tag="48" id="xks-Rv-Umd"/> + <menuItem title="128kb" tag="128" id="w8h-lY-JLX"/> + <menuItem title="+2" tag="2" id="Vvu-ua-pjg"/> + <menuItem title="+2a" state="on" tag="21" id="bFk-nC-Txe"/> <menuItem title="+3" tag="3" id="jwx-fZ-vXp"/> </items> </menu> </popUpButtonCell> </popUpButton> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fJ3-ma-Byy"> - <rect key="frame" x="18" y="53" width="46" height="16"/> + <rect key="frame" x="18" y="163" width="46" height="16"/> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="JId-Tp-LrE"> <font key="font" metaFont="system"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> diff --git a/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift b/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift index f358c8666..3445c27da 100644 --- a/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift +++ b/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift @@ -298,6 +298,10 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate { case "spectrum": var model: CSMachineSpectrumModel = .plus2a switch spectrumModelTypeButton.selectedItem!.tag { + case 16: model = .sixteenK + case 48: model = .fortyEightK + case 128: model = .oneTwoEightK + case 2: model = .plus2 case 21: model = .plus2a case 3: model = .plus3 default: break diff --git a/ROMImages/ZXSpectrum/128.rom b/ROMImages/ZXSpectrum/128.rom new file mode 100644 index 000000000..1ddee8c96 Binary files /dev/null and b/ROMImages/ZXSpectrum/128.rom differ diff --git a/ROMImages/ZXSpectrum/48.rom b/ROMImages/ZXSpectrum/48.rom new file mode 100644 index 000000000..4d6895e0b Binary files /dev/null and b/ROMImages/ZXSpectrum/48.rom differ diff --git a/ROMImages/ZXSpectrum/plus2.rom b/ROMImages/ZXSpectrum/plus2.rom new file mode 100644 index 000000000..2bd54e18e Binary files /dev/null and b/ROMImages/ZXSpectrum/plus2.rom differ diff --git a/ROMImages/ZXSpectrum/readme.txt b/ROMImages/ZXSpectrum/readme.txt index 63a56a9eb..afcbe6bef 100644 --- a/ROMImages/ZXSpectrum/readme.txt +++ b/ROMImages/ZXSpectrum/readme.txt @@ -9,3 +9,6 @@ material but retain that copyright"." With that in mind, Amstrad have kindly given their permission for the redistribution of their copyrighted material but retain that copyright. Material expected here, copyright Amstrad: plus3.rom — the +2a/+3 ROM file, 64kb in size. +plus2.rom – the +2 ROM file, 32kb in size. +128.rom – the 128kb ROM file, 32kb in size. +48.rom – the 16/48kb ROM file, 16kb in size. \ No newline at end of file