From 9cc747b3e2f2566ef28204cc0806b28335f02d2b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 24 Apr 2021 12:10:28 -0400 Subject: [PATCH 01/27] Resolves potential source of errors: specifying incorrect table size. (Having made exactly this mistake with the ZX Spectrum) --- Machines/AmstradCPC/Keyboard.cpp | 2 +- Machines/Commodore/Vic-20/Keyboard.cpp | 2 +- Machines/Electron/Keyboard.cpp | 2 +- Machines/Oric/Keyboard.cpp | 2 +- Machines/Sinclair/Keyboard/Keyboard.cpp | 6 +++--- Machines/Utility/Typer.cpp | 10 ---------- Machines/Utility/Typer.hpp | 12 ++++++++---- 7 files changed, 15 insertions(+), 21 deletions(-) diff --git a/Machines/AmstradCPC/Keyboard.cpp b/Machines/AmstradCPC/Keyboard.cpp index 98b67e8ae..7473f942f 100644 --- a/Machines/AmstradCPC/Keyboard.cpp +++ b/Machines/AmstradCPC/Keyboard.cpp @@ -151,7 +151,7 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const { #undef SHIFT #undef X - return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character); + return table_lookup_sequence_for_character(key_sequences, character); } bool CharacterMapper::needs_pause_after_key(uint16_t key) const { diff --git a/Machines/Commodore/Vic-20/Keyboard.cpp b/Machines/Commodore/Vic-20/Keyboard.cpp index 1b79bd414..62493d0e1 100644 --- a/Machines/Commodore/Vic-20/Keyboard.cpp +++ b/Machines/Commodore/Vic-20/Keyboard.cpp @@ -151,5 +151,5 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const { #undef SHIFT #undef X - return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character); + return table_lookup_sequence_for_character(key_sequences, character); } diff --git a/Machines/Electron/Keyboard.cpp b/Machines/Electron/Keyboard.cpp index c554c2cbe..689e5707f 100644 --- a/Machines/Electron/Keyboard.cpp +++ b/Machines/Electron/Keyboard.cpp @@ -145,7 +145,7 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const { #undef SHIFT #undef X - return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character); + return table_lookup_sequence_for_character(key_sequences, character); } bool CharacterMapper::needs_pause_after_key(uint16_t key) const { diff --git a/Machines/Oric/Keyboard.cpp b/Machines/Oric/Keyboard.cpp index 2e373ad57..3b12bc7e2 100644 --- a/Machines/Oric/Keyboard.cpp +++ b/Machines/Oric/Keyboard.cpp @@ -127,5 +127,5 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const { #undef SHIFT #undef X - return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character); + return table_lookup_sequence_for_character(key_sequences, character); } diff --git a/Machines/Sinclair/Keyboard/Keyboard.cpp b/Machines/Sinclair/Keyboard/Keyboard.cpp index 1e9db2bf8..f0cd23b39 100644 --- a/Machines/Sinclair/Keyboard/Keyboard.cpp +++ b/Machines/Sinclair/Keyboard/Keyboard.cpp @@ -275,13 +275,13 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const { switch(machine_) { case Machine::ZX80: - return table_lookup_sequence_for_character(zx80_key_sequences, sizeof(zx80_key_sequences), character); + return table_lookup_sequence_for_character(zx80_key_sequences, character); case Machine::ZX81: - return table_lookup_sequence_for_character(zx81_key_sequences, sizeof(zx81_key_sequences), character); + return table_lookup_sequence_for_character(zx81_key_sequences, character); case Machine::ZXSpectrum: - return table_lookup_sequence_for_character(spectrum_key_sequences, sizeof(zx81_key_sequences), character); + return table_lookup_sequence_for_character(spectrum_key_sequences, character); } } diff --git a/Machines/Utility/Typer.cpp b/Machines/Utility/Typer.cpp index 9706d85bf..1e290bef5 100644 --- a/Machines/Utility/Typer.cpp +++ b/Machines/Utility/Typer.cpp @@ -131,13 +131,3 @@ bool Typer::type_next_character() { return true; } - -// MARK: - Character mapper - -const uint16_t *CharacterMapper::table_lookup_sequence_for_character(const KeySequence *sequences, std::size_t length, char character) const { - std::size_t ucharacter = size_t((unsigned char)character); - if(ucharacter >= (length / sizeof(KeySequence))) return nullptr; - if(sequences[ucharacter][0] == MachineTypes::MappedKeyboardMachine::KeyNotMapped) return nullptr; - return sequences[ucharacter]; -} - diff --git a/Machines/Utility/Typer.hpp b/Machines/Utility/Typer.hpp index 0e0a15b3a..79ac1b86a 100644 --- a/Machines/Utility/Typer.hpp +++ b/Machines/Utility/Typer.hpp @@ -44,11 +44,15 @@ class CharacterMapper { typedef uint16_t KeySequence[16]; /*! - Provided in the base class as a convenience: given the lookup table of key sequences @c sequences, - with @c length entries, returns the sequence for character @c character if it exists; otherwise - returns @c nullptr. + Provided in the base class as a convenience: given the C array of key sequences @c sequences, + returns the sequence for character @c character if it exists; otherwise returns @c nullptr. */ - const uint16_t *table_lookup_sequence_for_character(const KeySequence *sequences, std::size_t length, char character) const; + template const uint16_t *table_lookup_sequence_for_character(const Collection &sequences, char character) const { + std::size_t ucharacter = size_t((unsigned char)character); + if(ucharacter >= sizeof(sequences) / sizeof(KeySequence)) return nullptr; + if(sequences[ucharacter][0] == MachineTypes::MappedKeyboardMachine::KeyNotMapped) return nullptr; + return sequences[ucharacter]; + } }; /*! From d61f478a395563e665021b1b17f0ad217e5276ea Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 24 Apr 2021 23:17:47 -0400 Subject: [PATCH 02/27] Basic sketch for state snapshots: an extra field on Target. I think it doesn't make sense for states to own a target as that complicates the concept of Media. Plus they're distinct because it makes sense to have only one per Target. Let's see how this pans out. --- Analyser/Static/StaticAnalyser.cpp | 43 ++++++++++++++++--- Analyser/Static/StaticAnalyser.hpp | 12 ++++-- Machines/MachineTypes.hpp | 1 + Machines/StateProducer.hpp | 26 +++++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 18 ++++++++ OSBindings/Mac/Clock Signal/Info.plist | 20 +++++++++ Storage/State/SNA.cpp | 36 ++++++++++++++++ Storage/State/SNA.hpp | 24 +++++++++++ 8 files changed, 170 insertions(+), 10 deletions(-) create mode 100644 Machines/StateProducer.hpp create mode 100644 Storage/State/SNA.cpp create mode 100644 Storage/State/SNA.hpp diff --git a/Analyser/Static/StaticAnalyser.cpp b/Analyser/Static/StaticAnalyser.cpp index e2c2e2823..e24897264 100644 --- a/Analyser/Static/StaticAnalyser.cpp +++ b/Analyser/Static/StaticAnalyser.cpp @@ -57,6 +57,9 @@ #include "../../Storage/MassStorage/Formats/DAT.hpp" #include "../../Storage/MassStorage/Formats/HFV.hpp" +// State Snapshots +#include "../../Storage/State/SNA.hpp" + // Tapes #include "../../Storage/Tape/Formats/CAS.hpp" #include "../../Storage/Tape/Formats/CommodoreTAP.hpp" @@ -73,15 +76,23 @@ using namespace Analyser::Static; -static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) { - Media result; +namespace { +std::string get_extension(const std::string &name) { // Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase // test as to file format. - std::string::size_type final_dot = file_name.find_last_of("."); - if(final_dot == std::string::npos) return result; - std::string extension = file_name.substr(final_dot + 1); + std::string::size_type final_dot = name.find_last_of("."); + if(final_dot == std::string::npos) return name; + std::string extension = name.substr(final_dot + 1); std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); + return extension; +} + +} + +static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) { + Media result; + const std::string extension = get_extension(file_name); #define InsertInstance(list, instance, platforms) \ list.emplace_back(instance);\ @@ -189,6 +200,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: #undef TryInsert #undef InsertInstance + return result; } @@ -199,14 +211,31 @@ Media Analyser::Static::GetMedia(const std::string &file_name) { TargetList Analyser::Static::GetTargets(const std::string &file_name) { TargetList targets; + const std::string extension = get_extension(file_name); + // Check whether the file directly identifies a target; if so then just return that. +#define Format(ext, class) \ + if(extension == ext) { \ + auto target = Storage::State::class::load(file_name); \ + if(target) { \ + targets.push_back(std::move(target)); \ + return targets; \ + } \ + } + + Format("sna", SNA); + +#undef TryInsert + + // Otherwise: + // // Collect all disks, tapes ROMs, etc as can be extrapolated from this file, forming the // union of all platforms this file might be a target for. TargetPlatform::IntType potential_platforms = 0; Media media = GetMediaAndPlatforms(file_name, potential_platforms); - // Hand off to platform-specific determination of whether these things are actually compatible and, - // if so, how to load them. + // Hand off to platform-specific determination of whether these + // things are actually compatible and, if so, how to load them. #define Append(x) if(potential_platforms & TargetPlatform::x) {\ auto new_targets = x::GetTargets(media, file_name, potential_platforms);\ std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));\ diff --git a/Analyser/Static/StaticAnalyser.hpp b/Analyser/Static/StaticAnalyser.hpp index e806ae042..9e7372cac 100644 --- a/Analyser/Static/StaticAnalyser.hpp +++ b/Analyser/Static/StaticAnalyser.hpp @@ -15,6 +15,7 @@ #include "../../Storage/Disk/Disk.hpp" #include "../../Storage/MassStorage/MassStorageDevice.hpp" #include "../../Storage/Tape/Tape.hpp" +#include "../../Reflection/Struct.hpp" #include #include @@ -23,8 +24,10 @@ namespace Analyser { namespace Static { +struct State; + /*! - A list of disks, tapes and cartridges. + A list of disks, tapes and cartridges, and possibly a state snapshot. */ struct Media { std::vector> disks; @@ -48,13 +51,16 @@ struct Media { }; /*! - A list of disks, tapes and cartridges plus information about the machine to which to attach them and its configuration, - and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness. + Describes a machine and possibly its state; conventionally subclassed to add other machine-specific configuration fields and any + necessary instructions on how to launch any software provided, plus a measure of confidence in this target's correctness. */ struct Target { Target(Machine machine) : machine(machine) {} virtual ~Target() {} + // This field is entirely optional. + std::shared_ptr state; + Machine machine; Media media; float confidence = 0.0f; diff --git a/Machines/MachineTypes.hpp b/Machines/MachineTypes.hpp index c96a02f10..f19e18d3a 100644 --- a/Machines/MachineTypes.hpp +++ b/Machines/MachineTypes.hpp @@ -20,6 +20,7 @@ #include "MediaTarget.hpp" #include "MouseMachine.hpp" #include "ScanProducer.hpp" +#include "StateProducer.hpp" #include "TimedMachine.hpp" #endif /* MachineTypes_h */ diff --git a/Machines/StateProducer.hpp b/Machines/StateProducer.hpp new file mode 100644 index 000000000..3a27e3cd0 --- /dev/null +++ b/Machines/StateProducer.hpp @@ -0,0 +1,26 @@ +// +// StateProducer.hpp +// Clock Signal +// +// Created by Thomas Harte on 24/04/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#ifndef State_h +#define State_h + +#include +#include "../Analyser/Static/StaticAnalyser.hpp" + +namespace MachineTypes { + +struct StateProducer { + // TODO. +// virtual bool get_state(Analyser::Static::State *, [[maybe_unused]] bool advance_to_simple = false) { +// return false; +// } +}; + +}; + +#endif /* State_h */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 24c509446..60002f0fd 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -479,6 +479,8 @@ 4B89453E201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894517201967B4007DE474 /* StaticAnalyser.cpp */; }; 4B89453F201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894517201967B4007DE474 /* StaticAnalyser.cpp */; }; 4B8DD3682633B2D400B3C866 /* SpectrumVideoContentionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DD3672633B2D400B3C866 /* SpectrumVideoContentionTests.mm */; }; + 4B8DD3862634D37E00B3C866 /* SNA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DD3842634D37E00B3C866 /* SNA.cpp */; }; + 4B8DD3872634D37E00B3C866 /* SNA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DD3842634D37E00B3C866 /* SNA.cpp */; }; 4B8DF4D825465B7500F3433C /* IIgsMemoryMapTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DF4D725465B7500F3433C /* IIgsMemoryMapTests.mm */; }; 4B8DF4F9254E36AE00F3433C /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DF4F7254E36AD00F3433C /* Video.cpp */; }; 4B8DF4FA254E36AE00F3433C /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DF4F7254E36AD00F3433C /* Video.cpp */; }; @@ -1429,6 +1431,9 @@ 4B8A7E85212F988200F2BBC6 /* DeferredQueue.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DeferredQueue.hpp; sourceTree = ""; }; 4B8D287E1F77207100645199 /* TrackSerialiser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TrackSerialiser.hpp; sourceTree = ""; }; 4B8DD3672633B2D400B3C866 /* SpectrumVideoContentionTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = SpectrumVideoContentionTests.mm; sourceTree = ""; }; + 4B8DD375263481BB00B3C866 /* StateProducer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = StateProducer.hpp; sourceTree = ""; }; + 4B8DD3842634D37E00B3C866 /* SNA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SNA.cpp; sourceTree = ""; }; + 4B8DD3852634D37E00B3C866 /* SNA.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SNA.hpp; sourceTree = ""; }; 4B8DF4D62546561300F3433C /* MemoryMap.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MemoryMap.hpp; sourceTree = ""; }; 4B8DF4D725465B7500F3433C /* IIgsMemoryMapTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = IIgsMemoryMapTests.mm; sourceTree = ""; }; 4B8DF4ED254B840B00F3433C /* AppleClock.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AppleClock.hpp; sourceTree = ""; }; @@ -2842,6 +2847,7 @@ 4B8805F81DCFF6CD003085B1 /* Data */, 4BAB62AA1D3272D200DF5BA0 /* Disk */, 4B6AAEA1230E3E1D0078E864 /* MassStorage */, + 4B8DD3832634D37E00B3C866 /* State */, 4B69FB3A1C4D908A00B5F0AA /* Tape */, ); name = Storage; @@ -3248,6 +3254,15 @@ path = AmstradCPC; sourceTree = ""; }; + 4B8DD3832634D37E00B3C866 /* State */ = { + isa = PBXGroup; + children = ( + 4B8DD3842634D37E00B3C866 /* SNA.cpp */, + 4B8DD3852634D37E00B3C866 /* SNA.hpp */, + ); + path = State; + sourceTree = ""; + }; 4B8DF4EC254B840B00F3433C /* AppleClock */ = { isa = PBXGroup; children = ( @@ -4018,6 +4033,7 @@ 4B92294222B04A3D00A1458F /* MouseMachine.hpp */, 4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */, 4B046DC31CFE651500E9E45E /* ScanProducer.hpp */, + 4B8DD375263481BB00B3C866 /* StateProducer.hpp */, 4BC57CD32434282000FBC404 /* TimedMachine.hpp */, 4B38F3491F2EC12000D9235D /* AmstradCPC */, 4BCE0048227CE8CA000CA200 /* Apple */, @@ -5216,6 +5232,7 @@ 4BEDA43225B3C700000C2DBD /* Executor.cpp in Sources */, 4BC1317B2346DF2B00E4FF3D /* MSA.cpp in Sources */, 4B894533201967B4007DE474 /* 6502.cpp in Sources */, + 4B8DD3872634D37E00B3C866 /* SNA.cpp in Sources */, 4B055AA91FAE85EF0060FFFF /* CommodoreGCR.cpp in Sources */, 4B055ADB1FAE9B460060FFFF /* 6560.cpp in Sources */, 4B17B58C20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */, @@ -5486,6 +5503,7 @@ 4BEBFB4D2002C4BF000708CC /* MSXDSK.cpp in Sources */, 4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */, 4B83348A1F5DB94B0097E338 /* IRQDelegatePortHandler.cpp in Sources */, + 4B8DD3862634D37E00B3C866 /* SNA.cpp in Sources */, 4B4DEC06252BFA56004583AC /* 65816Base.cpp in Sources */, 4B894524201967B4007DE474 /* Tape.cpp in Sources */, 4B7136891F78725F008B8ED9 /* Shifter.cpp in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index 5bb5dc8d1..aac5bb5fd 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -592,6 +592,26 @@ NSDocumentClass $(PRODUCT_MODULE_NAME).MachineDocument + + CFBundleTypeExtensions + + sna + + CFBundleTypeName + ZX Spectrum SNA image + CFBundleTypeOSTypes + + ???? + + CFBundleTypeRole + Viewer + LSHandlerRank + Owner + LSTypeIsPackage + + NSDocumentClass + $(PRODUCT_MODULE_NAME).MachineDocument + CFBundleExecutable $(EXECUTABLE_NAME) diff --git a/Storage/State/SNA.cpp b/Storage/State/SNA.cpp new file mode 100644 index 000000000..7199a5c2f --- /dev/null +++ b/Storage/State/SNA.cpp @@ -0,0 +1,36 @@ +// +// SNA.cpp +// Clock Signal +// +// Created by Thomas Harte on 24/04/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#include "SNA.hpp" + +using namespace Storage::State; + +std::unique_ptr SNA::load(const std::string &file_name) { + // 0x1a byte header: + // + // 00 I + // 01 HL' + // 03 DE' + // 05 BC' + // 07 AF' + // 09 HL + // 0B DE + // 0D BC + // 0F IY + // 11 IX + // 13 IFF2 (in bit 2) + // 14 R + // 15 AF + // 17 SP + // 19 interrupt mode + // 1A border colour + // 1B– 48kb RAM contents + + (void)file_name; + return nullptr; +} diff --git a/Storage/State/SNA.hpp b/Storage/State/SNA.hpp new file mode 100644 index 000000000..9afa703b0 --- /dev/null +++ b/Storage/State/SNA.hpp @@ -0,0 +1,24 @@ +// +// SNA.hpp +// Clock Signal +// +// Created by Thomas Harte on 24/04/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#ifndef SNA_hpp +#define SNA_hpp + +#include "../../Analyser/Static/StaticAnalyser.hpp" + +namespace Storage { +namespace State { + +struct SNA { + static std::unique_ptr load(const std::string &file_name); +}; + +} +} + +#endif /* SNA_hpp */ From e7a9ae18a1877751343798e6ea008c6fc6c2f493 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 24 Apr 2021 23:18:00 -0400 Subject: [PATCH 03/27] Introduce further default state. --- Processors/Z80/State/State.hpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Processors/Z80/State/State.hpp b/Processors/Z80/State/State.hpp index f5676aa60..adce4d7f6 100644 --- a/Processors/Z80/State/State.hpp +++ b/Processors/Z80/State/State.hpp @@ -60,25 +60,25 @@ struct State: public Reflection::StructImpl { obviously doesn't. */ struct ExecutionState: public Reflection::StructImpl { - bool is_halted; + bool is_halted = false; - uint8_t requests; - uint8_t last_requests; - uint8_t temp8; - uint8_t operation; - uint16_t temp16; - unsigned int flag_adjustment_history; - uint16_t pc_increment; - uint16_t refresh_address; + uint8_t requests = 0; + uint8_t last_requests = 0; + uint8_t temp8 = 0; + uint8_t operation = 0; + uint16_t temp16 = 0; + unsigned int flag_adjustment_history = 0; + uint16_t pc_increment = 1; + uint16_t refresh_address = 0; ReflectableEnum(Phase, UntakenConditionalCall, Reset, IRQMode0, IRQMode1, IRQMode2, NMI, FetchDecode, Operation ); - Phase phase; - int half_cycles_into_step; - int steps_into_phase; + Phase phase = Phase::FetchDecode; + int half_cycles_into_step = 0; + int steps_into_phase = 0; uint16_t instruction_page = 0; ExecutionState(); From 1c2ea0d7fe665d8e5be09230830a2a4b52b2f6b0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 24 Apr 2021 23:19:30 -0400 Subject: [PATCH 04/27] `unique_ptr` makes more sense here. --- Analyser/Static/StaticAnalyser.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Analyser/Static/StaticAnalyser.hpp b/Analyser/Static/StaticAnalyser.hpp index 9e7372cac..259f7cca9 100644 --- a/Analyser/Static/StaticAnalyser.hpp +++ b/Analyser/Static/StaticAnalyser.hpp @@ -59,7 +59,7 @@ struct Target { virtual ~Target() {} // This field is entirely optional. - std::shared_ptr state; + std::unique_ptr state; Machine machine; Media media; From 14ae579fca5c9a9839dc9d096af5c4a52294f311 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 24 Apr 2021 23:19:41 -0400 Subject: [PATCH 05/27] Add further note to future self. --- Storage/State/SNA.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Storage/State/SNA.cpp b/Storage/State/SNA.cpp index 7199a5c2f..15595cab2 100644 --- a/Storage/State/SNA.cpp +++ b/Storage/State/SNA.cpp @@ -30,6 +30,8 @@ std::unique_ptr SNA::load(const std::string &file_name // 19 interrupt mode // 1A border colour // 1B– 48kb RAM contents + // + // (perform a POP to get the PC) (void)file_name; return nullptr; From 5b419ca5bf9c8089d861dd641617d4cdcd0fe665 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 24 Apr 2021 23:25:08 -0400 Subject: [PATCH 06/27] Add State folder to Scons and Qt projects. --- OSBindings/Qt/ClockSignal.pro | 2 ++ OSBindings/SDL/SConstruct | 1 + 2 files changed, 3 insertions(+) diff --git a/OSBindings/Qt/ClockSignal.pro b/OSBindings/Qt/ClockSignal.pro index a78500ab7..65055ae4d 100644 --- a/OSBindings/Qt/ClockSignal.pro +++ b/OSBindings/Qt/ClockSignal.pro @@ -129,6 +129,7 @@ SOURCES += \ $$SRC/Storage/MassStorage/Encodings/*.cpp \ $$SRC/Storage/MassStorage/Formats/*.cpp \ $$SRC/Storage/MassStorage/SCSI/*.cpp \ + $$SRC/Storage/State/*.cpp \ $$SRC/Storage/Tape/*.cpp \ $$SRC/Storage/Tape/Formats/*.cpp \ $$SRC/Storage/Tape/Parsers/*.cpp \ @@ -269,6 +270,7 @@ HEADERS += \ $$SRC/Storage/MassStorage/Encodings/*.hpp \ $$SRC/Storage/MassStorage/Formats/*.hpp \ $$SRC/Storage/MassStorage/SCSI/*.hpp \ + $$SRC/Storage/State/*.hpp \ $$SRC/Storage/Tape/*.hpp \ $$SRC/Storage/Tape/Formats/*.hpp \ $$SRC/Storage/Tape/Parsers/*.hpp \ diff --git a/OSBindings/SDL/SConstruct b/OSBindings/SDL/SConstruct index 6be9e9e28..2511a5291 100644 --- a/OSBindings/SDL/SConstruct +++ b/OSBindings/SDL/SConstruct @@ -125,6 +125,7 @@ SOURCES += glob.glob('../../Storage/MassStorage/*.cpp') SOURCES += glob.glob('../../Storage/MassStorage/Encodings/*.cpp') SOURCES += glob.glob('../../Storage/MassStorage/Formats/*.cpp') SOURCES += glob.glob('../../Storage/MassStorage/SCSI/*.cpp') +SOURCES += glob.glob('../../Storage/State/*.cpp') SOURCES += glob.glob('../../Storage/Tape/*.cpp') SOURCES += glob.glob('../../Storage/Tape/Formats/*.cpp') SOURCES += glob.glob('../../Storage/Tape/Parsers/*.cpp') From 485c2a866c67cc8309a0da90999912918492cd64 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 24 Apr 2021 23:38:00 -0400 Subject: [PATCH 07/27] Without yet a struct for Spectrum states, at least checks general wiring. --- Storage/State/SNA.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Storage/State/SNA.cpp b/Storage/State/SNA.cpp index 15595cab2..99af049d1 100644 --- a/Storage/State/SNA.cpp +++ b/Storage/State/SNA.cpp @@ -7,10 +7,17 @@ // #include "SNA.hpp" +#include "../../Analyser/Static/ZXSpectrum/Target.hpp" using namespace Storage::State; std::unique_ptr SNA::load(const std::string &file_name) { + using Target = Analyser::Static::ZXSpectrum::Target; + auto result = std::make_unique(); + + // SNAs are always for 48kb machines. + result->model = Target::Model::FortyEightK; + // 0x1a byte header: // // 00 I @@ -34,5 +41,6 @@ std::unique_ptr SNA::load(const std::string &file_name // (perform a POP to get the PC) (void)file_name; - return nullptr; + + return result; } From cc78bfb229fd3213c335ccb4c0880d4245ec146c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Apr 2021 13:00:43 -0400 Subject: [PATCH 08/27] Forwards most of the Z80 state. --- Analyser/Static/StaticAnalyser.cpp | 16 +++-- Machines/Sinclair/ZXSpectrum/State.hpp | 32 +++++++++ Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 15 +++-- .../Clock Signal.xcodeproj/project.pbxproj | 2 + Storage/State/SNA.cpp | 65 +++++++++++++------ 5 files changed, 97 insertions(+), 33 deletions(-) create mode 100644 Machines/Sinclair/ZXSpectrum/State.hpp diff --git a/Analyser/Static/StaticAnalyser.cpp b/Analyser/Static/StaticAnalyser.cpp index e24897264..8cc0954de 100644 --- a/Analyser/Static/StaticAnalyser.cpp +++ b/Analyser/Static/StaticAnalyser.cpp @@ -214,13 +214,15 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) { const std::string extension = get_extension(file_name); // Check whether the file directly identifies a target; if so then just return that. -#define Format(ext, class) \ - if(extension == ext) { \ - auto target = Storage::State::class::load(file_name); \ - if(target) { \ - targets.push_back(std::move(target)); \ - return targets; \ - } \ +#define Format(ext, class) \ + if(extension == ext) { \ + try { \ + auto target = Storage::State::class::load(file_name); \ + if(target) { \ + targets.push_back(std::move(target)); \ + return targets; \ + } \ + } catch(...) {} \ } Format("sna", SNA); diff --git a/Machines/Sinclair/ZXSpectrum/State.hpp b/Machines/Sinclair/ZXSpectrum/State.hpp new file mode 100644 index 000000000..dc6a9bff2 --- /dev/null +++ b/Machines/Sinclair/ZXSpectrum/State.hpp @@ -0,0 +1,32 @@ +// +// State.hpp +// Clock Signal +// +// Created by Thomas Harte on 25/04/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#ifndef State_hpp +#define State_hpp + +#include "../../../Reflection/Struct.hpp" +#include "../../../Processors/Z80/State/State.hpp" + +namespace Sinclair { +namespace ZXSpectrum { + + +struct State: public Reflection::StructImpl { + CPU::Z80::State z80; + + State() { + if(needs_declare()) { + DeclareField(z80); + } + } +}; + +} +} + +#endif /* State_h */ diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 06812cdce..bf3142cfc 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -8,9 +8,9 @@ #include "ZXSpectrum.hpp" +#include "State.hpp" #include "Video.hpp" - -#define LOG_PREFIX "[Spectrum] " +#include "../Keyboard/Keyboard.hpp" #include "../../../Activity/Source.hpp" #include "../../MachineTypes.hpp" @@ -24,7 +24,9 @@ // just grab the CPC's version of an FDC. #include "../../AmstradCPC/FDC.hpp" +#define LOG_PREFIX "[Spectrum] " #include "../../../Outputs/Log.hpp" + #include "../../../Outputs/Speaker/Implementation/CompoundSource.hpp" #include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" #include "../../../Outputs/Speaker/Implementation/SampleSource.hpp" @@ -39,10 +41,6 @@ #include "../../../ClockReceiver/JustInTime.hpp" -#include "../../../Processors/Z80/State/State.hpp" - -#include "../Keyboard/Keyboard.hpp" - #include namespace Sinclair { @@ -124,6 +122,11 @@ template class ConcreteMachine: duration_to_press_enter_ = Cycles(5 * clock_rate()); keyboard_.set_key_state(ZX::Keyboard::KeyEnter, true); } + + // Install state if supplied. + if(target.state) { + LOG("TODO: state"); + } } ~ConcreteMachine() { diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 60002f0fd..05d2ef9b4 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1434,6 +1434,7 @@ 4B8DD375263481BB00B3C866 /* StateProducer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = StateProducer.hpp; sourceTree = ""; }; 4B8DD3842634D37E00B3C866 /* SNA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SNA.cpp; sourceTree = ""; }; 4B8DD3852634D37E00B3C866 /* SNA.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SNA.hpp; sourceTree = ""; }; + 4B8DD3912635A72F00B3C866 /* State.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = State.hpp; sourceTree = ""; }; 4B8DF4D62546561300F3433C /* MemoryMap.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MemoryMap.hpp; sourceTree = ""; }; 4B8DF4D725465B7500F3433C /* IIgsMemoryMapTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = IIgsMemoryMapTests.mm; sourceTree = ""; }; 4B8DF4ED254B840B00F3433C /* AppleClock.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AppleClock.hpp; sourceTree = ""; }; @@ -2215,6 +2216,7 @@ 4B0F1BFA260300D900B85C66 /* ZXSpectrum.cpp */, 4B0F1BFB260300D900B85C66 /* ZXSpectrum.hpp */, 4B0F1C092603BA5F00B85C66 /* Video.hpp */, + 4B8DD3912635A72F00B3C866 /* State.hpp */, ); path = ZXSpectrum; sourceTree = ""; diff --git a/Storage/State/SNA.cpp b/Storage/State/SNA.cpp index 99af049d1..dc2ad8e2b 100644 --- a/Storage/State/SNA.cpp +++ b/Storage/State/SNA.cpp @@ -7,40 +7,65 @@ // #include "SNA.hpp" + +#include "../FileHolder.hpp" + #include "../../Analyser/Static/ZXSpectrum/Target.hpp" +#include "../../Machines/Sinclair/ZXSpectrum/State.hpp" using namespace Storage::State; std::unique_ptr SNA::load(const std::string &file_name) { - using Target = Analyser::Static::ZXSpectrum::Target; - auto result = std::make_unique(); + // Make sure the file is accessible and appropriately sized. + FileHolder file(file_name); + if(file.stats().st_size != 48*1024 + 0x1b) { + return nullptr; + } // SNAs are always for 48kb machines. + using Target = Analyser::Static::ZXSpectrum::Target; + auto result = std::make_unique(); result->model = Target::Model::FortyEightK; - // 0x1a byte header: - // + // Prepare to populate ZX Spectrum state. + auto *const state = new Sinclair::ZXSpectrum::State(); + result->state = std::unique_ptr(state); + + // Comments below: [offset] [contents] + // 00 I - // 01 HL' - // 03 DE' - // 05 BC' - // 07 AF' - // 09 HL - // 0B DE - // 0D BC - // 0F IY - // 11 IX + const uint8_t i = file.get8(); + + // 01 HL'; 03 DE'; 05 BC'; 07 AF' + state->z80.registers.hlDash = file.get16le(); + state->z80.registers.deDash = file.get16le(); + state->z80.registers.bcDash = file.get16le(); + state->z80.registers.afDash = file.get16le(); + + // 09 HL; 0B DE; 0D BC; 0F IY; 11 IX + state->z80.registers.hl = file.get16le(); + state->z80.registers.de = file.get16le(); + state->z80.registers.bc = file.get16le(); + state->z80.registers.iy = file.get16le(); + state->z80.registers.ix = file.get16le(); + // 13 IFF2 (in bit 2) + const uint8_t iff = file.get8(); + state->z80.registers.iff1 = state->z80.registers.iff2 = iff & 4; + // 14 R - // 15 AF - // 17 SP - // 19 interrupt mode + const uint8_t r = file.get8(); + state->z80.registers.ir = uint16_t((i << 8) | r); + + // 15 AF; 17 SP; 19 interrupt mode + state->z80.registers.flags = file.get8(); + state->z80.registers.a = file.get8(); + state->z80.registers.stack_pointer = file.get16le(); + state->z80.registers.interrupt_mode = file.get8(); + + // TODO: border colour, RAM contents, then pop the program counter. // 1A border colour // 1B– 48kb RAM contents - // - // (perform a POP to get the PC) - - (void)file_name; return result; } From 7aeb17ac923c6b8f5c99e32637d8d582efafb629 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Apr 2021 13:01:23 -0400 Subject: [PATCH 09/27] Corrects HeaderDoc/etc directive. --- Storage/FileHolder.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Storage/FileHolder.hpp b/Storage/FileHolder.hpp index a78fce034..dfcfb4d29 100644 --- a/Storage/FileHolder.hpp +++ b/Storage/FileHolder.hpp @@ -43,7 +43,7 @@ class FileHolder final { Rewrite opens the file for rewriting; none of the original content is preserved; whatever the caller outputs will replace the existing file. - @raises ErrorCantOpen if the file cannot be opened. + @throws ErrorCantOpen if the file cannot be opened. */ FileHolder(const std::string &file_name, FileMode ideal_mode = FileMode::ReadWrite); From 0ebd900e4081b17ba18467537229241f886a01ec Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Apr 2021 13:03:24 -0400 Subject: [PATCH 10/27] Baby steps: apply Z80 state. As far as it currently is. Since SNA is leaving the PC at the default of 0x0000, this currently has no visible effect. --- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index bf3142cfc..23996270e 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -125,7 +125,10 @@ template class ConcreteMachine: // Install state if supplied. if(target.state) { - LOG("TODO: state"); + const auto state = static_cast(target.state.get()); + state->z80.apply(z80_); + + LOG("TODO: apply rest of state"); } } From a5098a60ec950e1379859959b3931525eef1a211 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Apr 2021 13:18:26 -0400 Subject: [PATCH 11/27] Attempts to get in-SNA software to start. --- Machines/Sinclair/ZXSpectrum/State.hpp | 2 ++ Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 1 + Storage/State/SNA.cpp | 12 +++++++++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Machines/Sinclair/ZXSpectrum/State.hpp b/Machines/Sinclair/ZXSpectrum/State.hpp index dc6a9bff2..60b6db78f 100644 --- a/Machines/Sinclair/ZXSpectrum/State.hpp +++ b/Machines/Sinclair/ZXSpectrum/State.hpp @@ -18,10 +18,12 @@ namespace ZXSpectrum { struct State: public Reflection::StructImpl { CPU::Z80::State z80; + std::vector ram; State() { if(needs_declare()) { DeclareField(z80); + DeclareField(ram); } } }; diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 23996270e..469e74fd8 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -127,6 +127,7 @@ template class ConcreteMachine: if(target.state) { const auto state = static_cast(target.state.get()); state->z80.apply(z80_); + memcpy(ram_.data(), state->ram.data(), std::min(ram_.size(), state->ram.size())); LOG("TODO: apply rest of state"); } diff --git a/Storage/State/SNA.cpp b/Storage/State/SNA.cpp index dc2ad8e2b..0f5c6a978 100644 --- a/Storage/State/SNA.cpp +++ b/Storage/State/SNA.cpp @@ -63,9 +63,19 @@ std::unique_ptr SNA::load(const std::string &file_name state->z80.registers.stack_pointer = file.get16le(); state->z80.registers.interrupt_mode = file.get8(); - // TODO: border colour, RAM contents, then pop the program counter. // 1A border colour + const uint8_t border_colour = file.get8(); + (void)border_colour; // TODO. + // 1B– 48kb RAM contents + state->ram = file.read(48*1024); + + // Establish program counter. + state->z80.registers.program_counter = state->ram[state->z80.registers.stack_pointer]; + state->ram[state->z80.registers.stack_pointer] = 0; + state->z80.registers.program_counter |= state->ram[state->z80.registers.stack_pointer+1] << 8; + state->ram[state->z80.registers.stack_pointer+1] = 0; + state->z80.registers.stack_pointer += 2; return result; } From 9b65d56ed0effa9aae175253366d29a277e1a048 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Apr 2021 13:26:53 -0400 Subject: [PATCH 12/27] Resolves potential flaw in POPping here. --- Storage/State/SNA.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Storage/State/SNA.cpp b/Storage/State/SNA.cpp index 0f5c6a978..2dce6657f 100644 --- a/Storage/State/SNA.cpp +++ b/Storage/State/SNA.cpp @@ -70,12 +70,11 @@ std::unique_ptr SNA::load(const std::string &file_name // 1B– 48kb RAM contents state->ram = file.read(48*1024); - // Establish program counter. - state->z80.registers.program_counter = state->ram[state->z80.registers.stack_pointer]; - state->ram[state->z80.registers.stack_pointer] = 0; - state->z80.registers.program_counter |= state->ram[state->z80.registers.stack_pointer+1] << 8; - state->ram[state->z80.registers.stack_pointer+1] = 0; - state->z80.registers.stack_pointer += 2; + // To establish program counter, point it to a RET that + // I know is in the 16/48kb ROM. This avoids having to + // try to do a pop here, given that the true program counter + // might currently be in the ROM. + state->z80.registers.program_counter = 0x1d83; return result; } From 2bbf8bc9faea5b121f6936e2b731890fa9f7386c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Apr 2021 13:27:11 -0400 Subject: [PATCH 13/27] Ensures 16/48kb snapshots are properly copied into place. --- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 469e74fd8..7e026dfdb 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -127,7 +127,17 @@ template class ConcreteMachine: if(target.state) { const auto state = static_cast(target.state.get()); state->z80.apply(z80_); - memcpy(ram_.data(), state->ram.data(), std::min(ram_.size(), state->ram.size())); + + // If this is a 48k or 16k machine, remap source data from its original + // linear form to whatever the banks end up being; otherwise copy as is. + if(model <= Model::FortyEightK) { + for(size_t c = 0; c < 48*1024 && c < state->ram.size(); c++) { + const auto address = c + 0x4000; + write_pointers_[address >> 14][address] = state->ram[c]; + } + } else { + memcpy(ram_.data(), state->ram.data(), std::min(ram_.size(), state->ram.size())); + } LOG("TODO: apply rest of state"); } From fd271d920bd1acfa799e003c2e23c88ec4326cfe Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Apr 2021 14:00:12 -0400 Subject: [PATCH 14/27] Adds capture and forwarding of border colour. --- Machines/Sinclair/ZXSpectrum/State.hpp | 3 ++ Machines/Sinclair/ZXSpectrum/Video.hpp | 36 ++++++++++++++------- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 6 ++-- Storage/State/SNA.cpp | 3 +- 4 files changed, 32 insertions(+), 16 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/State.hpp b/Machines/Sinclair/ZXSpectrum/State.hpp index 60b6db78f..1333c8d62 100644 --- a/Machines/Sinclair/ZXSpectrum/State.hpp +++ b/Machines/Sinclair/ZXSpectrum/State.hpp @@ -11,6 +11,7 @@ #include "../../../Reflection/Struct.hpp" #include "../../../Processors/Z80/State/State.hpp" +#include "Video.hpp" namespace Sinclair { namespace ZXSpectrum { @@ -18,11 +19,13 @@ namespace ZXSpectrum { struct State: public Reflection::StructImpl { CPU::Z80::State z80; + Video::State video; std::vector ram; State() { if(needs_declare()) { DeclareField(z80); + DeclareField(video); DeclareField(ram); } } diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp index c48392a61..8363d0ad3 100644 --- a/Machines/Sinclair/ZXSpectrum/Video.hpp +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -12,12 +12,15 @@ #include "../../../Outputs/CRT/CRT.hpp" #include "../../../ClockReceiver/ClockReceiver.hpp" +#include "../../../Reflection/Struct.hpp" + #include namespace Sinclair { namespace ZXSpectrum { +namespace Video { -enum class VideoTiming { +enum class Timing { FortyEightK, OneTwoEightK, Plus3, @@ -47,7 +50,7 @@ enum class VideoTiming { */ -template class Video { +template class Video { private: struct Timings { // Number of cycles per line. Will be 224 or 228. @@ -78,17 +81,17 @@ template class Video { }; static constexpr Timings get_timings() { - if constexpr (timing == VideoTiming::Plus3) { + if constexpr (timing == Timing::Plus3) { constexpr int delays[] = {1, 0, 7, 6, 5, 4, 3, 2}; return Timings(228, 311, 6, 129, 14361, delays); } - if constexpr (timing == VideoTiming::OneTwoEightK) { + if constexpr (timing == Timing::OneTwoEightK) { constexpr int delays[] = {6, 5, 4, 3, 2, 1, 0, 0}; return Timings(228, 311, 4, 128, 14361, delays); } - if constexpr (timing == VideoTiming::FortyEightK) { + if constexpr (timing == Timing::FortyEightK) { constexpr int delays[] = {6, 5, 4, 3, 2, 1, 0, 0}; return Timings(224, 312, 4, 128, 14335, delays); } @@ -103,7 +106,7 @@ template class Video { constexpr int sync_line = (timings.interrupt_time / timings.cycles_per_line) + 1; - constexpr int sync_position = (timing == VideoTiming::FortyEightK) ? 164 * 2 : 166 * 2; + constexpr int sync_position = (timing == Timing::FortyEightK) ? 164 * 2 : 166 * 2; constexpr int sync_length = 17 * 2; constexpr int burst_position = sync_position + 40; constexpr int burst_length = 17; @@ -220,7 +223,7 @@ template class Video { if(offset >= burst_position && offset < burst_position+burst_length && end_offset > offset) { const int burst_duration = std::min(burst_position + burst_length, end_offset) - offset; - if constexpr (timing >= VideoTiming::OneTwoEightK) { + if constexpr (timing >= Timing::OneTwoEightK) { crt_.output_colour_burst(burst_duration, 116, is_alternate_line_); // The colour burst phase above is an empirical guess. I need to research further. } else { @@ -248,7 +251,7 @@ template class Video { } static constexpr int half_cycles_per_line() { - if constexpr (timing == VideoTiming::FortyEightK) { + if constexpr (timing == Timing::FortyEightK) { // TODO: determine real figure here, if one exists. // The source I'm looking at now suggests that the theoretical // ideal of 224*2 ignores the real-life effects of separate @@ -328,7 +331,7 @@ template class Video { */ uint8_t get_floating_value() const { constexpr auto timings = get_timings(); - const uint8_t out_of_bounds = (timing == VideoTiming::Plus3) ? last_contended_access_ : 0xff; + const uint8_t out_of_bounds = (timing == Timing::Plus3) ? last_contended_access_ : 0xff; const int line = time_into_frame_ / timings.cycles_per_line; if(line >= 192) { @@ -342,7 +345,7 @@ template class Video { // The +2a and +3 always return the low bit as set. const uint8_t value = last_fetches_[(time_into_line >> 1) & 3]; - if constexpr (timing == VideoTiming::Plus3) { + if constexpr (timing == Timing::Plus3) { return value | 1; } return value; @@ -354,7 +357,7 @@ template class Video { bus is accessed when the gate array isn't currently reading. */ void set_last_contended_area_access([[maybe_unused]] uint8_t value) { - if constexpr (timing == VideoTiming::Plus3) { + if constexpr (timing == Timing::Plus3) { last_contended_access_ = value | 1; } } @@ -408,6 +411,17 @@ template class Video { #undef RGB }; +struct State: public Reflection::StructImpl { + uint8_t border_colour; + + State() { + if(needs_declare()) { + DeclareField(border_colour); + } + } +}; + +} } } diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 7e026dfdb..83a07ab98 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -775,10 +775,10 @@ template class ConcreteMachine: // MARK: - Video. using VideoType = std::conditional_t< - model <= Model::FortyEightK, Video, + model <= Model::FortyEightK, Video::Video, std::conditional_t< - model <= Model::Plus2, Video, - Video + model <= Model::Plus2, Video::Video, + Video::Video > >; JustInTimeActor video_; diff --git a/Storage/State/SNA.cpp b/Storage/State/SNA.cpp index 2dce6657f..7d29faf62 100644 --- a/Storage/State/SNA.cpp +++ b/Storage/State/SNA.cpp @@ -64,8 +64,7 @@ std::unique_ptr SNA::load(const std::string &file_name state->z80.registers.interrupt_mode = file.get8(); // 1A border colour - const uint8_t border_colour = file.get8(); - (void)border_colour; // TODO. + state->video.border_colour = file.get8(); // 1B– 48kb RAM contents state->ram = file.read(48*1024); From d80f03e36954e3303efa498105969e7c486bf6a8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Apr 2021 14:11:36 -0400 Subject: [PATCH 15/27] Corrects longstanding deviation from naming convention. --- Processors/Z80/Implementation/Z80Base.cpp | 48 +++++++++---------- .../Z80/Implementation/Z80Implementation.hpp | 14 +++--- Processors/Z80/Implementation/Z80Storage.hpp | 2 +- Processors/Z80/State/State.cpp | 24 +++++----- Processors/Z80/State/State.hpp | 2 +- Storage/State/SNA.cpp | 8 ++-- 6 files changed, 49 insertions(+), 49 deletions(-) diff --git a/Processors/Z80/Implementation/Z80Base.cpp b/Processors/Z80/Implementation/Z80Base.cpp index c1c10943a..1516220d5 100644 --- a/Processors/Z80/Implementation/Z80Base.cpp +++ b/Processors/Z80/Implementation/Z80Base.cpp @@ -33,18 +33,18 @@ uint16_t ProcessorBase::get_value_of_register(Register r) const { case Register::L: return hl_.halves.low; case Register::HL: return hl_.full; - case Register::ADash: return afDash_.halves.high; - case Register::FlagsDash: return afDash_.halves.low; - case Register::AFDash: return afDash_.full; - case Register::BDash: return bcDash_.halves.high; - case Register::CDash: return bcDash_.halves.low; - case Register::BCDash: return bcDash_.full; - case Register::DDash: return deDash_.halves.high; - case Register::EDash: return deDash_.halves.low; - case Register::DEDash: return deDash_.full; - case Register::HDash: return hlDash_.halves.high; - case Register::LDash: return hlDash_.halves.low; - case Register::HLDash: return hlDash_.full; + case Register::ADash: return af_dash_.halves.high; + case Register::FlagsDash: return af_dash_.halves.low; + case Register::AFDash: return af_dash_.full; + case Register::BDash: return bc_dash_.halves.high; + case Register::CDash: return bc_dash_.halves.low; + case Register::BCDash: return bc_dash_.full; + case Register::DDash: return de_dash_.halves.high; + case Register::EDash: return de_dash_.halves.low; + case Register::DEDash: return de_dash_.full; + case Register::HDash: return hl_dash_.halves.high; + case Register::LDash: return hl_dash_.halves.low; + case Register::HLDash: return hl_dash_.full; case Register::IXh: return ix_.halves.high; case Register::IXl: return ix_.halves.low; @@ -86,18 +86,18 @@ void ProcessorBase::set_value_of_register(Register r, uint16_t value) { case Register::L: hl_.halves.low = uint8_t(value); break; case Register::HL: hl_.full = value; break; - case Register::ADash: afDash_.halves.high = uint8_t(value); break; - case Register::FlagsDash: afDash_.halves.low = uint8_t(value); break; - case Register::AFDash: afDash_.full = value; break; - case Register::BDash: bcDash_.halves.high = uint8_t(value); break; - case Register::CDash: bcDash_.halves.low = uint8_t(value); break; - case Register::BCDash: bcDash_.full = value; break; - case Register::DDash: deDash_.halves.high = uint8_t(value); break; - case Register::EDash: deDash_.halves.low = uint8_t(value); break; - case Register::DEDash: deDash_.full = value; break; - case Register::HDash: hlDash_.halves.high = uint8_t(value); break; - case Register::LDash: hlDash_.halves.low = uint8_t(value); break; - case Register::HLDash: hlDash_.full = value; break; + case Register::ADash: af_dash_.halves.high = uint8_t(value); break; + case Register::FlagsDash: af_dash_.halves.low = uint8_t(value); break; + case Register::AFDash: af_dash_.full = value; break; + case Register::BDash: bc_dash_.halves.high = uint8_t(value); break; + case Register::CDash: bc_dash_.halves.low = uint8_t(value); break; + case Register::BCDash: bc_dash_.full = value; break; + case Register::DDash: de_dash_.halves.high = uint8_t(value); break; + case Register::EDash: de_dash_.halves.low = uint8_t(value); break; + case Register::DEDash: de_dash_.full = value; break; + case Register::HDash: hl_dash_.halves.high = uint8_t(value); break; + case Register::LDash: hl_dash_.halves.low = uint8_t(value); break; + case Register::HLDash: hl_dash_.full = value; break; case Register::IXh: ix_.halves.high = uint8_t(value); break; case Register::IXl: ix_.halves.low = uint8_t(value); break; diff --git a/Processors/Z80/Implementation/Z80Implementation.hpp b/Processors/Z80/Implementation/Z80Implementation.hpp index 490e5f50f..56cf63fd9 100644 --- a/Processors/Z80/Implementation/Z80Implementation.hpp +++ b/Processors/Z80/Implementation/Z80Implementation.hpp @@ -461,17 +461,17 @@ template < class T, case MicroOp::ExAFAFDash: { const uint8_t a = a_; const uint8_t f = get_flags(); - set_flags(afDash_.halves.low); - a_ = afDash_.halves.high; - afDash_.halves.high = a; - afDash_.halves.low = f; + set_flags(af_dash_.halves.low); + a_ = af_dash_.halves.high; + af_dash_.halves.high = a; + af_dash_.halves.low = f; } break; case MicroOp::EXX: { uint16_t temp; - swap(de_, deDash_); - swap(bc_, bcDash_); - swap(hl_, hlDash_); + swap(de_, de_dash_); + swap(bc_, bc_dash_); + swap(hl_, hl_dash_); } break; #undef swap diff --git a/Processors/Z80/Implementation/Z80Storage.hpp b/Processors/Z80/Implementation/Z80Storage.hpp index 613e9334a..b88bf2a84 100644 --- a/Processors/Z80/Implementation/Z80Storage.hpp +++ b/Processors/Z80/Implementation/Z80Storage.hpp @@ -129,7 +129,7 @@ class ProcessorStorage { uint8_t a_; RegisterPair16 bc_, de_, hl_; - RegisterPair16 afDash_, bcDash_, deDash_, hlDash_; + RegisterPair16 af_dash_, bc_dash_, de_dash_, hl_dash_; RegisterPair16 ix_, iy_, pc_, sp_; RegisterPair16 ir_, refresh_addr_; bool iff1_ = false, iff2_ = false; diff --git a/Processors/Z80/State/State.cpp b/Processors/Z80/State/State.cpp index c9b90f2de..4f6f83fae 100644 --- a/Processors/Z80/State/State.cpp +++ b/Processors/Z80/State/State.cpp @@ -19,10 +19,10 @@ State::State(const ProcessorBase &src): State() { registers.bc = src.bc_.full; registers.de = src.de_.full; registers.hl = src.hl_.full; - registers.afDash = src.afDash_.full; - registers.bcDash = src.bcDash_.full; - registers.deDash = src.deDash_.full; - registers.hlDash = src.hlDash_.full; + registers.af_dash = src.af_dash_.full; + registers.bc_dash = src.bc_dash_.full; + registers.de_dash = src.de_dash_.full; + registers.hl_dash = src.hl_dash_.full; registers.ix = src.ix_.full; registers.iy = src.iy_.full; registers.ir = src.ir_.full; @@ -108,10 +108,10 @@ void State::apply(ProcessorBase &target) { target.bc_.full = registers.bc; target.de_.full = registers.de; target.hl_.full = registers.hl; - target.afDash_.full = registers.afDash; - target.bcDash_.full = registers.bcDash; - target.deDash_.full = registers.deDash; - target.hlDash_.full = registers.hlDash; + target.af_dash_.full = registers.af_dash; + target.bc_dash_.full = registers.bc_dash; + target.de_dash_.full = registers.de_dash; + target.hl_dash_.full = registers.hl_dash; target.ix_.full = registers.ix; target.iy_.full = registers.iy; target.ir_.full = registers.ir; @@ -179,10 +179,10 @@ State::Registers::Registers() { DeclareField(bc); DeclareField(de); DeclareField(hl); - DeclareField(afDash); - DeclareField(bcDash); - DeclareField(deDash); - DeclareField(hlDash); + DeclareField(af_dash); // TODO: is there any disadvantage to declaring these for reflective + DeclareField(bc_dash); // purposes as AF', BC', etc? + DeclareField(de_dash); + DeclareField(hl_dash); DeclareField(ix); DeclareField(iy); DeclareField(ir); diff --git a/Processors/Z80/State/State.hpp b/Processors/Z80/State/State.hpp index adce4d7f6..b074e38bf 100644 --- a/Processors/Z80/State/State.hpp +++ b/Processors/Z80/State/State.hpp @@ -31,7 +31,7 @@ struct State: public Reflection::StructImpl { uint8_t a; uint8_t flags; uint16_t bc, de, hl; - uint16_t afDash, bcDash, deDash, hlDash; + uint16_t af_dash, bc_dash, de_dash, hl_dash; uint16_t ix, iy, ir; uint16_t program_counter, stack_pointer; uint16_t memptr; diff --git a/Storage/State/SNA.cpp b/Storage/State/SNA.cpp index 7d29faf62..5daa42444 100644 --- a/Storage/State/SNA.cpp +++ b/Storage/State/SNA.cpp @@ -37,10 +37,10 @@ std::unique_ptr SNA::load(const std::string &file_name const uint8_t i = file.get8(); // 01 HL'; 03 DE'; 05 BC'; 07 AF' - state->z80.registers.hlDash = file.get16le(); - state->z80.registers.deDash = file.get16le(); - state->z80.registers.bcDash = file.get16le(); - state->z80.registers.afDash = file.get16le(); + state->z80.registers.hl_dash = file.get16le(); + state->z80.registers.de_dash = file.get16le(); + state->z80.registers.bc_dash = file.get16le(); + state->z80.registers.af_dash = file.get16le(); // 09 HL; 0B DE; 0D BC; 0F IY; 11 IX state->z80.registers.hl = file.get16le(); From 0ef2806970e10b327fb98b398a9d93f99605d2f8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Apr 2021 14:16:35 -0400 Subject: [PATCH 16/27] Adds just enough to ensure that border state gets through. --- Machines/Sinclair/ZXSpectrum/Video.hpp | 20 +++++++++++++++++++- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 3 +-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp index 8363d0ad3..a6bc38a14 100644 --- a/Machines/Sinclair/ZXSpectrum/Video.hpp +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -401,6 +401,8 @@ template class Video { uint8_t last_fetches_[4] = {0xff, 0xff, 0xff, 0xff}; uint8_t last_contended_access_ = 0xff; + friend struct State; + #define RGB(r, g, b) (r << 4) | (g << 2) | b static constexpr uint8_t palette[] = { RGB(0, 0, 0), RGB(0, 0, 2), RGB(2, 0, 0), RGB(2, 0, 2), @@ -412,13 +414,29 @@ template class Video { }; struct State: public Reflection::StructImpl { - uint8_t border_colour; + uint8_t border_colour = 0; + int time_into_frame = 0; + bool flash = 0; + int flash_counter = 0; + bool is_alternate_line = false; State() { if(needs_declare()) { DeclareField(border_colour); + DeclareField(time_into_frame); + DeclareField(flash); + DeclareField(flash_counter); + DeclareField(is_alternate_line); } } + + template void apply(Video &target) { + target.set_border_colour(border_colour); + target.time_into_frame_ = time_into_frame; + target.flash_mask_ = flash ? 0xff : 0x00; + target.flash_counter_ = flash_counter; + target.is_alternate_line_ = is_alternate_line; + } }; } diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 83a07ab98..7a4bdd9c7 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -127,6 +127,7 @@ template class ConcreteMachine: if(target.state) { const auto state = static_cast(target.state.get()); state->z80.apply(z80_); + state->video.apply(*video_.last_valid()); // If this is a 48k or 16k machine, remap source data from its original // linear form to whatever the banks end up being; otherwise copy as is. @@ -138,8 +139,6 @@ template class ConcreteMachine: } else { memcpy(ram_.data(), state->ram.data(), std::min(ram_.size(), state->ram.size())); } - - LOG("TODO: apply rest of state"); } } From 25100642186d2746cc5868b199ee1f1927b2d112 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Apr 2021 14:20:40 -0400 Subject: [PATCH 17/27] Completes state object. Subject to not yet dealing with last_fetches_ and last_contended_access_ correctly. Thought required. --- Machines/Sinclair/ZXSpectrum/Video.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp index a6bc38a14..4304c5542 100644 --- a/Machines/Sinclair/ZXSpectrum/Video.hpp +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -366,6 +366,7 @@ template class Video { Sets the current border colour. */ void set_border_colour(uint8_t colour) { + border_byte_ = colour; border_colour_ = palette[colour]; } @@ -389,6 +390,7 @@ template class Video { Outputs::CRT::CRT crt_; const uint8_t *memory_ = nullptr; uint8_t border_colour_ = 0; + uint8_t border_byte_ = 0; uint8_t *pixel_target_ = nullptr; int attribute_address_ = 0; @@ -430,6 +432,14 @@ struct State: public Reflection::StructImpl { } } + template State(const Video &source) : State() { + border_colour = source.border_byte_; + time_into_frame = source.time_into_frame_; + flash = source.flash_mask_; + flash_counter = source.flash_counter_; + is_alternate_line = source. is_alternate_line_; + } + template void apply(Video &target) { target.set_border_colour(border_colour); target.time_into_frame_ = time_into_frame; From 205518ba75506b428310cd70cd50e7e80802b43c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Apr 2021 16:51:07 -0400 Subject: [PATCH 18/27] Switch to more efficient copy. --- Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 7a4bdd9c7..bde983d91 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -132,9 +132,9 @@ template class ConcreteMachine: // If this is a 48k or 16k machine, remap source data from its original // linear form to whatever the banks end up being; otherwise copy as is. if(model <= Model::FortyEightK) { - for(size_t c = 0; c < 48*1024 && c < state->ram.size(); c++) { - const auto address = c + 0x4000; - write_pointers_[address >> 14][address] = state->ram[c]; + const size_t num_banks = std::min(size_t(48*1024), state->ram.size()) >> 14; + for(size_t c = 0; c < num_banks; c++) { + memcpy(&write_pointers_[c + 1][(c+1) * 0x4000], &state->ram[c * 0x4000], 0x4000); } } else { memcpy(ram_.data(), state->ram.data(), std::min(ram_.size(), state->ram.size())); From 03577de675e3cd05d4c22face688408cfc28e6b4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Apr 2021 16:54:34 -0400 Subject: [PATCH 19/27] Adds an empty vessel for .z80 support. --- Analyser/Static/StaticAnalyser.cpp | 3 ++- .../Clock Signal.xcodeproj/project.pbxproj | 8 +++++++ OSBindings/Mac/Clock Signal/Info.plist | 22 ++++++++++++++++- Storage/State/Z80.cpp | 15 ++++++++++++ Storage/State/Z80.hpp | 24 +++++++++++++++++++ 5 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 Storage/State/Z80.cpp create mode 100644 Storage/State/Z80.hpp diff --git a/Analyser/Static/StaticAnalyser.cpp b/Analyser/Static/StaticAnalyser.cpp index 8cc0954de..97160fe37 100644 --- a/Analyser/Static/StaticAnalyser.cpp +++ b/Analyser/Static/StaticAnalyser.cpp @@ -59,6 +59,7 @@ // State Snapshots #include "../../Storage/State/SNA.hpp" +#include "../../Storage/State/Z80.hpp" // Tapes #include "../../Storage/Tape/Formats/CAS.hpp" @@ -200,7 +201,6 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: #undef TryInsert #undef InsertInstance - return result; } @@ -226,6 +226,7 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) { } Format("sna", SNA); + Format("z80", Z80); #undef TryInsert diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 05d2ef9b4..7d908b13d 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -481,6 +481,8 @@ 4B8DD3682633B2D400B3C866 /* SpectrumVideoContentionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DD3672633B2D400B3C866 /* SpectrumVideoContentionTests.mm */; }; 4B8DD3862634D37E00B3C866 /* SNA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DD3842634D37E00B3C866 /* SNA.cpp */; }; 4B8DD3872634D37E00B3C866 /* SNA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DD3842634D37E00B3C866 /* SNA.cpp */; }; + 4B8DD39726360DDF00B3C866 /* Z80.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DD39526360DDF00B3C866 /* Z80.cpp */; }; + 4B8DD39826360DDF00B3C866 /* Z80.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DD39526360DDF00B3C866 /* Z80.cpp */; }; 4B8DF4D825465B7500F3433C /* IIgsMemoryMapTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DF4D725465B7500F3433C /* IIgsMemoryMapTests.mm */; }; 4B8DF4F9254E36AE00F3433C /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DF4F7254E36AD00F3433C /* Video.cpp */; }; 4B8DF4FA254E36AE00F3433C /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DF4F7254E36AD00F3433C /* Video.cpp */; }; @@ -1435,6 +1437,8 @@ 4B8DD3842634D37E00B3C866 /* SNA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SNA.cpp; sourceTree = ""; }; 4B8DD3852634D37E00B3C866 /* SNA.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SNA.hpp; sourceTree = ""; }; 4B8DD3912635A72F00B3C866 /* State.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = State.hpp; sourceTree = ""; }; + 4B8DD39526360DDF00B3C866 /* Z80.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Z80.cpp; sourceTree = ""; }; + 4B8DD39626360DDF00B3C866 /* Z80.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Z80.hpp; sourceTree = ""; }; 4B8DF4D62546561300F3433C /* MemoryMap.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MemoryMap.hpp; sourceTree = ""; }; 4B8DF4D725465B7500F3433C /* IIgsMemoryMapTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = IIgsMemoryMapTests.mm; sourceTree = ""; }; 4B8DF4ED254B840B00F3433C /* AppleClock.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AppleClock.hpp; sourceTree = ""; }; @@ -3261,6 +3265,8 @@ children = ( 4B8DD3842634D37E00B3C866 /* SNA.cpp */, 4B8DD3852634D37E00B3C866 /* SNA.hpp */, + 4B8DD39526360DDF00B3C866 /* Z80.cpp */, + 4B8DD39626360DDF00B3C866 /* Z80.hpp */, ); path = State; sourceTree = ""; @@ -5268,6 +5274,7 @@ 4B055AC81FAE9AFB0060FFFF /* C1540.cpp in Sources */, 4B055A8F1FAE85A90060FFFF /* FileHolder.cpp in Sources */, 4B055A911FAE85B50060FFFF /* Cartridge.cpp in Sources */, + 4B8DD39826360DDF00B3C866 /* Z80.cpp in Sources */, 4B894525201967B4007DE474 /* Tape.cpp in Sources */, 4B055ACD1FAE9B030060FFFF /* Keyboard.cpp in Sources */, 4B055AB21FAE860F0060FFFF /* CommodoreTAP.cpp in Sources */, @@ -5364,6 +5371,7 @@ 4BE211FF253FC80900435408 /* StaticAnalyser.cpp in Sources */, 4B0F1BE22602FF9C00B85C66 /* ZX8081.cpp in Sources */, 4B8334951F5E25B60097E338 /* C1540.cpp in Sources */, + 4B8DD39726360DDF00B3C866 /* Z80.cpp in Sources */, 4BEDA40C25B2844B000C2DBD /* Decoder.cpp in Sources */, 4B89453C201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4B595FAD2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index aac5bb5fd..b37475e53 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -598,7 +598,27 @@ sna CFBundleTypeName - ZX Spectrum SNA image + ZX Spectrum SNA snapshot + CFBundleTypeOSTypes + + ???? + + CFBundleTypeRole + Viewer + LSHandlerRank + Owner + LSTypeIsPackage + + NSDocumentClass + $(PRODUCT_MODULE_NAME).MachineDocument + + + CFBundleTypeExtensions + + z80 + + CFBundleTypeName + ZX Spectrum Z80 snapshot CFBundleTypeOSTypes ???? diff --git a/Storage/State/Z80.cpp b/Storage/State/Z80.cpp new file mode 100644 index 000000000..16aeaf81a --- /dev/null +++ b/Storage/State/Z80.cpp @@ -0,0 +1,15 @@ +// +// Z80.cpp +// Clock Signal +// +// Created by Thomas Harte on 25/04/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#include "Z80.hpp" + +using namespace Storage::State; + +std::unique_ptr Z80::load(const std::string &file_name) { + return nullptr; +} diff --git a/Storage/State/Z80.hpp b/Storage/State/Z80.hpp new file mode 100644 index 000000000..062089fca --- /dev/null +++ b/Storage/State/Z80.hpp @@ -0,0 +1,24 @@ +// +// Z80.hpp +// Clock Signal +// +// Created by Thomas Harte on 25/04/2021. +// Copyright © 2021 Thomas Harte. All rights reserved. +// + +#ifndef Z80_hpp +#define Z80_hpp + +#include "../../Analyser/Static/StaticAnalyser.hpp" + +namespace Storage { +namespace State { + +struct Z80 { + static std::unique_ptr load(const std::string &file_name); +}; + +} +} + +#endif /* Z80_hpp */ From e6252fe0edaf855e55a401d3c7b50387262b9755 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Apr 2021 17:34:43 -0400 Subject: [PATCH 20/27] Sneaks up towards loading RAM. --- Storage/State/SNA.hpp | 6 +- Storage/State/Z80.cpp | 173 +++++++++++++++++++++++++++++++++++++++++- Storage/State/Z80.hpp | 6 +- 3 files changed, 177 insertions(+), 8 deletions(-) diff --git a/Storage/State/SNA.hpp b/Storage/State/SNA.hpp index 9afa703b0..2f2811d93 100644 --- a/Storage/State/SNA.hpp +++ b/Storage/State/SNA.hpp @@ -6,8 +6,8 @@ // Copyright © 2021 Thomas Harte. All rights reserved. // -#ifndef SNA_hpp -#define SNA_hpp +#ifndef Storage_State_SNA_hpp +#define Storage_State_SNA_hpp #include "../../Analyser/Static/StaticAnalyser.hpp" @@ -21,4 +21,4 @@ struct SNA { } } -#endif /* SNA_hpp */ +#endif /* Storage_State_SNA_hpp */ diff --git a/Storage/State/Z80.cpp b/Storage/State/Z80.cpp index 16aeaf81a..c7323aba1 100644 --- a/Storage/State/Z80.cpp +++ b/Storage/State/Z80.cpp @@ -8,8 +8,177 @@ #include "Z80.hpp" +#include "../FileHolder.hpp" + +#include "../../Analyser/Static/ZXSpectrum/Target.hpp" +#include "../../Machines/Sinclair/ZXSpectrum/State.hpp" + using namespace Storage::State; -std::unique_ptr Z80::load(const std::string &file_name) { - return nullptr; +namespace { + +std::vector read_memory(Storage::FileHolder &file, size_t size, bool is_compressed) { + if(!is_compressed) { + return file.read(size); + } + + std::vector result(size); + size_t cursor = 0; + + uint8_t incoming[2] = { file.get8(), file.get8()}; + while(true) { + if(incoming[0] == 0xed && incoming[1] == 0xed) { + const uint8_t count = file.get8(); + const uint8_t value = file.get8(); + + memset(&result[cursor], value, count); + cursor += count; + if(cursor == size) break; + incoming[0] = file.get8(); + incoming[1] = file.get8(); + } else { + result[cursor] = incoming[0]; + ++cursor; + if(cursor == size) break; + incoming[0] = incoming[1]; + incoming[1] = file.get8(); + } + } + + return result; +} + +} + +std::unique_ptr Z80::load(const std::string &file_name) { + FileHolder file(file_name); + + // Construct a target with a Spectrum state. + using Target = Analyser::Static::ZXSpectrum::Target; + auto result = std::make_unique(); + auto *const state = new Sinclair::ZXSpectrum::State(); + result->state = std::unique_ptr(state); + + // Read version 1 header. + state->z80.registers.a = file.get8(); + state->z80.registers.flags = file.get8(); + state->z80.registers.bc = file.get16le(); + state->z80.registers.hl = file.get16le(); + state->z80.registers.program_counter = file.get16le(); + state->z80.registers.stack_pointer = file.get16le(); + state->z80.registers.ir = file.get16be(); // Stored I then R. + + // Bit 7 of R is stored separately; likely this relates to an + // optimisation in the Z80 emulator that for some reason was + // exported into its file format. + const uint8_t raw_misc = file.get8(); + const uint8_t misc = (raw_misc == 0xff) ? 1 : raw_misc; + state->z80.registers.ir = uint16_t((state->z80.registers.ir & ~0x80) | ((misc&1) << 7)); + + state->z80.registers.de = file.get16le(); + state->z80.registers.bc_dash = file.get16le(); + state->z80.registers.de_dash = file.get16le(); + state->z80.registers.hl_dash = file.get16le(); + state->z80.registers.af_dash = file.get16be(); // Stored A' then F'. + state->z80.registers.iy = file.get16le(); + state->z80.registers.ix = file.get16le(); + state->z80.registers.iff1 = bool(file.get8()); + state->z80.registers.iff2 = bool(file.get8()); + + // Ignored from the next byte: + // + // bit 2 = 1 => issue 2 emulation + // bit 3 = 1 => double interrupt frequency (?) + // bit 4–5 => video synchronisation (to do with emulation hackery?) + // bit 6–7 => joystick type + state->z80.registers.interrupt_mode = file.get8() & 3; + + // If the program counter is non-0 then this is a version 1 snapshot, + // which means it's definitely a 48k image. + if(state->z80.registers.program_counter) { + result->model = Target::Model::FortyEightK; + state->ram = read_memory(file, 48*1024, misc & 0x20); + return result; + } + + // This was a version 1 or 2 snapshot, so keep going... + const uint16_t bonus_header_size = file.get16le(); + if(bonus_header_size != 23 && bonus_header_size != 54 && bonus_header_size != 55) { + return nullptr; + } + + state->z80.registers.program_counter = file.get16le(); + switch(file.get8()) { + default: return nullptr; + case 0: result->model = Target::Model::FortyEightK; break; + case 3: result->model = Target::Model::OneTwoEightK; break; + case 7: + case 8: result->model = Target::Model::Plus3; break; + case 12: result->model = Target::Model::Plus2; break; + case 13: result->model = Target::Model::Plus2a; break; + } + + const uint8_t last7ffd = file.get8(); (void)last7ffd; // TODO + + file.seek(1, SEEK_CUR); + if(file.get8() & 0x80) { + // The 'hardware modify' bit, which inexplicably does this: + switch(result->model) { + default: break; + case Target::Model::FortyEightK: result->model = Target::Model::SixteenK; break; + case Target::Model::OneTwoEightK: result->model = Target::Model::Plus2; break; + case Target::Model::Plus3: result->model = Target::Model::Plus2a; break; + } + } + + const uint8_t lastfffd = file.get8(); (void)lastfffd; // TODO + file.seek(16, SEEK_CUR); // Sound chip registers: TODO. + + if(bonus_header_size != 23) { + // More Z80, the emulator, lack of encapsulation to deal with here. + const uint16_t low_t_state = file.get16le(); + const uint16_t high_t_state = file.get8(); + int time_since_interrupt; + switch(result->model) { + case Target::Model::SixteenK: + case Target::Model::FortyEightK: + time_since_interrupt = (17471 - low_t_state) + (high_t_state * 17472); + break; + + default: + time_since_interrupt = (17726 - low_t_state) + (high_t_state * 17727); + break; + } + // TODO: map time_since_interrupt to time_into_frame, somehow. + + // Skip: Spectator flag, MGT, Multiface and other ROM flags. + file.seek(5, SEEK_CUR); + + // Skip: highly Z80-the-emulator-specific stuff about user-defined joystick. + file.seek(20, SEEK_CUR); + + // Skip: Disciple/Plus D stuff. + file.seek(3, SEEK_CUR); + + if(bonus_header_size == 55) { + const uint8_t last1ffd = file.get8(); (void)last1ffd; // TODO + } + } + + // Grab RAM. + state->ram.resize(128 * 1024); + while(true) { + const uint16_t block_size = file.get16le(); + const uint8_t page = file.get8(); + const auto location = file.tell(); + if(file.eof()) break; + + switch(page) { + default: break; + } + + file.seek(location + block_size, SEEK_SET); + } + + return result; } diff --git a/Storage/State/Z80.hpp b/Storage/State/Z80.hpp index 062089fca..ef5a9af1e 100644 --- a/Storage/State/Z80.hpp +++ b/Storage/State/Z80.hpp @@ -6,8 +6,8 @@ // Copyright © 2021 Thomas Harte. All rights reserved. // -#ifndef Z80_hpp -#define Z80_hpp +#ifndef Storage_State_Z80_hpp +#define Storage_State_Z80_hpp #include "../../Analyser/Static/StaticAnalyser.hpp" @@ -21,4 +21,4 @@ struct Z80 { } } -#endif /* Z80_hpp */ +#endif /* Storage_State_Z80_hpp */ From cc41ccc5f1f50c7908b0f5a6cf18324602f26944 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Apr 2021 17:55:52 -0400 Subject: [PATCH 21/27] Adds RAM deserialisation. --- Storage/State/Z80.cpp | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/Storage/State/Z80.cpp b/Storage/State/Z80.cpp index c7323aba1..7a8ca8a21 100644 --- a/Storage/State/Z80.cpp +++ b/Storage/State/Z80.cpp @@ -25,7 +25,7 @@ std::vector read_memory(Storage::FileHolder &file, size_t size, bool is std::vector result(size); size_t cursor = 0; - uint8_t incoming[2] = { file.get8(), file.get8()}; + uint8_t incoming[2] = { file.get8(), file.get8() }; while(true) { if(incoming[0] == 0xed && incoming[1] == 0xed) { const uint8_t count = file.get8(); @@ -166,15 +166,31 @@ std::unique_ptr Z80::load(const std::string &file_name } // Grab RAM. - state->ram.resize(128 * 1024); + switch(result->model) { + case Target::Model::SixteenK: state->ram.resize(16 * 1024); break; + case Target::Model::FortyEightK: state->ram.resize(48 * 1024); break; + default: state->ram.resize(128 * 1024); break; + } + while(true) { const uint16_t block_size = file.get16le(); const uint8_t page = file.get8(); const auto location = file.tell(); if(file.eof()) break; - switch(page) { - default: break; + const auto data = read_memory(file, 16384, block_size != 0xffff); + + if(result->model == Target::Model::SixteenK || result->model == Target::Model::FortyEightK) { + switch(page) { + default: break; + case 4: memcpy(&state->ram[0x4000], data.data(), 16384); break; + case 5: memcpy(&state->ram[0x8000], data.data(), 16384); break; + case 8: memcpy(&state->ram[0x0000], data.data(), 16384); break; + } + } else { + if(page >= 3 && page <= 10) { + memcpy(&state->ram[(page - 3) * 0x4000], data.data(), 16384); + } } file.seek(location + block_size, SEEK_SET); From 8d86aa69bc9270338b4fa0e3d1e508e3d6df40a9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Apr 2021 18:02:31 -0400 Subject: [PATCH 22/27] Adds an assert to check handling of compressed data. --- Storage/State/Z80.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Storage/State/Z80.cpp b/Storage/State/Z80.cpp index 7a8ca8a21..d59b8a7d4 100644 --- a/Storage/State/Z80.cpp +++ b/Storage/State/Z80.cpp @@ -193,6 +193,7 @@ std::unique_ptr Z80::load(const std::string &file_name } } + assert(location + block_size == file.tell()); file.seek(location + block_size, SEEK_SET); } From c34cb310a806ff52385926dcdeae132e0cbcf61f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Apr 2021 18:07:36 -0400 Subject: [PATCH 23/27] Switches to more straightforward handler for .z80-style compression. --- Storage/State/Z80.cpp | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/Storage/State/Z80.cpp b/Storage/State/Z80.cpp index d59b8a7d4..d676b6248 100644 --- a/Storage/State/Z80.cpp +++ b/Storage/State/Z80.cpp @@ -25,24 +25,28 @@ std::vector read_memory(Storage::FileHolder &file, size_t size, bool is std::vector result(size); size_t cursor = 0; - uint8_t incoming[2] = { file.get8(), file.get8() }; - while(true) { - if(incoming[0] == 0xed && incoming[1] == 0xed) { - const uint8_t count = file.get8(); - const uint8_t value = file.get8(); + while(cursor != size) { + const uint8_t next = file.get8(); - memset(&result[cursor], value, count); - cursor += count; - if(cursor == size) break; - incoming[0] = file.get8(); - incoming[1] = file.get8(); - } else { - result[cursor] = incoming[0]; + if(next != 0xed) { + result[cursor] = next; ++cursor; - if(cursor == size) break; - incoming[0] = incoming[1]; - incoming[1] = file.get8(); + continue; } + + const uint8_t after = file.get8(); + if(after != 0xed) { + result[cursor] = next; + ++cursor; + file.seek(-1, SEEK_CUR); + continue; + } + + const uint8_t count = file.get8(); + const uint8_t value = file.get8(); + + memset(&result[cursor], value, count); + cursor += count; } return result; From 5e08d7db3975d6dca5fadd255dd7dede9d10a09c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Apr 2021 20:46:49 -0400 Subject: [PATCH 24/27] Carries through paging state; avoids file rereads. --- Machines/Sinclair/ZXSpectrum/State.hpp | 14 ++++++++++++++ Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 14 ++++++++++---- Storage/State/Z80.cpp | 20 +++++++++++++------- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/Machines/Sinclair/ZXSpectrum/State.hpp b/Machines/Sinclair/ZXSpectrum/State.hpp index 1333c8d62..ae9632ecd 100644 --- a/Machines/Sinclair/ZXSpectrum/State.hpp +++ b/Machines/Sinclair/ZXSpectrum/State.hpp @@ -20,13 +20,27 @@ namespace ZXSpectrum { struct State: public Reflection::StructImpl { CPU::Z80::State z80; Video::State video; + + // In 16kb or 48kb mode, RAM will be 16kb or 48kb and represent + // memory in standard linear order. In 128kb mode, RAM will be + // 128kb with the first 16kb representing bank 0, the next bank 1, etc. std::vector ram; + // Meaningful for 128kb machines only. + uint8_t last_7ffd = 0; + uint8_t last_fffd = 0; + + // Meaningful for the +2a and +3 only. + uint8_t last_1ffd = 0; + State() { if(needs_declare()) { DeclareField(z80); DeclareField(video); DeclareField(ram); + DeclareField(last_7ffd); + DeclareField(last_fffd); + DeclareField(last_1ffd); } } }; diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index bde983d91..2632f5295 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -138,6 +138,12 @@ template class ConcreteMachine: } } else { memcpy(ram_.data(), state->ram.data(), std::min(ram_.size(), state->ram.size())); + + port1ffd_ = state->last_1ffd; + port7ffd_ = state->last_7ffd; + update_memory_map(); + + GI::AY38910::Utility::select_register(ay_, state->last_fffd); } } } @@ -400,10 +406,6 @@ template class ConcreteMachine: // 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). @@ -729,6 +731,10 @@ template class ConcreteMachine: set_memory(2, 2); set_memory(3, port7ffd_ & 7); } + + // Potentially lock paging, _after_ the current + // port values have taken effect. + disable_paging_ = port7ffd_ & 0x20; } void set_memory(int bank, uint8_t source) { diff --git a/Storage/State/Z80.cpp b/Storage/State/Z80.cpp index d676b6248..9cf63eeee 100644 --- a/Storage/State/Z80.cpp +++ b/Storage/State/Z80.cpp @@ -28,20 +28,25 @@ std::vector read_memory(Storage::FileHolder &file, size_t size, bool is while(cursor != size) { const uint8_t next = file.get8(); - if(next != 0xed) { + // If the next byte definitely doesn't, or can't, + // start an ED ED sequence then just take it. + if(next != 0xed || cursor == size - 1) { result[cursor] = next; ++cursor; continue; } + // Grab the next byte. If it's not ED then write + // both and continue. const uint8_t after = file.get8(); if(after != 0xed) { result[cursor] = next; - ++cursor; - file.seek(-1, SEEK_CUR); + result[cursor+1] = after; + cursor += 2; continue; } + // An ED ED has begun, so grab the RLE sequence. const uint8_t count = file.get8(); const uint8_t value = file.get8(); @@ -112,7 +117,8 @@ std::unique_ptr Z80::load(const std::string &file_name } state->z80.registers.program_counter = file.get16le(); - switch(file.get8()) { + const uint8_t model = file.get8(); + switch(model) { default: return nullptr; case 0: result->model = Target::Model::FortyEightK; break; case 3: result->model = Target::Model::OneTwoEightK; break; @@ -122,7 +128,7 @@ std::unique_ptr Z80::load(const std::string &file_name case 13: result->model = Target::Model::Plus2a; break; } - const uint8_t last7ffd = file.get8(); (void)last7ffd; // TODO + state->last_7ffd = file.get8(); file.seek(1, SEEK_CUR); if(file.get8() & 0x80) { @@ -135,7 +141,7 @@ std::unique_ptr Z80::load(const std::string &file_name } } - const uint8_t lastfffd = file.get8(); (void)lastfffd; // TODO + state->last_fffd = file.get8(); file.seek(16, SEEK_CUR); // Sound chip registers: TODO. if(bonus_header_size != 23) { @@ -165,7 +171,7 @@ std::unique_ptr Z80::load(const std::string &file_name file.seek(3, SEEK_CUR); if(bonus_header_size == 55) { - const uint8_t last1ffd = file.get8(); (void)last1ffd; // TODO + state->last_1ffd = file.get8(); } } From d403036d865cc8db55e6f278e503a7d91a5f8b27 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Apr 2021 20:56:57 -0400 Subject: [PATCH 25/27] Reduce bounce at Spectrum startup. --- Machines/Sinclair/ZXSpectrum/Video.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp index 4304c5542..a82ea61dd 100644 --- a/Machines/Sinclair/ZXSpectrum/Video.hpp +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -270,6 +270,11 @@ template class Video { crt_.set_display_type(Outputs::Display::DisplayType::RGB); crt_.set_visible_area(Outputs::Display::Rect(0.1f, 0.1f, 0.8f, 0.8f)); + // Get the CRT roughly into phase. + // + // TODO: this is coupled to an assumption about the initial CRT. Fix. + const auto timings = get_timings(); + crt_.output_blank(timings.lines_per_frame*timings.cycles_per_line - timings.interrupt_time); } void set_video_source(const uint8_t *source) { From 700c5059744e78bbb0ce2f1b3a48502884f393e5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Apr 2021 21:16:22 -0400 Subject: [PATCH 26/27] Ensures the ZX Spectrum properly reports its display type. --- Machines/Sinclair/ZXSpectrum/Video.hpp | 5 +++++ Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp index a82ea61dd..4bb96d883 100644 --- a/Machines/Sinclair/ZXSpectrum/Video.hpp +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -390,6 +390,11 @@ template class Video { crt_.set_display_type(type); } + /*! Gets the display type. */ + Outputs::Display::DisplayType get_display_type() const { + return crt_.get_display_type(); + } + private: int time_into_frame_ = 0; Outputs::CRT::CRT crt_; diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 2632f5295..1a2f4ed0f 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -224,6 +224,10 @@ template class ConcreteMachine: video_->set_display_type(display_type); } + Outputs::Display::DisplayType get_display_type() const override { + return video_->get_display_type(); + } + // MARK: - BusHandler. forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { @@ -643,6 +647,7 @@ template class ConcreteMachine: auto options = std::make_unique(Configurable::OptionsType::UserFriendly); // OptionsType is arbitrary, but not optional. options->automatic_tape_motor_control = use_automatic_tape_motor_control_; options->quickload = allow_fast_tape_hack_; + options->output = get_video_signal_configurable(); return options; } From 3348167c4640563951d3bde139d212cf63f80e6a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 26 Apr 2021 17:39:11 -0400 Subject: [PATCH 27/27] Ensures AY registers are conveyed. --- Components/AY38910/AY38910.hpp | 24 +++++++++++++++++++++ Machines/Sinclair/ZXSpectrum/State.hpp | 4 ++++ Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp | 1 + Storage/State/Z80.cpp | 2 +- 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index f13a00810..cbd7e19b7 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -12,6 +12,8 @@ #include "../../Outputs/Speaker/Implementation/SampleSource.hpp" #include "../../Concurrency/AsyncTaskQueue.hpp" +#include "../../Reflection/Struct.hpp" + namespace GI { namespace AY38910 { @@ -162,6 +164,8 @@ template class AY38910: public ::Outputs::Speaker::SampleSource uint8_t a_left_ = 255, a_right_ = 255; uint8_t b_left_ = 255, b_right_ = 255; uint8_t c_left_ = 255, c_right_ = 255; + + friend struct State; }; /*! @@ -192,6 +196,26 @@ struct Utility { }; +struct State: public Reflection::StructImpl { + uint8_t registers[16]{}; + + // TODO: all audio-production thread state. + + State() { + if(needs_declare()) { + DeclareField(registers); + } + } + + template void apply(AY &target) { + // Establish emulator-thread state + for(uint8_t c = 0; c < 16; c++) { + target.select_register(c); + target.set_register_value(registers[c]); + } + } +}; + } } diff --git a/Machines/Sinclair/ZXSpectrum/State.hpp b/Machines/Sinclair/ZXSpectrum/State.hpp index ae9632ecd..69d23c5f5 100644 --- a/Machines/Sinclair/ZXSpectrum/State.hpp +++ b/Machines/Sinclair/ZXSpectrum/State.hpp @@ -11,7 +11,9 @@ #include "../../../Reflection/Struct.hpp" #include "../../../Processors/Z80/State/State.hpp" + #include "Video.hpp" +#include "../../../Components/AY38910/AY38910.hpp" namespace Sinclair { namespace ZXSpectrum { @@ -29,6 +31,7 @@ struct State: public Reflection::StructImpl { // Meaningful for 128kb machines only. uint8_t last_7ffd = 0; uint8_t last_fffd = 0; + GI::AY38910::State ay; // Meaningful for the +2a and +3 only. uint8_t last_1ffd = 0; @@ -41,6 +44,7 @@ struct State: public Reflection::StructImpl { DeclareField(last_7ffd); DeclareField(last_fffd); DeclareField(last_1ffd); + DeclareField(ay); } } }; diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 1a2f4ed0f..e3c242d81 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -128,6 +128,7 @@ template class ConcreteMachine: const auto state = static_cast(target.state.get()); state->z80.apply(z80_); state->video.apply(*video_.last_valid()); + state->ay.apply(ay_); // If this is a 48k or 16k machine, remap source data from its original // linear form to whatever the banks end up being; otherwise copy as is. diff --git a/Storage/State/Z80.cpp b/Storage/State/Z80.cpp index 9cf63eeee..4f5c9d390 100644 --- a/Storage/State/Z80.cpp +++ b/Storage/State/Z80.cpp @@ -142,7 +142,7 @@ std::unique_ptr Z80::load(const std::string &file_name } state->last_fffd = file.get8(); - file.seek(16, SEEK_CUR); // Sound chip registers: TODO. + file.read(state->ay.registers, 16); if(bonus_header_size != 23) { // More Z80, the emulator, lack of encapsulation to deal with here.