From 814bb4ec63d5cd2404d44e5fd15f7c864a11b7f1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 3 Oct 2019 20:17:26 -0400 Subject: [PATCH 01/39] Adds an Atari ST enumeration and factory method. --- Analyser/Machines.hpp | 1 + Machines/AtariST/AtariST.cpp | 17 +++++++++++ Machines/AtariST/AtariST.hpp | 28 +++++++++++++++++++ Machines/Utility/MachineForTarget.cpp | 4 +++ .../Clock Signal.xcodeproj/project.pbxproj | 16 +++++++++++ 5 files changed, 66 insertions(+) create mode 100644 Machines/AtariST/AtariST.cpp create mode 100644 Machines/AtariST/AtariST.hpp diff --git a/Analyser/Machines.hpp b/Analyser/Machines.hpp index 796c376de..cd64edd33 100644 --- a/Analyser/Machines.hpp +++ b/Analyser/Machines.hpp @@ -15,6 +15,7 @@ enum class Machine { AmstradCPC, AppleII, Atari2600, + AtariST, ColecoVision, Electron, Macintosh, diff --git a/Machines/AtariST/AtariST.cpp b/Machines/AtariST/AtariST.cpp new file mode 100644 index 000000000..4e4001993 --- /dev/null +++ b/Machines/AtariST/AtariST.cpp @@ -0,0 +1,17 @@ +// +// AtariST.cpp +// Clock Signal +// +// Created by Thomas Harte on 03/10/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#include "AtariST.hpp" + +using namespace Atari::ST; + +Machine *Machine::AtariST(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { + return nullptr; +} + +Machine::~Machine() {} diff --git a/Machines/AtariST/AtariST.hpp b/Machines/AtariST/AtariST.hpp new file mode 100644 index 000000000..959ad313a --- /dev/null +++ b/Machines/AtariST/AtariST.hpp @@ -0,0 +1,28 @@ +// +// AtariST.hpp +// Clock Signal +// +// Created by Thomas Harte on 03/10/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef AtariST_hpp +#define AtariST_hpp + +#include "../../Configurable/Configurable.hpp" +#include "../../Analyser/Static/StaticAnalyser.hpp" +#include "../ROMMachine.hpp" + +namespace Atari { +namespace ST { + +class Machine { + public: + virtual ~Machine(); + + static Machine *AtariST(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); +}; + +} +} +#endif /* AtariST_hpp */ diff --git a/Machines/Utility/MachineForTarget.cpp b/Machines/Utility/MachineForTarget.cpp index b62e05990..7636021c5 100644 --- a/Machines/Utility/MachineForTarget.cpp +++ b/Machines/Utility/MachineForTarget.cpp @@ -12,6 +12,7 @@ #include "../Apple/AppleII/AppleII.hpp" #include "../Apple/Macintosh/Macintosh.hpp" #include "../Atari2600/Atari2600.hpp" +#include "../AtariST/AtariST.hpp" #include "../ColecoVision/ColecoVision.hpp" #include "../Commodore/Vic-20/Vic20.hpp" #include "../Electron/Electron.hpp" @@ -37,6 +38,7 @@ namespace { BindD(Apple::II, AppleII) BindD(Apple::Macintosh, Macintosh) Bind(Atari2600) + BindD(Atari::ST, AtariST) BindD(Coleco::Vision, ColecoVision) BindD(Commodore::Vic20, Vic20) Bind(Electron) @@ -103,6 +105,7 @@ std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine) case Analyser::Machine::AmstradCPC: return "AmstradCPC"; case Analyser::Machine::AppleII: return "AppleII"; case Analyser::Machine::Atari2600: return "Atari2600"; + case Analyser::Machine::AtariST: return "AtariST"; case Analyser::Machine::ColecoVision: return "ColecoVision"; case Analyser::Machine::Electron: return "Electron"; case Analyser::Machine::Macintosh: return "Macintosh"; @@ -121,6 +124,7 @@ std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) { case Analyser::Machine::AmstradCPC: return "Amstrad CPC"; case Analyser::Machine::AppleII: return "Apple II"; case Analyser::Machine::Atari2600: return "Atari 2600"; + case Analyser::Machine::AtariST: return "Atari ST"; case Analyser::Machine::ColecoVision: return "ColecoVision"; case Analyser::Machine::Electron: return "Acorn Electron"; case Analyser::Machine::Macintosh: return "Apple Macintosh"; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 3e65642b2..05e9a8eff 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -636,6 +636,8 @@ 4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */; }; 4BBFE83D21015D9C00BF1C40 /* CSJoystickManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */; }; 4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */; }; + 4BC1316A2346C61100E4FF3D /* AtariST.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC131682346C61100E4FF3D /* AtariST.cpp */; }; + 4BC1316B2346C61100E4FF3D /* AtariST.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC131682346C61100E4FF3D /* AtariST.cpp */; }; 4BC5C3E022C994CD00795658 /* 68000MoveTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BC5C3DF22C994CC00795658 /* 68000MoveTests.mm */; }; 4BC5FC3020CDDDEF00410AA0 /* AppleIIOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BC5FC2E20CDDDEE00410AA0 /* AppleIIOptions.xib */; }; 4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; }; @@ -1443,6 +1445,8 @@ 4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSJoystickManager.m; sourceTree = ""; }; 4BBFE83E21015DAE00BF1C40 /* CSJoystickManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSJoystickManager.h; sourceTree = ""; }; 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TrackSerialiser.cpp; sourceTree = ""; }; + 4BC131682346C61100E4FF3D /* AtariST.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AtariST.cpp; sourceTree = ""; }; + 4BC131692346C61100E4FF3D /* AtariST.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AtariST.hpp; sourceTree = ""; }; 4BC5C3DF22C994CC00795658 /* 68000MoveTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000MoveTests.mm; sourceTree = ""; }; 4BC5FC2F20CDDDEE00410AA0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/AppleIIOptions.xib"; sourceTree = SOURCE_ROOT; }; 4BC751B11D157E61006C31D9 /* 6522Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6522Tests.swift; sourceTree = ""; }; @@ -3098,6 +3102,7 @@ 4B38F3491F2EC12000D9235D /* AmstradCPC */, 4BCE0048227CE8CA000CA200 /* Apple */, 4B2E2D961C3A06EC00138695 /* Atari2600 */, + 4BC131672346C61100E4FF3D /* AtariST */, 4B7A90E22041097C008514A2 /* ColecoVision */, 4B4DC81D1D2C2425003C5BF8 /* Commodore */, 4B2E2D9E1C3A070900138695 /* Electron */, @@ -3179,6 +3184,15 @@ path = "Joystick Manager"; sourceTree = ""; }; + 4BC131672346C61100E4FF3D /* AtariST */ = { + isa = PBXGroup; + children = ( + 4BC131682346C61100E4FF3D /* AtariST.cpp */, + 4BC131692346C61100E4FF3D /* AtariST.hpp */, + ); + path = AtariST; + sourceTree = ""; + }; 4BC9DF4A1D04691600F44158 /* Components */ = { isa = PBXGroup; children = ( @@ -4033,6 +4047,7 @@ 4B055AB31FAE860F0060FFFF /* CSW.cpp in Sources */, 4B89451D201967B4007DE474 /* Disk.cpp in Sources */, 4BDACBED22FFA5D20045EF7E /* ncr5380.cpp in Sources */, + 4BC1316B2346C61100E4FF3D /* AtariST.cpp in Sources */, 4B055ACF1FAE9B030060FFFF /* SoundGenerator.cpp in Sources */, 4B894519201967B4007DE474 /* ConfidenceCounter.cpp in Sources */, 4B055AEE1FAE9BBF0060FFFF /* Keyboard.cpp in Sources */, @@ -4167,6 +4182,7 @@ 4B4518A11F75FD1C00926311 /* D64.cpp in Sources */, 4B1558C01F844ECD006E9A97 /* BitReverse.cpp in Sources */, 4BCE0052227CE8CA000CA200 /* DiskIICard.cpp in Sources */, + 4BC1316A2346C61100E4FF3D /* AtariST.cpp in Sources */, 4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */, 4BD67DCB209BE4D700AB2146 /* StaticAnalyser.cpp in Sources */, 4BB4BFB922A4372F0069048D /* StaticAnalyser.cpp in Sources */, From fe621d7e524fc9c2f1efb861c6a368d9c2bf89d4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 3 Oct 2019 22:10:10 -0400 Subject: [PATCH 02/39] Adds a route through the static analyser to the Atari ST. --- .../{Atari => Atari2600}/StaticAnalyser.cpp | 49 +++++++------- .../{Atari => Atari2600}/StaticAnalyser.hpp | 2 +- .../Static/{Atari => Atari2600}/Target.hpp | 6 +- Analyser/Static/AtariST/StaticAnalyser.cpp | 26 ++++++++ Analyser/Static/AtariST/StaticAnalyser.hpp | 27 ++++++++ Analyser/Static/AtariST/Target.hpp | 23 +++++++ Analyser/Static/StaticAnalyser.cpp | 8 ++- Machines/Atari2600/Atari2600.cpp | 9 +-- .../Clock Signal.xcodeproj/project.pbxproj | 66 +++++++++++++------ OSBindings/Mac/Clock Signal/Info.plist | 20 ++++++ Storage/Disk/DiskImage/Formats/MSA.cpp | 23 +++++++ Storage/Disk/DiskImage/Formats/MSA.hpp | 38 +++++++++++ Storage/TargetPlatforms.hpp | 31 ++++----- 13 files changed, 259 insertions(+), 69 deletions(-) rename Analyser/Static/{Atari => Atari2600}/StaticAnalyser.cpp (73%) rename Analyser/Static/{Atari => Atari2600}/StaticAnalyser.hpp (96%) rename Analyser/Static/{Atari => Atari2600}/Target.hpp (85%) create mode 100644 Analyser/Static/AtariST/StaticAnalyser.cpp create mode 100644 Analyser/Static/AtariST/StaticAnalyser.hpp create mode 100644 Analyser/Static/AtariST/Target.hpp create mode 100644 Storage/Disk/DiskImage/Formats/MSA.cpp create mode 100644 Storage/Disk/DiskImage/Formats/MSA.hpp diff --git a/Analyser/Static/Atari/StaticAnalyser.cpp b/Analyser/Static/Atari2600/StaticAnalyser.cpp similarity index 73% rename from Analyser/Static/Atari/StaticAnalyser.cpp rename to Analyser/Static/Atari2600/StaticAnalyser.cpp index 84773bb9b..74d048cd5 100644 --- a/Analyser/Static/Atari/StaticAnalyser.cpp +++ b/Analyser/Static/Atari2600/StaticAnalyser.cpp @@ -12,9 +12,10 @@ #include "../Disassembler/6502.hpp" -using namespace Analyser::Static::Atari; +using namespace Analyser::Static::Atari2600; +using Target = Analyser::Static::Atari2600::Target; -static void DeterminePagingFor2kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { +static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { // if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid uint16_t entry_address, break_address; @@ -48,10 +49,10 @@ static void DeterminePagingFor2kCartridge(Analyser::Static::Atari::Target &targe // caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses // itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it // attempts to modify itself but it probably doesn't - if(has_wide_area_store) target.paging_model = Analyser::Static::Atari::Target::PagingModel::CommaVid; + if(has_wide_area_store) target.paging_model = Target::PagingModel::CommaVid; } -static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { +static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { // Activision stack titles have their vectors at the top of the low 4k, not the top, and // always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all // issue an SEI as their first instruction (maybe some sort of relic of the development environment?) @@ -60,12 +61,12 @@ static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::Target &targe (segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) && segment.data[0] == 0x78 ) { - target.paging_model = Analyser::Static::Atari::Target::PagingModel::ActivisionStack; + target.paging_model = Target::PagingModel::ActivisionStack; return; } // make an assumption that this is the Atari paging model - target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari8k; + target.paging_model = Target::PagingModel::Atari8k; std::set internal_accesses; internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end()); @@ -85,13 +86,13 @@ static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::Target &targe tigervision_access_count += masked_address == 0x3f; } - if(parker_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::ParkerBros; - else if(tigervision_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision; + if(parker_access_count > atari_access_count) target.paging_model = Target::PagingModel::ParkerBros; + else if(tigervision_access_count > atari_access_count) target.paging_model = Target::PagingModel::Tigervision; } -static void DeterminePagingFor16kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { +static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { // make an assumption that this is the Atari paging model - target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari16k; + target.paging_model = Target::PagingModel::Atari16k; std::set internal_accesses; internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end()); @@ -106,17 +107,17 @@ static void DeterminePagingFor16kCartridge(Analyser::Static::Atari::Target &targ mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb; } - if(mnetwork_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::MNetwork; + if(mnetwork_access_count > atari_access_count) target.paging_model = Target::PagingModel::MNetwork; } -static void DeterminePagingFor64kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { +static void DeterminePagingFor64kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) { // make an assumption that this is a Tigervision if there is a write to 3F target.paging_model = (disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ? - Analyser::Static::Atari::Target::PagingModel::Tigervision : Analyser::Static::Atari::Target::PagingModel::MegaBoy; + Target::PagingModel::Tigervision : Target::PagingModel::MegaBoy; } -static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { +static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment) { if(segment.data.size() == 2048) { DeterminePagingFor2kCartridge(target, segment); return; @@ -140,16 +141,16 @@ static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target, DeterminePagingFor8kCartridge(target, segment, disassembly); break; case 10495: - target.paging_model = Analyser::Static::Atari::Target::PagingModel::Pitfall2; + target.paging_model = Target::PagingModel::Pitfall2; break; case 12288: - target.paging_model = Analyser::Static::Atari::Target::PagingModel::CBSRamPlus; + target.paging_model = Target::PagingModel::CBSRamPlus; break; case 16384: DeterminePagingFor16kCartridge(target, segment, disassembly); break; case 32768: - target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari32k; + target.paging_model = Target::PagingModel::Atari32k; break; case 65536: DeterminePagingFor64kCartridge(target, segment, disassembly); @@ -161,8 +162,8 @@ static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target, // check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM // regions; when they don't they at least seem to have the first 128 bytes be the same as the // next 128 bytes. So check for that. - if( target.paging_model != Analyser::Static::Atari::Target::PagingModel::CBSRamPlus && - target.paging_model != Analyser::Static::Atari::Target::PagingModel::MNetwork) { + if( target.paging_model != Target::PagingModel::CBSRamPlus && + target.paging_model != Target::PagingModel::MNetwork) { bool has_superchip = true; for(std::size_t address = 0; address < 128; address++) { if(segment.data[address] != segment.data[address+128]) { @@ -174,19 +175,19 @@ static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target, } // check for a Tigervision or Tigervision-esque scheme - if(target.paging_model == Analyser::Static::Atari::Target::PagingModel::None && segment.data.size() > 4096) { + if(target.paging_model == Target::PagingModel::None && segment.data.size() > 4096) { bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end(); - if(looks_like_tigervision) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision; + if(looks_like_tigervision) target.paging_model = Target::PagingModel::Tigervision; } } -Analyser::Static::TargetList Analyser::Static::Atari::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { +Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { // TODO: sanity checking; is this image really for an Atari 2600? - std::unique_ptr target(new Analyser::Static::Atari::Target); + std::unique_ptr target(new Target); target->machine = Machine::Atari2600; target->confidence = 0.5; target->media.cartridges = media.cartridges; - target->paging_model = Analyser::Static::Atari::Target::PagingModel::None; + target->paging_model = Target::PagingModel::None; target->uses_superchip = false; // try to figure out the paging scheme diff --git a/Analyser/Static/Atari/StaticAnalyser.hpp b/Analyser/Static/Atari2600/StaticAnalyser.hpp similarity index 96% rename from Analyser/Static/Atari/StaticAnalyser.hpp rename to Analyser/Static/Atari2600/StaticAnalyser.hpp index a311b2834..0cf6e6fd3 100644 --- a/Analyser/Static/Atari/StaticAnalyser.hpp +++ b/Analyser/Static/Atari2600/StaticAnalyser.hpp @@ -15,7 +15,7 @@ namespace Analyser { namespace Static { -namespace Atari { +namespace Atari2600 { TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms); diff --git a/Analyser/Static/Atari/Target.hpp b/Analyser/Static/Atari2600/Target.hpp similarity index 85% rename from Analyser/Static/Atari/Target.hpp rename to Analyser/Static/Atari2600/Target.hpp index 0e2ab69c2..4bd4c5384 100644 --- a/Analyser/Static/Atari/Target.hpp +++ b/Analyser/Static/Atari2600/Target.hpp @@ -6,14 +6,14 @@ // Copyright 2018 Thomas Harte. All rights reserved. // -#ifndef Analyser_Static_Atari_Target_h -#define Analyser_Static_Atari_Target_h +#ifndef Analyser_Static_Atari2600_Target_h +#define Analyser_Static_Atari2600_Target_h #include "../StaticAnalyser.hpp" namespace Analyser { namespace Static { -namespace Atari { +namespace Atari2600 { struct Target: public ::Analyser::Static::Target { enum class PagingModel { diff --git a/Analyser/Static/AtariST/StaticAnalyser.cpp b/Analyser/Static/AtariST/StaticAnalyser.cpp new file mode 100644 index 000000000..b46d0e17d --- /dev/null +++ b/Analyser/Static/AtariST/StaticAnalyser.cpp @@ -0,0 +1,26 @@ +// +// StaticAnalyser.cpp +// Clock Signal +// +// Created by Thomas Harte on 03/10/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#include "StaticAnalyser.hpp" +#include "Target.hpp" + +Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { + // This analyser can comprehend disks and mass-storage devices only. + if(media.disks.empty()) return {}; + + // As there is at least one usable media image, wave it through. + Analyser::Static::TargetList targets; + + using Target = Analyser::Static::Target; + auto *target = new Target; + target->machine = Analyser::Machine::AtariST; + target->media = media; + targets.push_back(std::unique_ptr(target)); + + return targets; +} diff --git a/Analyser/Static/AtariST/StaticAnalyser.hpp b/Analyser/Static/AtariST/StaticAnalyser.hpp new file mode 100644 index 000000000..54c041eed --- /dev/null +++ b/Analyser/Static/AtariST/StaticAnalyser.hpp @@ -0,0 +1,27 @@ +// +// StaticAnalyser.hpp +// Clock Signal +// +// Created by Thomas Harte on 03/10/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef Analyser_Static_AtariST_StaticAnalyser_hpp +#define Analyser_Static_AtariST_StaticAnalyser_hpp + +#include "../StaticAnalyser.hpp" +#include "../../../Storage/TargetPlatforms.hpp" +#include + +namespace Analyser { +namespace Static { +namespace AtariST { + +TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms); + +} +} +} + + +#endif /* Analyser_Static_AtariST_StaticAnalyser_hpp */ diff --git a/Analyser/Static/AtariST/Target.hpp b/Analyser/Static/AtariST/Target.hpp new file mode 100644 index 000000000..f9c6f2828 --- /dev/null +++ b/Analyser/Static/AtariST/Target.hpp @@ -0,0 +1,23 @@ +// +// Target.hpp +// Clock Signal +// +// Created by Thomas Harte on 03/06/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef Analyser_Static_AtariST_Target_h +#define Analyser_Static_AtariST_Target_h + +namespace Analyser { +namespace Static { +namespace AtariST { + +struct Target: public ::Analyser::Static::Target { +}; + +} +} +} + +#endif /* Analyser_Static_AtariST_Target_h */ diff --git a/Analyser/Static/StaticAnalyser.cpp b/Analyser/Static/StaticAnalyser.cpp index a9df90cf0..bbd49c8d2 100644 --- a/Analyser/Static/StaticAnalyser.cpp +++ b/Analyser/Static/StaticAnalyser.cpp @@ -17,7 +17,8 @@ #include "Acorn/StaticAnalyser.hpp" #include "AmstradCPC/StaticAnalyser.hpp" #include "AppleII/StaticAnalyser.hpp" -#include "Atari/StaticAnalyser.hpp" +#include "Atari2600/StaticAnalyser.hpp" +#include "AtariST/StaticAnalyser.hpp" #include "Coleco/StaticAnalyser.hpp" #include "Commodore/StaticAnalyser.hpp" #include "DiskII/StaticAnalyser.hpp" @@ -40,6 +41,7 @@ #include "../../Storage/Disk/DiskImage/Formats/G64.hpp" #include "../../Storage/Disk/DiskImage/Formats/DMK.hpp" #include "../../Storage/Disk/DiskImage/Formats/HFE.hpp" +#include "../../Storage/Disk/DiskImage/Formats/MSA.hpp" #include "../../Storage/Disk/DiskImage/Formats/MSXDSK.hpp" #include "../../Storage/Disk/DiskImage/Formats/NIB.hpp" #include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp" @@ -117,6 +119,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: // HFE (TODO: switch to AllDisk once the MSX stops being so greedy) Format("img", result.disks, Disk::DiskImageHolder, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2) Format("image", result.disks, Disk::DiskImageHolder, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2) + Format("msa", result.disks, Disk::DiskImageHolder, TargetPlatform::AtariST) // MSA Format("nib", result.disks, Disk::DiskImageHolder, TargetPlatform::DiskII) // NIB Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P @@ -178,7 +181,8 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) { if(potential_platforms & TargetPlatform::Acorn) Append(Acorn); if(potential_platforms & TargetPlatform::AmstradCPC) Append(AmstradCPC); if(potential_platforms & TargetPlatform::AppleII) Append(AppleII); - if(potential_platforms & TargetPlatform::Atari2600) Append(Atari); + if(potential_platforms & TargetPlatform::Atari2600) Append(Atari2600); + if(potential_platforms & TargetPlatform::AtariST) Append(AtariST); if(potential_platforms & TargetPlatform::ColecoVision) Append(Coleco); if(potential_platforms & TargetPlatform::Commodore) Append(Commodore); if(potential_platforms & TargetPlatform::DiskII) Append(DiskII); diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index a9ac5d483..0324f900c 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -14,7 +14,7 @@ #include "../CRTMachine.hpp" #include "../JoystickMachine.hpp" -#include "../../Analyser/Static/Atari/Target.hpp" +#include "../../Analyser/Static/Atari2600/Target.hpp" #include "Cartridges/Atari8k.hpp" #include "Cartridges/Atari16k.hpp" @@ -72,18 +72,20 @@ class Joystick: public Inputs::ConcreteJoystick { std::size_t shift_, fire_tia_input_; }; +using Target = Analyser::Static::Atari2600::Target; + class ConcreteMachine: public Machine, public CRTMachine::Machine, public JoystickMachine::Machine, public Outputs::CRT::Delegate { public: - ConcreteMachine(const Analyser::Static::Atari::Target &target) { + ConcreteMachine(const Target &target) { set_clock_rate(NTSC_clock_rate); const std::vector &rom = target.media.cartridges.front()->get_segments().front().data; - using PagingModel = Analyser::Static::Atari::Target::PagingModel; + using PagingModel = Target::PagingModel; switch(target.paging_model) { case PagingModel::ActivisionStack: bus_.reset(new Cartridge::Cartridge(rom)); break; case PagingModel::CBSRamPlus: bus_.reset(new Cartridge::Cartridge(rom)); break; @@ -232,7 +234,6 @@ class ConcreteMachine: using namespace Atari2600; Machine *Machine::Atari2600(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { - using Target = Analyser::Static::Atari::Target; const Target *const atari_target = dynamic_cast(target); return new Atari2600::ConcreteMachine(*atari_target); } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 05e9a8eff..b083d6697 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -280,8 +280,6 @@ 4B89451F201967B4007DE474 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8944F0201967B4007DE474 /* Tape.cpp */; }; 4B894520201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8944F2201967B4007DE474 /* StaticAnalyser.cpp */; }; 4B894521201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8944F2201967B4007DE474 /* StaticAnalyser.cpp */; }; - 4B894522201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8944F5201967B4007DE474 /* StaticAnalyser.cpp */; }; - 4B894523201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8944F5201967B4007DE474 /* StaticAnalyser.cpp */; }; 4B894524201967B4007DE474 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8944F9201967B4007DE474 /* Tape.cpp */; }; 4B894525201967B4007DE474 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8944F9201967B4007DE474 /* Tape.cpp */; }; 4B894526201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8944FA201967B4007DE474 /* StaticAnalyser.cpp */; }; @@ -638,6 +636,12 @@ 4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */; }; 4BC1316A2346C61100E4FF3D /* AtariST.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC131682346C61100E4FF3D /* AtariST.cpp */; }; 4BC1316B2346C61100E4FF3D /* AtariST.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC131682346C61100E4FF3D /* AtariST.cpp */; }; + 4BC131702346DE5000E4FF3D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC1316F2346DE5000E4FF3D /* StaticAnalyser.cpp */; }; + 4BC131712346DE5000E4FF3D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC1316F2346DE5000E4FF3D /* StaticAnalyser.cpp */; }; + 4BC131762346DE9100E4FF3D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC131752346DE9100E4FF3D /* StaticAnalyser.cpp */; }; + 4BC131772346DE9100E4FF3D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC131752346DE9100E4FF3D /* StaticAnalyser.cpp */; }; + 4BC1317A2346DF2B00E4FF3D /* MSA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC131782346DF2B00E4FF3D /* MSA.cpp */; }; + 4BC1317B2346DF2B00E4FF3D /* MSA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC131782346DF2B00E4FF3D /* MSA.cpp */; }; 4BC5C3E022C994CD00795658 /* 68000MoveTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BC5C3DF22C994CC00795658 /* 68000MoveTests.mm */; }; 4BC5FC3020CDDDEF00410AA0 /* AppleIIOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BC5FC2E20CDDDEE00410AA0 /* AppleIIOptions.xib */; }; 4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; }; @@ -1053,8 +1057,6 @@ 4B8944F0201967B4007DE474 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Tape.cpp; sourceTree = ""; }; 4B8944F1201967B4007DE474 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Disk.hpp; sourceTree = ""; }; 4B8944F2201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = ""; }; - 4B8944F4201967B4007DE474 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = ""; }; - 4B8944F5201967B4007DE474 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = ""; }; 4B8944F7201967B4007DE474 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = ""; }; 4B8944F8201967B4007DE474 /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Tape.hpp; sourceTree = ""; }; 4B8944F9201967B4007DE474 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Tape.cpp; sourceTree = ""; }; @@ -1447,6 +1449,14 @@ 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TrackSerialiser.cpp; sourceTree = ""; }; 4BC131682346C61100E4FF3D /* AtariST.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AtariST.cpp; sourceTree = ""; }; 4BC131692346C61100E4FF3D /* AtariST.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AtariST.hpp; sourceTree = ""; }; + 4BC1316D2346DE5000E4FF3D /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = ""; }; + 4BC1316E2346DE5000E4FF3D /* Target.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; + 4BC1316F2346DE5000E4FF3D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = ""; }; + 4BC131732346DE9100E4FF3D /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = ""; }; + 4BC131742346DE9100E4FF3D /* Target.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; + 4BC131752346DE9100E4FF3D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = ""; }; + 4BC131782346DF2B00E4FF3D /* MSA.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MSA.cpp; sourceTree = ""; }; + 4BC131792346DF2B00E4FF3D /* MSA.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MSA.hpp; sourceTree = ""; }; 4BC5C3DF22C994CC00795658 /* 68000MoveTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000MoveTests.mm; sourceTree = ""; }; 4BC5FC2F20CDDDEE00410AA0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/AppleIIOptions.xib"; sourceTree = SOURCE_ROOT; }; 4BC751B11D157E61006C31D9 /* 6522Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6522Tests.swift; sourceTree = ""; }; @@ -1525,7 +1535,6 @@ 4BE32314205328FF006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; 4BE3231520532AA7006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; 4BE3231620532BED006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; - 4BE3231720532CC0006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; 4BE76CF822641ED300ACD6FA /* QLTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = QLTests.mm; sourceTree = ""; }; 4BE7C9161E3D397100A5496D /* TIA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIA.cpp; sourceTree = ""; }; 4BE7C9171E3D397100A5496D /* TIA.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIA.hpp; sourceTree = ""; }; @@ -2041,11 +2050,12 @@ 4B0333AD2094081A0050B93D /* AppleDSK.cpp */, 4B45188F1F75FD1B00926311 /* CPCDSK.cpp */, 4B4518911F75FD1B00926311 /* D64.cpp */, - 4BB4BFAE22A42F290069048D /* MacintoshIMG.cpp */, 4BAF2B4C2004580C00480230 /* DMK.cpp */, 4B4518931F75FD1B00926311 /* G64.cpp */, 4B4518951F75FD1B00926311 /* HFE.cpp */, + 4BB4BFAE22A42F290069048D /* MacintoshIMG.cpp */, 4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */, + 4BC131782346DF2B00E4FF3D /* MSA.cpp */, 4BEBFB4B2002C4BF000708CC /* MSXDSK.cpp */, 4B0F94FC208C1A1600FE41D9 /* NIB.cpp */, 4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */, @@ -2055,11 +2065,12 @@ 4B0333AE2094081A0050B93D /* AppleDSK.hpp */, 4B4518901F75FD1B00926311 /* CPCDSK.hpp */, 4B4518921F75FD1B00926311 /* D64.hpp */, - 4BB4BFAF22A42F290069048D /* MacintoshIMG.hpp */, 4BAF2B4D2004580C00480230 /* DMK.hpp */, 4B4518941F75FD1B00926311 /* G64.hpp */, 4B4518961F75FD1B00926311 /* HFE.hpp */, + 4BB4BFAF22A42F290069048D /* MacintoshIMG.hpp */, 4B58601D1F806AB200AEE2E3 /* MFMSectorDump.hpp */, + 4BC131792346DF2B00E4FF3D /* MSA.hpp */, 4BEBFB4C2002C4BF000708CC /* MSXDSK.hpp */, 4B0F94FD208C1A1600FE41D9 /* NIB.hpp */, 4B4518981F75FD1B00926311 /* OricMFMDSK.hpp */, @@ -2491,7 +2502,8 @@ 4B8944EB201967B4007DE474 /* Acorn */, 4B894514201967B4007DE474 /* AmstradCPC */, 4B15A9FE20824C9F005E6C8D /* AppleII */, - 4B8944F3201967B4007DE474 /* Atari */, + 4BC1316C2346DE5000E4FF3D /* Atari2600 */, + 4BC131722346DE9100E4FF3D /* AtariST */, 4B7A90EA20410A85008514A2 /* Coleco */, 4B8944FB201967B4007DE474 /* Commodore */, 4B894507201967B4007DE474 /* Disassembler */, @@ -2520,16 +2532,6 @@ path = Acorn; sourceTree = ""; }; - 4B8944F3201967B4007DE474 /* Atari */ = { - isa = PBXGroup; - children = ( - 4B8944F5201967B4007DE474 /* StaticAnalyser.cpp */, - 4B8944F4201967B4007DE474 /* StaticAnalyser.hpp */, - 4BE3231720532CC0006EF799 /* Target.hpp */, - ); - path = Atari; - sourceTree = ""; - }; 4B8944F6201967B4007DE474 /* Oric */ = { isa = PBXGroup; children = ( @@ -3193,6 +3195,26 @@ path = AtariST; sourceTree = ""; }; + 4BC1316C2346DE5000E4FF3D /* Atari2600 */ = { + isa = PBXGroup; + children = ( + 4BC1316D2346DE5000E4FF3D /* StaticAnalyser.hpp */, + 4BC1316E2346DE5000E4FF3D /* Target.hpp */, + 4BC1316F2346DE5000E4FF3D /* StaticAnalyser.cpp */, + ); + path = Atari2600; + sourceTree = ""; + }; + 4BC131722346DE9100E4FF3D /* AtariST */ = { + isa = PBXGroup; + children = ( + 4BC131732346DE9100E4FF3D /* StaticAnalyser.hpp */, + 4BC131742346DE9100E4FF3D /* Target.hpp */, + 4BC131752346DE9100E4FF3D /* StaticAnalyser.cpp */, + ); + path = AtariST; + sourceTree = ""; + }; 4BC9DF4A1D04691600F44158 /* Components */ = { isa = PBXGroup; children = ( @@ -4048,6 +4070,7 @@ 4B89451D201967B4007DE474 /* Disk.cpp in Sources */, 4BDACBED22FFA5D20045EF7E /* ncr5380.cpp in Sources */, 4BC1316B2346C61100E4FF3D /* AtariST.cpp in Sources */, + 4BC131772346DE9100E4FF3D /* StaticAnalyser.cpp in Sources */, 4B055ACF1FAE9B030060FFFF /* SoundGenerator.cpp in Sources */, 4B894519201967B4007DE474 /* ConfidenceCounter.cpp in Sources */, 4B055AEE1FAE9BBF0060FFFF /* Keyboard.cpp in Sources */, @@ -4071,6 +4094,7 @@ 4B055AEF1FAE9BF00060FFFF /* Typer.cpp in Sources */, 4B89453F201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4B89453D201967B4007DE474 /* StaticAnalyser.cpp in Sources */, + 4BC131712346DE5000E4FF3D /* StaticAnalyser.cpp in Sources */, 4B055ACA1FAE9AFB0060FFFF /* Vic20.cpp in Sources */, 4B8318B222D3E53C006DB630 /* Video.cpp in Sources */, 4B055ABC1FAE86170060FFFF /* ZX8081.cpp in Sources */, @@ -4080,6 +4104,7 @@ 4B74CF822312FA9C00500CE8 /* HFV.cpp in Sources */, 4B8318B022D3E531006DB630 /* AppleII.cpp in Sources */, 4B055AB11FAE86070060FFFF /* Tape.cpp in Sources */, + 4BC1317B2346DF2B00E4FF3D /* MSA.cpp in Sources */, 4BFE7B881FC39D8900160B38 /* StandardOptions.cpp in Sources */, 4B894533201967B4007DE474 /* 6502.cpp in Sources */, 4B055AA91FAE85EF0060FFFF /* CommodoreGCR.cpp in Sources */, @@ -4133,7 +4158,6 @@ 4B055A9F1FAE85DA0060FFFF /* HFE.cpp in Sources */, 4B07835B1FC11D42001D12BB /* Configurable.cpp in Sources */, 4BD191F52191180E0042E144 /* ScanTarget.cpp in Sources */, - 4B894523201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4B055AEC1FAE9BA20060FFFF /* Z80Base.cpp in Sources */, 4B0F94FF208C1A1600FE41D9 /* NIB.cpp in Sources */, 4B0E04EB1FC9E78800F43484 /* CAS.cpp in Sources */, @@ -4275,7 +4299,6 @@ 4B2B3A4B1F9B8FA70062DABF /* Typer.cpp in Sources */, 4B4518821F75E91A00926311 /* PCMSegment.cpp in Sources */, 4B74CF812312FA9C00500CE8 /* HFV.cpp in Sources */, - 4B894522201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4B17B58B20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */, 4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */, 4B80AD001F85CACA00176895 /* BestEffortUpdater.cpp in Sources */, @@ -4310,6 +4333,7 @@ 4BCE0051227CE8CA000CA200 /* Video.cpp in Sources */, 4B894536201967B4007DE474 /* Z80.cpp in Sources */, 4BCA6CC81D9DD9F000C2D7B2 /* CommodoreROM.cpp in Sources */, + 4BC1317A2346DF2B00E4FF3D /* MSA.cpp in Sources */, 4BEA52661DF3472B007E74F2 /* TIASound.cpp in Sources */, 4BEBFB4D2002C4BF000708CC /* MSXDSK.cpp in Sources */, 4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */, @@ -4329,12 +4353,14 @@ 4B79A5011FC913C900EEDAD5 /* MSX.cpp in Sources */, 4BEE0A701D72496600532C7B /* PRG.cpp in Sources */, 4BF437EE209D0F7E008CBD6B /* SegmentParser.cpp in Sources */, + 4BC131762346DE9100E4FF3D /* StaticAnalyser.cpp in Sources */, 4B8334861F5DA3780097E338 /* 6502Storage.cpp in Sources */, 4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */, 4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */, 4B55DD8320DF06680043F2E5 /* MachinePicker.swift in Sources */, 4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */, 4B89453E201967B4007DE474 /* StaticAnalyser.cpp in Sources */, + 4BC131702346DE5000E4FF3D /* StaticAnalyser.cpp in Sources */, 4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */, 4BCE0053227CE8CA000CA200 /* AppleII.cpp in Sources */, 4B8334821F5D9FF70097E338 /* PartialMachineCycle.cpp in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index d318aa73b..c6ef0500a 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -387,6 +387,26 @@ LSTypeIsPackage + + CFBundleTypeExtensions + + msa + + CFBundleTypeIconFile + floppy35.png + CFBundleTypeName + Atari ST Disk Image + CFBundleTypeOSTypes + + ???? + + CFBundleTypeRole + Viewer + LSHandlerRank + Owner + LSTypeIsPackage + + CFBundleTypeExtensions diff --git a/Storage/Disk/DiskImage/Formats/MSA.cpp b/Storage/Disk/DiskImage/Formats/MSA.cpp new file mode 100644 index 000000000..9e24a87a3 --- /dev/null +++ b/Storage/Disk/DiskImage/Formats/MSA.cpp @@ -0,0 +1,23 @@ +// +// MSA.cpp +// Clock Signal +// +// Created by Thomas Harte on 03/10/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#include "MSA.hpp" + +using namespace Storage::Disk; + +MSA::MSA(const std::string &file_name) : + file_(file_name) { +} + +std::shared_ptr<::Storage::Disk::Track> MSA::get_track_at_position(::Storage::Disk::Track::Address address) { + return nullptr; +} + +HeadPosition MSA::get_maximum_head_position() { + return HeadPosition(10); +} diff --git a/Storage/Disk/DiskImage/Formats/MSA.hpp b/Storage/Disk/DiskImage/Formats/MSA.hpp new file mode 100644 index 000000000..076f85201 --- /dev/null +++ b/Storage/Disk/DiskImage/Formats/MSA.hpp @@ -0,0 +1,38 @@ +// +// MSA.hpp +// Clock Signal +// +// Created by Thomas Harte on 03/10/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef MSA_hpp +#define MSA_hpp + +#include "../DiskImage.hpp" +#include "../../../FileHolder.hpp" + +namespace Storage { +namespace Disk { + +/*! + Provides a @c DiskImage describing an Atari ST MSA disk image: + a track dump with some metadata and potentially patches of RLE compression. +*/ +class MSA final: public DiskImage { + public: + MSA(const std::string &file_name); + + // Implemented to satisfy @c DiskImage. + HeadPosition get_maximum_head_position() override; + std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) override; + + private: + FileHolder file_; +}; + + +} +} + +#endif /* MSA_hpp */ diff --git a/Storage/TargetPlatforms.hpp b/Storage/TargetPlatforms.hpp index 4df8a6a1c..1056147c2 100644 --- a/Storage/TargetPlatforms.hpp +++ b/Storage/TargetPlatforms.hpp @@ -16,25 +16,26 @@ enum Type: IntType { AmstradCPC = 1 << 1, AppleII = 1 << 2, Atari2600 = 1 << 3, - AcornAtom = 1 << 4, - AcornElectron = 1 << 5, - BBCMaster = 1 << 6, - BBCModelA = 1 << 7, - BBCModelB = 1 << 8, - ColecoVision = 1 << 9, - Commodore = 1 << 10, - DiskII = 1 << 11, - Sega = 1 << 12, - Macintosh = 1 << 13, - MSX = 1 << 14, - Oric = 1 << 15, - ZX80 = 1 << 16, - ZX81 = 1 << 17, + AtariST = 1 << 4, + AcornAtom = 1 << 5, + AcornElectron = 1 << 6, + BBCMaster = 1 << 7, + BBCModelA = 1 << 8, + BBCModelB = 1 << 9, + ColecoVision = 1 << 10, + Commodore = 1 << 11, + DiskII = 1 << 12, + Sega = 1 << 13, + Macintosh = 1 << 14, + MSX = 1 << 15, + Oric = 1 << 16, + ZX80 = 1 << 17, + ZX81 = 1 << 18, Acorn = AcornAtom | AcornElectron | BBCMaster | BBCModelA | BBCModelB, ZX8081 = ZX80 | ZX81, AllCartridge = Atari2600 | AcornElectron | ColecoVision | MSX, - AllDisk = Acorn | AmstradCPC | Commodore | Oric | MSX, + AllDisk = Acorn | AmstradCPC | Commodore | Oric | MSX, // TODO: | AtariST AllTape = Acorn | AmstradCPC | Commodore | Oric | ZX80 | ZX81 | MSX, }; From 3c5ae9cf8eb0c7bc0080acdb764fbfe98a839332 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 3 Oct 2019 22:41:20 -0400 Subject: [PATCH 03/39] Implements read-only MSA support. --- OSBindings/Mac/Clock Signal/Info.plist | 4 ++ Storage/Disk/DiskImage/Formats/MSA.cpp | 65 ++++++++++++++++++- Storage/Disk/DiskImage/Formats/MSA.hpp | 9 +++ .../Formats/Utility/ImplicitSectors.cpp | 2 +- .../Formats/Utility/ImplicitSectors.hpp | 4 +- 5 files changed, 79 insertions(+), 5 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index c6ef0500a..20f8b00df 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -386,6 +386,8 @@ Owner LSTypeIsPackage + NSDocumentClass + $(PRODUCT_MODULE_NAME).MachineDocument CFBundleTypeExtensions @@ -406,6 +408,8 @@ Owner LSTypeIsPackage + NSDocumentClass + $(PRODUCT_MODULE_NAME).MachineDocument CFBundleTypeExtensions diff --git a/Storage/Disk/DiskImage/Formats/MSA.cpp b/Storage/Disk/DiskImage/Formats/MSA.cpp index 9e24a87a3..ce7729290 100644 --- a/Storage/Disk/DiskImage/Formats/MSA.cpp +++ b/Storage/Disk/DiskImage/Formats/MSA.cpp @@ -8,16 +8,77 @@ #include "MSA.hpp" +#include "Utility/ImplicitSectors.hpp" + using namespace Storage::Disk; MSA::MSA(const std::string &file_name) : file_(file_name) { + const auto signature = file_.get16be(); + if(signature != 0x0e0f) throw Error::InvalidFormat; + + sectors_per_track_ = file_.get16be(); + sides_ = 1 + file_.get16be(); + starting_track_ = file_.get16be(); + ending_track_ = file_.get16be(); + + // Create the uncompressed track list. + while(true) { + const auto data_length = file_.get16be(); + if(file_.eof()) break; + + if(data_length == sectors_per_track_ * 512) { + // This is an uncompressed track. + uncompressed_tracks_.push_back(file_.read(data_length)); + } else { + // This is an RLE-compressed track. + std::vector track; + track.reserve(sectors_per_track_ * 512); + uint16_t pointer = 0; + while(pointer < data_length) { + const auto byte = file_.get8(); + + // Compression scheme: if the byte E5 is encountered, an RLE run follows. + // An RLE run is encoded as the byte to repeat plus a 16-bit repeat count. + if(byte != 0xe5) { + track.push_back(byte); + ++pointer; + continue; + } + + pointer += 4; + if(pointer > data_length) break; + + const auto value = file_.get8(); + auto count = file_.get16be(); + while(count--) { + track.push_back(value); + } + } + + if(pointer != data_length || track.size() != sectors_per_track_ * 512) throw Error::InvalidFormat; + uncompressed_tracks_.push_back(std::move(track)); + } + } + + if(uncompressed_tracks_.size() != (ending_track_ - starting_track_ + 1)*sides_) throw Error::InvalidFormat; } std::shared_ptr<::Storage::Disk::Track> MSA::get_track_at_position(::Storage::Disk::Track::Address address) { - return nullptr; + if(address.head >= sides_) return nullptr; + + const auto position = address.position.as_int(); + if(position < starting_track_) return nullptr; + if(position >= ending_track_) return nullptr; + + const auto &track = uncompressed_tracks_[size_t(position) * size_t(sides_) + size_t(address.head)]; + return track_for_sectors(track.data(), sectors_per_track_, uint8_t(position), uint8_t(address.head), 0, 2, true); } HeadPosition MSA::get_maximum_head_position() { - return HeadPosition(10); + return HeadPosition(ending_track_); +} + +int MSA::get_head_count() { + return sides_; } diff --git a/Storage/Disk/DiskImage/Formats/MSA.hpp b/Storage/Disk/DiskImage/Formats/MSA.hpp index 076f85201..5bd8542f0 100644 --- a/Storage/Disk/DiskImage/Formats/MSA.hpp +++ b/Storage/Disk/DiskImage/Formats/MSA.hpp @@ -12,6 +12,8 @@ #include "../DiskImage.hpp" #include "../../../FileHolder.hpp" +#include + namespace Storage { namespace Disk { @@ -25,10 +27,17 @@ class MSA final: public DiskImage { // Implemented to satisfy @c DiskImage. HeadPosition get_maximum_head_position() override; + int get_head_count() override; std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) override; private: FileHolder file_; + uint16_t sectors_per_track_; + uint16_t sides_; + uint16_t starting_track_; + uint16_t ending_track_; + + std::vector> uncompressed_tracks_; }; diff --git a/Storage/Disk/DiskImage/Formats/Utility/ImplicitSectors.cpp b/Storage/Disk/DiskImage/Formats/Utility/ImplicitSectors.cpp index 1b3b6ae41..20851efa3 100644 --- a/Storage/Disk/DiskImage/Formats/Utility/ImplicitSectors.cpp +++ b/Storage/Disk/DiskImage/Formats/Utility/ImplicitSectors.cpp @@ -18,7 +18,7 @@ using namespace Storage::Disk; -std::shared_ptr Storage::Disk::track_for_sectors(uint8_t *const source, int number_of_sectors, uint8_t track, uint8_t side, uint8_t first_sector, uint8_t size, bool is_double_density) { +std::shared_ptr Storage::Disk::track_for_sectors(const uint8_t *const source, int number_of_sectors, uint8_t track, uint8_t side, uint8_t first_sector, uint8_t size, bool is_double_density) { std::vector sectors; off_t byte_size = static_cast(128 << size); diff --git a/Storage/Disk/DiskImage/Formats/Utility/ImplicitSectors.hpp b/Storage/Disk/DiskImage/Formats/Utility/ImplicitSectors.hpp index e656f24af..8db583ee6 100644 --- a/Storage/Disk/DiskImage/Formats/Utility/ImplicitSectors.hpp +++ b/Storage/Disk/DiskImage/Formats/Utility/ImplicitSectors.hpp @@ -16,8 +16,8 @@ namespace Storage { namespace Disk { -std::shared_ptr track_for_sectors(uint8_t *const source, int number_of_sectors, uint8_t track, uint8_t side, uint8_t first_sector, uint8_t size, bool is_double_density); -void decode_sectors(Track &track, uint8_t *const destination, uint8_t first_sector, uint8_t last_sector, uint8_t sector_size, bool is_double_density); +std::shared_ptr track_for_sectors(const uint8_t *source, int number_of_sectors, uint8_t track, uint8_t side, uint8_t first_sector, uint8_t size, bool is_double_density); +void decode_sectors(Track &track, uint8_t *destination, uint8_t first_sector, uint8_t last_sector, uint8_t sector_size, bool is_double_density); } } From a2ca887b9995203ea92a04446a9742f633fdd623 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 3 Oct 2019 22:47:41 -0400 Subject: [PATCH 04/39] Removes unused includes. --- Storage/Disk/DiskImage/Formats/SSD.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Storage/Disk/DiskImage/Formats/SSD.cpp b/Storage/Disk/DiskImage/Formats/SSD.cpp index e46b74fdc..71cc6ba0b 100644 --- a/Storage/Disk/DiskImage/Formats/SSD.cpp +++ b/Storage/Disk/DiskImage/Formats/SSD.cpp @@ -8,10 +8,6 @@ #include "SSD.hpp" -#include - -#include "Utility/ImplicitSectors.hpp" - namespace { static const int sectors_per_track = 10; static const int sector_size = 1; @@ -29,7 +25,7 @@ SSD::SSD(const std::string &file_name) : MFMSectorDump(file_name) { // this has two heads if the suffix is .dsd, one if it's .ssd head_count_ = (tolower(file_name[file_name.size() - 3]) == 'd') ? 2 : 1; - track_count_ = static_cast(file_.stats().st_size / (256 * 10)); + track_count_ = int(file_.stats().st_size / (256 * 10)); if(track_count_ < 40) track_count_ = 40; else if(track_count_ < 80) track_count_ = 80; From 953423cc0288e8ba8a080692fe0ad3f9ee12a3e8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 3 Oct 2019 22:47:57 -0400 Subject: [PATCH 05/39] Starts to create an actual shell of a machine. --- Machines/AtariST/AtariST.cpp | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/Machines/AtariST/AtariST.cpp b/Machines/AtariST/AtariST.cpp index 4e4001993..da56271ee 100644 --- a/Machines/AtariST/AtariST.cpp +++ b/Machines/AtariST/AtariST.cpp @@ -8,10 +8,39 @@ #include "AtariST.hpp" +#include "../CRTMachine.hpp" + +namespace { + +const int CLOCK_RATE = 8000000; + +using Target = Analyser::Static::Target; + +class ConcreteMachine: + public Atari::ST::Machine, + public CRTMachine::Machine { + public: + ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) { + set_clock_rate(CLOCK_RATE); + } + + void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { + } + + Outputs::Speaker::Speaker *get_speaker() final { + return nullptr; + } + + void run_for(const Cycles cycles) final { + } +}; + +} + using namespace Atari::ST; Machine *Machine::AtariST(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { - return nullptr; + return new ConcreteMachine(*target, rom_fetcher); } Machine::~Machine() {} From a8a34497ff279d68effc075d60d49bc0cabeda2b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 4 Oct 2019 21:34:15 -0400 Subject: [PATCH 06/39] Gifts the Atari ST a 68000 and non-functional video. --- Machines/AtariST/AtariST.cpp | 40 ++++++++++++++++++- Machines/AtariST/Video.cpp | 22 ++++++++++ Machines/AtariST/Video.hpp | 39 ++++++++++++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 8 ++++ 4 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 Machines/AtariST/Video.cpp create mode 100644 Machines/AtariST/Video.hpp diff --git a/Machines/AtariST/AtariST.cpp b/Machines/AtariST/AtariST.cpp index da56271ee..eff85308c 100644 --- a/Machines/AtariST/AtariST.cpp +++ b/Machines/AtariST/AtariST.cpp @@ -10,7 +10,13 @@ #include "../CRTMachine.hpp" -namespace { +#include "../../Processors/68000/68000.hpp" + +#include "Video.hpp" +#include "../../ClockReceiver/JustInTime.hpp" + +namespace Atari { +namespace ST { const int CLOCK_RATE = 8000000; @@ -18,13 +24,28 @@ using Target = Analyser::Static::Target; class ConcreteMachine: public Atari::ST::Machine, + public CPU::MC68000::BusHandler, public CRTMachine::Machine { public: - ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) { + ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : + mc68000_(*this) { set_clock_rate(CLOCK_RATE); + + ram_.resize(512 * 1024); + + std::vector rom_descriptions = { + {"AtariST", "the TOS ROM", "tos100.img", 192*1024, 0x1a586c64} + }; + const auto roms = rom_fetcher(rom_descriptions); + if(!roms[0]) { + throw ROMMachine::Error::MissingROMs; + } + rom_ = *roms[0]; } + // MARK: CRTMachine::Machine void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { + video_->set_scan_target(scan_target); } Outputs::Speaker::Speaker *get_speaker() final { @@ -32,9 +53,24 @@ class ConcreteMachine: } void run_for(const Cycles cycles) final { + mc68000_.run_for(cycles); } + + // MARK: MC68000::BusHandler + HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int is_supervisor) { + return HalfCycles(0); + } + + private: + CPU::MC68000::Processor mc68000_; + JustInTimeActor video_; + + std::vector ram_; + std::vector rom_; + }; +} } using namespace Atari::ST; diff --git a/Machines/AtariST/Video.cpp b/Machines/AtariST/Video.cpp new file mode 100644 index 000000000..5387799fd --- /dev/null +++ b/Machines/AtariST/Video.cpp @@ -0,0 +1,22 @@ +// +// Video.cpp +// Clock Signal +// +// Created by Thomas Harte on 04/10/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#include "Video.hpp" + +using namespace Atari::ST; + +Video::Video() : + crt_(512, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4) { +} + +void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) { + crt_.set_scan_target(scan_target); +} + +void Video::run_for(HalfCycles duration) { +} diff --git a/Machines/AtariST/Video.hpp b/Machines/AtariST/Video.hpp new file mode 100644 index 000000000..8a93e5c6b --- /dev/null +++ b/Machines/AtariST/Video.hpp @@ -0,0 +1,39 @@ +// +// Video.hpp +// Clock Signal +// +// Created by Thomas Harte on 04/10/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef Video_hpp +#define Video_hpp + +#include "../../Outputs/CRT/CRT.hpp" +#include "../../ClockReceiver/ClockReceiver.hpp" + +namespace Atari { +namespace ST { + +class Video { + public: + Video(); + + /*! + Sets the target device for video data. + */ + void set_scan_target(Outputs::Display::ScanTarget *scan_target); + + /*! + Produces the next @c duration period of pixels. + */ + void run_for(HalfCycles duration); + + private: + Outputs::CRT::CRT crt_; +}; + +} +} + +#endif /* Video_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index b083d6697..26fcce5e6 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -618,6 +618,8 @@ 4BB4BFB022A42F290069048D /* MacintoshIMG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB4BFAE22A42F290069048D /* MacintoshIMG.cpp */; }; 4BB4BFB922A4372F0069048D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB4BFB822A4372E0069048D /* StaticAnalyser.cpp */; }; 4BB4BFBA22A4372F0069048D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB4BFB822A4372E0069048D /* StaticAnalyser.cpp */; }; + 4BB50DC8234828A20057B9CA /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB50DC6234828A20057B9CA /* Video.cpp */; }; + 4BB50DC9234828A70057B9CA /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB50DC6234828A20057B9CA /* Video.cpp */; }; 4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */; }; 4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */; }; 4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */; }; @@ -1416,6 +1418,8 @@ 4BB4BFAF22A42F290069048D /* MacintoshIMG.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MacintoshIMG.hpp; sourceTree = ""; }; 4BB4BFB722A4372E0069048D /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = ""; }; 4BB4BFB822A4372E0069048D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = ""; }; + 4BB50DC6234828A20057B9CA /* Video.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Video.cpp; sourceTree = ""; }; + 4BB50DC7234828A20057B9CA /* Video.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = ""; }; 4BB697C61D4B558F00248BDF /* Factors.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Factors.hpp; path = ../../NumberTheory/Factors.hpp; sourceTree = ""; }; 4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TimedEventLoop.cpp; sourceTree = ""; }; 4BB697CA1D4B6D3E00248BDF /* TimedEventLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TimedEventLoop.hpp; sourceTree = ""; }; @@ -3191,6 +3195,8 @@ children = ( 4BC131682346C61100E4FF3D /* AtariST.cpp */, 4BC131692346C61100E4FF3D /* AtariST.hpp */, + 4BB50DC6234828A20057B9CA /* Video.cpp */, + 4BB50DC7234828A20057B9CA /* Video.hpp */, ); path = AtariST; sourceTree = ""; @@ -4063,6 +4069,7 @@ 4B055AC31FAE9AE80060FFFF /* AmstradCPC.cpp in Sources */, 4B055A9E1FAE85DA0060FFFF /* G64.cpp in Sources */, 4B055AB81FAE860F0060FFFF /* ZX80O81P.cpp in Sources */, + 4BB50DC9234828A70057B9CA /* Video.cpp in Sources */, 4B055A8E1FAE85920060FFFF /* BestEffortUpdater.cpp in Sources */, 4B055AB01FAE86070060FFFF /* PulseQueuedTape.cpp in Sources */, 4B055AAC1FAE85FD0060FFFF /* PCMSegment.cpp in Sources */, @@ -4269,6 +4276,7 @@ 4B302184208A550100773308 /* DiskII.cpp in Sources */, 4BEA52631DF339D7007E74F2 /* SoundGenerator.cpp in Sources */, 4BD67DD0209BF27B00AB2146 /* Encoder.cpp in Sources */, + 4BB50DC8234828A20057B9CA /* Video.cpp in Sources */, 4BAE495920328897004BE78E /* ZX8081OptionsPanel.swift in Sources */, 4B89451A201967B4007DE474 /* ConfidenceSummary.cpp in Sources */, 4B54C0C51F8D91D90050900F /* Keyboard.cpp in Sources */, From 3f45cd2380acc4a036da4a9e26613a246aa30800 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 4 Oct 2019 22:38:46 -0400 Subject: [PATCH 07/39] Starts forming an Atari ST memory map. --- Machines/AtariST/AtariST.cpp | 59 ++++++++++++++++++++++++++++--- Machines/Utility/MemoryPacker.cpp | 5 +++ Machines/Utility/MemoryPacker.hpp | 7 ++++ 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/Machines/AtariST/AtariST.cpp b/Machines/AtariST/AtariST.cpp index eff85308c..750ad2972 100644 --- a/Machines/AtariST/AtariST.cpp +++ b/Machines/AtariST/AtariST.cpp @@ -15,6 +15,9 @@ #include "Video.hpp" #include "../../ClockReceiver/JustInTime.hpp" +#include "../Utility/MemoryPacker.hpp" +#include "../Utility/MemoryFuzzer.hpp" + namespace Atari { namespace ST { @@ -31,7 +34,8 @@ class ConcreteMachine: mc68000_(*this) { set_clock_rate(CLOCK_RATE); - ram_.resize(512 * 1024); + ram_.resize(512 * 512); + Memory::Fuzz(ram_); std::vector rom_descriptions = { {"AtariST", "the TOS ROM", "tos100.img", 192*1024, 0x1a586c64} @@ -40,7 +44,7 @@ class ConcreteMachine: if(!roms[0]) { throw ROMMachine::Error::MissingROMs; } - rom_ = *roms[0]; + Memory::PackBigEndian16(*roms[0], rom_); } // MARK: CRTMachine::Machine @@ -57,7 +61,54 @@ class ConcreteMachine: } // MARK: MC68000::BusHandler + using Microcycle = CPU::MC68000::Microcycle; HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int is_supervisor) { + // Advance time. + video_ += cycle.length; + + // A null cycle leaves nothing else to do. + if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return HalfCycles(0); + + auto address = cycle.word_address(); + uint16_t *memory; + if(address < 4) { + memory = rom_.data(); + } else if(address < 0x700000) { + memory = ram_.data(); + address &= ram_.size() - 1; + // TODO: align with the next access window. + } else if(address < 0x780000) { // TOS 2.0+ address + memory = rom_.data(); + address &= rom_.size() - 1; + } else if(address >= 0x7e0000 && address < 0x7f8000) { // TOS 1.0 address + memory = rom_.data(); + address &= rom_.size() - 1; + } else { + assert(false); + } + + // If control has fallen through to here, the access is either a read from ROM, or a read or write to RAM. + switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) { + default: + break; + + case Microcycle::SelectWord | Microcycle::Read: + cycle.value->full = memory[address]; + break; + case Microcycle::SelectByte | Microcycle::Read: + cycle.value->halves.low = uint8_t(memory[address] >> cycle.byte_shift()); + break; + case Microcycle::SelectWord: + memory[address] = cycle.value->full; + break; + case Microcycle::SelectByte: + memory[address] = uint16_t( + (cycle.value->halves.low << cycle.byte_shift()) | + (memory[address] & cycle.untouched_byte_mask()) + ); + break; + } + return HalfCycles(0); } @@ -65,8 +116,8 @@ class ConcreteMachine: CPU::MC68000::Processor mc68000_; JustInTimeActor video_; - std::vector ram_; - std::vector rom_; + std::vector ram_; + std::vector rom_; }; diff --git a/Machines/Utility/MemoryPacker.cpp b/Machines/Utility/MemoryPacker.cpp index 9630af259..101a68b5c 100644 --- a/Machines/Utility/MemoryPacker.cpp +++ b/Machines/Utility/MemoryPacker.cpp @@ -13,3 +13,8 @@ void Memory::PackBigEndian16(const std::vector &source, uint16_t *targe target[c >> 1] = uint16_t(source[c] << 8) | uint16_t(source[c+1]); } } + +void Memory::PackBigEndian16(const std::vector &source, std::vector &target) { + target.resize(source.size() >> 1); + PackBigEndian16(source, target.data()); +} diff --git a/Machines/Utility/MemoryPacker.hpp b/Machines/Utility/MemoryPacker.hpp index 145191829..dbfd624a0 100644 --- a/Machines/Utility/MemoryPacker.hpp +++ b/Machines/Utility/MemoryPacker.hpp @@ -20,5 +20,12 @@ namespace Memory { */ void PackBigEndian16(const std::vector &source, uint16_t *target); +/*! + Copies the bytes from @c source into @c target, interpreting them + as big-endian 16-bit data. @c target will be resized to the proper size + exactly to contain the contents of @c source. +*/ +void PackBigEndian16(const std::vector &source, std::vector &target); + } #endif /* MemoryPacker_hpp */ From fcd214369741718f3fb00f97b85b0c3b1b6af2dd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 6 Oct 2019 17:15:29 -0400 Subject: [PATCH 08/39] Starts to formalise the ST memory map a little. --- Machines/AtariST/AtariST.cpp | 70 ++++++++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/Machines/AtariST/AtariST.cpp b/Machines/AtariST/AtariST.cpp index 750ad2972..0cba6c43d 100644 --- a/Machines/AtariST/AtariST.cpp +++ b/Machines/AtariST/AtariST.cpp @@ -45,6 +45,17 @@ class ConcreteMachine: throw ROMMachine::Error::MissingROMs; } Memory::PackBigEndian16(*roms[0], rom_); + + // Set up basic memory map. + memory_map_[0] = BusDevice::MostlyRAM; + for(int c = 1; c < 0xf0; ++c) memory_map_[c] = BusDevice::RAM; + + // This is appropriate for: TOS 1.x, no cartridge. + for(int c = 0xf0; c < 0xfc; ++c) memory_map_[c] = BusDevice::Unassigned; + for(int c = 0xfc; c < 0xff; ++c) memory_map_[c] = BusDevice::ROM; + memory_map_[0xfa] = memory_map_[0xfb] = BusDevice::Cartridge; + + memory_map_[0xff] = BusDevice::IO; } // MARK: CRTMachine::Machine @@ -69,22 +80,49 @@ class ConcreteMachine: // A null cycle leaves nothing else to do. if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return HalfCycles(0); + /* TODO: DTack, bus error, VPA. */ + auto address = cycle.word_address(); uint16_t *memory; - if(address < 4) { - memory = rom_.data(); - } else if(address < 0x700000) { - memory = ram_.data(); - address &= ram_.size() - 1; - // TODO: align with the next access window. - } else if(address < 0x780000) { // TOS 2.0+ address - memory = rom_.data(); - address &= rom_.size() - 1; - } else if(address >= 0x7e0000 && address < 0x7f8000) { // TOS 1.0 address - memory = rom_.data(); - address &= rom_.size() - 1; - } else { - assert(false); + switch(memory_map_[address >> 15]) { + case BusDevice::MostlyRAM: + if(address < 4) { + memory = rom_.data(); + break; + } + case BusDevice::RAM: + memory = ram_.data(); + address &= ram_.size() - 1; + // TODO: align with the next access window. + break; + + case BusDevice::ROM: + memory = rom_.data(); + address %= rom_.size(); + break; + + case BusDevice::Cartridge: + /* + TOS 1.0 appears to attempt to read from the catridge before it has setup + the bus error vector. Therefore I assume no bus error flows. + */ + switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) { + default: break; + case Microcycle::SelectWord | Microcycle::Read: + *cycle.value = 0xffff; + break; + case Microcycle::SelectByte | Microcycle::Read: + cycle.value->halves.low = 0xff; + break; + } + return HalfCycles(0); + + case BusDevice::Unassigned: + return HalfCycles(0); + + case BusDevice::IO: + assert(false); + break; } // If control has fallen through to here, the access is either a read from ROM, or a read or write to RAM. @@ -119,6 +157,10 @@ class ConcreteMachine: std::vector ram_; std::vector rom_; + enum class BusDevice { + MostlyRAM, RAM, ROM, Cartridge, IO, Unassigned + }; + BusDevice memory_map_[256]; }; } From e195497ab7f3d4f6187b645627d3ea0db600fbe0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 6 Oct 2019 22:30:48 -0400 Subject: [PATCH 09/39] Adds some semblance of an AY. --- Machines/AtariST/AtariST.cpp | 81 +++++++++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 5 deletions(-) diff --git a/Machines/AtariST/AtariST.cpp b/Machines/AtariST/AtariST.cpp index 0cba6c43d..1cda5e5a6 100644 --- a/Machines/AtariST/AtariST.cpp +++ b/Machines/AtariST/AtariST.cpp @@ -12,8 +12,13 @@ #include "../../Processors/68000/68000.hpp" +#include "../../Components/AY38910/AY38910.hpp" + #include "Video.hpp" #include "../../ClockReceiver/JustInTime.hpp" +#include "../../ClockReceiver/ForceInline.hpp" + +#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" #include "../Utility/MemoryPacker.hpp" #include "../Utility/MemoryFuzzer.hpp" @@ -31,8 +36,11 @@ class ConcreteMachine: public CRTMachine::Machine { public: ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : - mc68000_(*this) { + mc68000_(*this), + ay_(audio_queue_), + speaker_(ay_) { set_clock_rate(CLOCK_RATE); + speaker_.set_input_rate(CLOCK_RATE / 4); ram_.resize(512 * 512); Memory::Fuzz(ram_); @@ -58,13 +66,17 @@ class ConcreteMachine: memory_map_[0xff] = BusDevice::IO; } + ~ConcreteMachine() { + audio_queue_.flush(); + } + // MARK: CRTMachine::Machine void set_scan_target(Outputs::Display::ScanTarget *scan_target) final { video_->set_scan_target(scan_target); } Outputs::Speaker::Speaker *get_speaker() final { - return nullptr; + return &speaker_; } void run_for(const Cycles cycles) final { @@ -75,7 +87,7 @@ class ConcreteMachine: using Microcycle = CPU::MC68000::Microcycle; HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int is_supervisor) { // Advance time. - video_ += cycle.length; + advance_time(cycle.length); // A null cycle leaves nothing else to do. if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return HalfCycles(0); @@ -83,6 +95,7 @@ class ConcreteMachine: /* TODO: DTack, bus error, VPA. */ auto address = cycle.word_address(); + if(cycle.data_select_active()) printf("%c %06x\n", (cycle.operation & Microcycle::Read) ? 'r' : 'w', *cycle.address & 0xffffff); uint16_t *memory; switch(memory_map_[address >> 15]) { case BusDevice::MostlyRAM: @@ -118,11 +131,51 @@ class ConcreteMachine: return HalfCycles(0); case BusDevice::Unassigned: + assert(false); return HalfCycles(0); case BusDevice::IO: - assert(false); - break; + switch(address) { + default: + assert(false); + + case 0x7fc000: + /* Memory controller configuration: + b0, b1: bank 1 + b2, b3: bank 0 + + 00 = 128k + 01 = 512k + 10 = 2mb + 11 = reserved + */ + break; + + case 0x7fc400: /* PSG: write to select register, read to read register. */ + case 0x7fc401: /* PSG: write to write register. */ + if(!cycle.data_select_active()) return HalfCycles(0); + + // TODO: byte accesses to the odd addresses shouldn't obey logic below. + advance_time(HalfCycles(2)); + update_audio(); + if(cycle.operation & Microcycle::Read) { + ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1)); + cycle.value->halves.low = ay_.get_data_output(); + ay_.set_control_lines(GI::AY38910::ControlLines(0)); + } else { + if(address == 0x7fc400) { + ay_.set_control_lines(GI::AY38910::BC1); + ay_.set_data_input(cycle.value->halves.low); + ay_.set_control_lines(GI::AY38910::ControlLines(0)); + } else { + ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BDIR)); + ay_.set_data_input(cycle.value->halves.low); + ay_.set_control_lines(GI::AY38910::ControlLines(0)); + } + } + return HalfCycles(2); + } + return HalfCycles(0); } // If control has fallen through to here, the access is either a read from ROM, or a read or write to RAM. @@ -150,10 +203,28 @@ class ConcreteMachine: return HalfCycles(0); } + void flush() { + audio_queue_.perform(); + } + private: + forceinline void advance_time(HalfCycles length) { + video_ += length; + cycles_since_audio_update_ += length; + } + + void update_audio() { + speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide_cycles(Cycles(4))); + } + CPU::MC68000::Processor mc68000_; JustInTimeActor video_; + Concurrency::DeferringAsyncTaskQueue audio_queue_; + GI::AY38910::AY38910 ay_; + Outputs::Speaker::LowpassSpeaker speaker_; + HalfCycles cycles_since_audio_update_; + std::vector ram_; std::vector rom_; From 885f890df1af07a3d8f805343f84ccb918c343e0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 6 Oct 2019 23:14:05 -0400 Subject: [PATCH 10/39] Adds a target for MFP read/write operations. Completely without any implementation, so far. --- Components/68901/MFP68901.cpp | 28 ++++++++++ Components/68901/MFP68901.hpp | 30 +++++++++++ Machines/AtariST/AtariST.cpp | 51 +++++++++++++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 18 ++++++- 4 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 Components/68901/MFP68901.cpp create mode 100644 Components/68901/MFP68901.hpp diff --git a/Components/68901/MFP68901.cpp b/Components/68901/MFP68901.cpp new file mode 100644 index 000000000..be1b48a86 --- /dev/null +++ b/Components/68901/MFP68901.cpp @@ -0,0 +1,28 @@ +// +// MFP68901.cpp +// Clock Signal +// +// Created by Thomas Harte on 06/10/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#include "MFP68901.hpp" + +using namespace Motorola::MFP68901; + +uint8_t MFP68901::read(int address) { + /* TODO */ + return 0xff; +} + +void MFP68901::write(int address, uint8_t value) { + /* TODO */ +} + +void MFP68901::run_for(HalfCycles) { + /* TODO */ +} + +HalfCycles MFP68901::get_next_sequence_point() { + return HalfCycles(-1); +} diff --git a/Components/68901/MFP68901.hpp b/Components/68901/MFP68901.hpp new file mode 100644 index 000000000..fbecb5c0b --- /dev/null +++ b/Components/68901/MFP68901.hpp @@ -0,0 +1,30 @@ +// +// MFP68901.hpp +// Clock Signal +// +// Created by Thomas Harte on 06/10/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef MFP68901_hpp +#define MFP68901_hpp + +#include +#include "../../ClockReceiver/ClockReceiver.hpp" + +namespace Motorola { +namespace MFP68901 { + +class MFP68901 { + public: + uint8_t read(int address); + void write(int address, uint8_t value); + + void run_for(HalfCycles); + HalfCycles get_next_sequence_point(); +}; + +} +} + +#endif /* MFP68901_hpp */ diff --git a/Machines/AtariST/AtariST.cpp b/Machines/AtariST/AtariST.cpp index 1cda5e5a6..b7bef6755 100644 --- a/Machines/AtariST/AtariST.cpp +++ b/Machines/AtariST/AtariST.cpp @@ -13,6 +13,7 @@ #include "../../Processors/68000/68000.hpp" #include "../../Components/AY38910/AY38910.hpp" +#include "../../Components/68901/MFP68901.hpp" #include "Video.hpp" #include "../../ClockReceiver/JustInTime.hpp" @@ -173,7 +174,55 @@ class ConcreteMachine: ay_.set_control_lines(GI::AY38910::ControlLines(0)); } } + + /* + TODO: Port A: + b7: reserved + b6: "freely usable output (monitor jack)" + b5: centronics strobe + b4: RS-232 DTR output + b3: RS-232 RTS output + b2: select floppy drive 1 + b1: select floppy drive 0 + b0: "page choice signal for double-sided floppy drive" + */ return HalfCycles(2); + + // The MFP block: + case 0x7ffd00: case 0x7ffd01: case 0x7ffd02: case 0x7ffd03: + case 0x7ffd04: case 0x7ffd05: case 0x7ffd06: case 0x7ffd07: + case 0x7ffd08: case 0x7ffd09: case 0x7ffd0a: case 0x7ffd0b: + case 0x7ffd0c: case 0x7ffd0d: case 0x7ffd0e: case 0x7ffd0f: + case 0x7ffd10: case 0x7ffd11: case 0x7ffd12: case 0x7ffd13: + case 0x7ffd14: case 0x7ffd15: case 0x7ffd16: case 0x7ffd17: + case 0x7ffd18: case 0x7ffd19: case 0x7ffd1a: case 0x7ffd1b: + case 0x7ffd1c: case 0x7ffd1d: case 0x7ffd1e: case 0x7ffd1f: + if(!cycle.data_select_active()) return HalfCycles(0); + + // The lower data lines aren't connected. + if(!cycle.upper_data_select()) { + if(cycle.operation & Microcycle::Read) { + cycle.value->halves.low = 0xff; + } + return HalfCycles(0); + } + + if(cycle.operation & Microcycle::Read) { + const uint8_t value = mfp_->read(int(address)); + if(cycle.operation & Microcycle::SelectByte) { + cycle.value->halves.low = value; + } else { + cycle.value->halves.high = value; + cycle.value->halves.low = 0xff; + } + } else { + if(cycle.operation & Microcycle::SelectByte) { + mfp_->write(int(address), cycle.value->halves.low); + } else { + mfp_->write(int(address), cycle.value->halves.high); + } + } + break; } return HalfCycles(0); } @@ -211,6 +260,7 @@ class ConcreteMachine: forceinline void advance_time(HalfCycles length) { video_ += length; cycles_since_audio_update_ += length; + mfp_ += length; } void update_audio() { @@ -219,6 +269,7 @@ class ConcreteMachine: CPU::MC68000::Processor mc68000_; JustInTimeActor video_; + JustInTimeActor mfp_; Concurrency::DeferringAsyncTaskQueue audio_queue_; GI::AY38910::AY38910 ay_; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 26fcce5e6..e8ab56003 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -317,6 +317,8 @@ 4B90467622C6FD6E000E2074 /* 68000ArithmeticTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B90467522C6FD6E000E2074 /* 68000ArithmeticTests.mm */; }; 4B924E991E74D22700B76AF1 /* AtariStaticAnalyserTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */; }; 4B9252CE1E74D28200B76AF1 /* Atari ROMs in Resources */ = {isa = PBXBuildFile; fileRef = 4B9252CD1E74D28200B76AF1 /* Atari ROMs */; }; + 4B92E26A234AE35100CD6D1B /* MFP68901.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B92E268234AE35000CD6D1B /* MFP68901.cpp */; }; + 4B92E26B234AE35100CD6D1B /* MFP68901.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B92E268234AE35000CD6D1B /* MFP68901.cpp */; }; 4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */; }; 4B9378E422A199C600973513 /* Audio.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9378E222A199C600973513 /* Audio.cpp */; }; 4B98A05E1FFAD3F600ADF63B /* CSROMFetcher.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B98A05D1FFAD3F600ADF63B /* CSROMFetcher.mm */; }; @@ -1108,6 +1110,8 @@ 4B92294A22B064FD00A1458F /* QuadratureMouse.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = QuadratureMouse.hpp; sourceTree = ""; }; 4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AtariStaticAnalyserTests.mm; sourceTree = ""; }; 4B9252CD1E74D28200B76AF1 /* Atari ROMs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "Atari ROMs"; sourceTree = ""; }; + 4B92E268234AE35000CD6D1B /* MFP68901.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MFP68901.cpp; sourceTree = ""; }; + 4B92E269234AE35000CD6D1B /* MFP68901.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MFP68901.hpp; sourceTree = ""; }; 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6502TimingTests.swift; sourceTree = ""; }; 4B9378E222A199C600973513 /* Audio.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Audio.cpp; sourceTree = ""; }; 4B9378E322A199C600973513 /* Audio.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Audio.hpp; sourceTree = ""; }; @@ -2629,6 +2633,15 @@ path = QuadratureMouse; sourceTree = ""; }; + 4B92E267234AE35000CD6D1B /* 68901 */ = { + isa = PBXGroup; + children = ( + 4B92E268234AE35000CD6D1B /* MFP68901.cpp */, + 4B92E269234AE35000CD6D1B /* MFP68901.hpp */, + ); + path = 68901; + sourceTree = ""; + }; 4B9F11C72272375400701480 /* QL Startup */ = { isa = PBXGroup; children = ( @@ -3224,8 +3237,8 @@ 4BC9DF4A1D04691600F44158 /* Components */ = { isa = PBXGroup; children = ( - 4BDACBE922FFA5B50045EF7E /* 5380 */, 4BD468F81D8DF4290084958B /* 1770 */, + 4BDACBE922FFA5B50045EF7E /* 5380 */, 4BC9DF4B1D04691600F44158 /* 6522 */, 4B1E85791D174DEC001EF87D /* 6532 */, 4BC9DF4C1D04691600F44158 /* 6560 */, @@ -3234,6 +3247,7 @@ 4BBC951F1F368D87008F4C34 /* 8272 */, 4BB244D222AABAF500BE20E5 /* 8530 */, 4B0E04F71FC9F2C800F43484 /* 9918 */, + 4B92E267234AE35000CD6D1B /* 68901 */, 4B595FAA2086DFBA0083CAA8 /* AudioToggle */, 4B4A762D1DB1A35C007AAE2E /* AY38910 */, 4B302181208A550100773308 /* DiskII */, @@ -4035,6 +4049,7 @@ 4B89452F201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4B894531201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4B894539201967B4007DE474 /* Tape.cpp in Sources */, + 4B92E26B234AE35100CD6D1B /* MFP68901.cpp in Sources */, 4B7F1898215486A200388727 /* StaticAnalyser.cpp in Sources */, 4B15A9FD208249BB005E6C8D /* StaticAnalyser.cpp in Sources */, 4B055AD31FAE9B0B0060FFFF /* Microdisc.cpp in Sources */, @@ -4231,6 +4246,7 @@ 4BB4BFB022A42F290069048D /* MacintoshIMG.cpp in Sources */, 4B05401E219D1618001BF69C /* ScanTarget.cpp in Sources */, 4B4518861F75E91A00926311 /* MFMDiskController.cpp in Sources */, + 4B92E26A234AE35100CD6D1B /* MFP68901.cpp in Sources */, 4B54C0BF1F8D8F450050900F /* Keyboard.cpp in Sources */, 4B3FE75E1F3CF68B00448EE4 /* CPM.cpp in Sources */, 4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */, From 1de1818ebb674889ba4afd02ff7f98b6b0ed3dc9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 7 Oct 2019 22:44:35 -0400 Subject: [PATCH 11/39] Makes an unsuccessful first attempt at some timer functionality. --- Components/68901/MFP68901.cpp | 141 +++++++++++++++++++++++++++++++++- Components/68901/MFP68901.hpp | 19 +++++ Machines/AtariST/AtariST.cpp | 15 +++- 3 files changed, 170 insertions(+), 5 deletions(-) diff --git a/Components/68901/MFP68901.cpp b/Components/68901/MFP68901.cpp index be1b48a86..5466ad959 100644 --- a/Components/68901/MFP68901.cpp +++ b/Components/68901/MFP68901.cpp @@ -8,21 +8,154 @@ #include "MFP68901.hpp" +#include "../../Outputs/Log.hpp" + using namespace Motorola::MFP68901; uint8_t MFP68901::read(int address) { - /* TODO */ + address &= 0x1f; + switch(address) { + case 0x00: LOG("Read: general purpose IO"); break; + case 0x01: LOG("Read: active edge"); break; + case 0x02: LOG("Read: data direction"); break; + case 0x03: LOG("Read: interrupt enable A"); break; + case 0x04: LOG("Read: interrupt enable B"); break; + case 0x05: LOG("Read: interrupt pending A"); break; + case 0x06: LOG("Read: interrupt pending B"); break; + case 0x07: LOG("Read: interrupt in-service A"); break; + case 0x08: LOG("Read: interrupt in-service B"); break; + case 0x09: LOG("Read: interrupt mask A"); break; + case 0x0a: LOG("Read: interrupt mask B"); break; + case 0x0b: LOG("Read: vector"); break; + case 0x0c: LOG("Read: timer A control"); break; + case 0x0d: LOG("Read: timer B control"); break; + case 0x0e: LOG("Read: timers C/D control"); break; + case 0x0f: case 0x10: case 0x11: case 0x12: + return get_timer_data(address - 0xf); + case 0x13: LOG("Read: sync character generator"); break; + case 0x14: LOG("Read: USART control"); break; + case 0x15: LOG("Read: receiver status"); break; + case 0x16: LOG("Read: transmitter status"); break; + case 0x17: LOG("Read: USART data"); break; + } return 0xff; } void MFP68901::write(int address, uint8_t value) { - /* TODO */ + address &= 0x1f; + switch(address) { + case 0x00: LOG("Write: general purpose IO"); break; + case 0x01: LOG("Write: active edge"); break; + case 0x02: LOG("Write: data direction"); break; + case 0x03: LOG("Write: interrupt enable A"); break; + case 0x04: LOG("Write: interrupt enable B"); break; + case 0x05: LOG("Write: interrupt pending A"); break; + case 0x06: LOG("Write: interrupt pending B"); break; + case 0x07: LOG("Write: interrupt in-service A"); break; + case 0x08: LOG("Write: interrupt in-service B"); break; + case 0x09: LOG("Write: interrupt mask A"); break; + case 0x0a: LOG("Write: interrupt mask B"); break; + case 0x0b: LOG("Write: vector"); break; + case 0x0c: + case 0x0d: { + const auto timer = address - 0xc; + const bool reset = value & 0x10; + switch(value & 0xf) { + case 0x0: set_timer_mode(timer, TimerMode::Stopped, 0, reset); break; + case 0x1: set_timer_mode(timer, TimerMode::Delay, 4, reset); break; + case 0x2: set_timer_mode(timer, TimerMode::Delay, 10, reset); break; + case 0x3: set_timer_mode(timer, TimerMode::Delay, 16, reset); break; + case 0x4: set_timer_mode(timer, TimerMode::Delay, 50, reset); break; + case 0x5: set_timer_mode(timer, TimerMode::Delay, 64, reset); break; + case 0x6: set_timer_mode(timer, TimerMode::Delay, 100, reset); break; + case 0x7: set_timer_mode(timer, TimerMode::Delay, 200, reset); break; + case 0x8: set_timer_mode(timer, TimerMode::EventCount, 0, reset); break; + case 0x9: set_timer_mode(timer, TimerMode::PulseWidth, 4, reset); break; + case 0xa: set_timer_mode(timer, TimerMode::PulseWidth, 10, reset); break; + case 0xb: set_timer_mode(timer, TimerMode::PulseWidth, 16, reset); break; + case 0xc: set_timer_mode(timer, TimerMode::PulseWidth, 50, reset); break; + case 0xd: set_timer_mode(timer, TimerMode::PulseWidth, 64, reset); break; + case 0xe: set_timer_mode(timer, TimerMode::PulseWidth, 100, reset); break; + case 0xf: set_timer_mode(timer, TimerMode::PulseWidth, 200, reset); break; + } + } break; + case 0x0e: + switch(value & 7) { + case 0: set_timer_mode(3, TimerMode::Stopped, 0, false); break; + case 1: set_timer_mode(3, TimerMode::Delay, 4, false); break; + case 2: set_timer_mode(3, TimerMode::Delay, 10, false); break; + case 3: set_timer_mode(3, TimerMode::Delay, 16, false); break; + case 4: set_timer_mode(3, TimerMode::Delay, 50, false); break; + case 5: set_timer_mode(3, TimerMode::Delay, 64, false); break; + case 6: set_timer_mode(3, TimerMode::Delay, 100, false); break; + case 7: set_timer_mode(3, TimerMode::Delay, 200, false); break; + } + switch((value >> 4) & 7) { + case 0: set_timer_mode(2, TimerMode::Stopped, 0, false); break; + case 1: set_timer_mode(2, TimerMode::Delay, 4, false); break; + case 2: set_timer_mode(2, TimerMode::Delay, 10, false); break; + case 3: set_timer_mode(2, TimerMode::Delay, 16, false); break; + case 4: set_timer_mode(2, TimerMode::Delay, 50, false); break; + case 5: set_timer_mode(2, TimerMode::Delay, 64, false); break; + case 6: set_timer_mode(2, TimerMode::Delay, 100, false); break; + case 7: set_timer_mode(2, TimerMode::Delay, 200, false); break; + } + break; + case 0x0f: case 0x10: case 0x11: case 0x12: + set_timer_data(address - 0xf, value); + break; + case 0x13: LOG("Write: sync character generator"); break; + case 0x14: LOG("Write: USART control"); break; + case 0x15: LOG("Write: receiver status"); break; + case 0x16: LOG("Write: transmitter status"); break; + case 0x17: LOG("Write: USART data"); break; + } } -void MFP68901::run_for(HalfCycles) { - /* TODO */ +void MFP68901::run_for(HalfCycles time) { + cycles_left_ += time; + + // TODO: this is the stupidest possible implementation. Improve. + int cycles = cycles_left_.flush().as_int(); + while(cycles--) { + for(int c = 0; c < 4; ++c) { + if(timers_[c].mode >= TimerMode::Delay) { + --timers_[c].divisor; + if(!timers_[c].divisor) { + timers_[c].divisor = timers_[c].prescale; + + --timers_[c].value; + if(!timers_[c].value) { + // TODO: interrupt. + } + } + } + } + } } HalfCycles MFP68901::get_next_sequence_point() { return HalfCycles(-1); } + +// MARK: - Timers + +void MFP68901::set_timer_mode(int timer, TimerMode mode, int prescale, bool reset_timer) { + timers_[timer].mode = mode; + timers_[timer].prescale = prescale; + if(reset_timer) { + timers_[timer].divisor = prescale; + timers_[timer].value = timers_[timer].reload_value; + } +} + +void MFP68901::set_timer_data(int timer, uint8_t value) { + if(timers_[timer].mode == TimerMode::Stopped) { + timers_[timer].value = value; + } + timers_[timer].reload_value = value; +} + +uint8_t MFP68901::get_timer_data(int timer) { + return timers_[timer].value; +} diff --git a/Components/68901/MFP68901.hpp b/Components/68901/MFP68901.hpp index fbecb5c0b..d94cc04c5 100644 --- a/Components/68901/MFP68901.hpp +++ b/Components/68901/MFP68901.hpp @@ -22,6 +22,25 @@ class MFP68901 { void run_for(HalfCycles); HalfCycles get_next_sequence_point(); + + private: + // MARK: - Timers + enum class TimerMode { + Stopped, EventCount, Delay, PulseWidth + }; + void set_timer_mode(int timer, TimerMode, int prescale, bool reset_timer); + void set_timer_data(int timer, uint8_t); + uint8_t get_timer_data(int timer); + + struct Timer { + TimerMode mode = TimerMode::Stopped; + uint8_t value = 0; + uint8_t reload_value = 0; + int prescale = 1; + int divisor = 0; + } timers_[4]; + + HalfCycles cycles_left_; }; } diff --git a/Machines/AtariST/AtariST.cpp b/Machines/AtariST/AtariST.cpp index b7bef6755..042261312 100644 --- a/Machines/AtariST/AtariST.cpp +++ b/Machines/AtariST/AtariST.cpp @@ -96,7 +96,7 @@ class ConcreteMachine: /* TODO: DTack, bus error, VPA. */ auto address = cycle.word_address(); - if(cycle.data_select_active()) printf("%c %06x\n", (cycle.operation & Microcycle::Read) ? 'r' : 'w', *cycle.address & 0xffffff); +// if(cycle.data_select_active()) printf("%c %06x\n", (cycle.operation & Microcycle::Read) ? 'r' : 'w', *cycle.address & 0xffffff); uint16_t *memory; switch(memory_map_[address >> 15]) { case BusDevice::MostlyRAM: @@ -222,6 +222,19 @@ class ConcreteMachine: mfp_->write(int(address), cycle.value->halves.high); } } + + /* + Atari ST GPIP bits: + + GPIP 7: monochrome monitor detect + GPIP 6: RS-232 ring indicator + GPIP 5: FD/HD interrupt + GPIP 4: keyboard/MIDI interrupt + GPIP 3: unused + GPIP 2: RS-232 clear to send + GPIP 1: RS-232 carrier detect + GPIP 0: centronics busy + */ break; } return HalfCycles(0); From 7722596a3b133038db0be7c773af5e0abebb1759 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 8 Oct 2019 21:18:08 -0400 Subject: [PATCH 12/39] Makes a first attempt to box out the ST display area. --- Machines/AtariST/AtariST.cpp | 1 + Machines/AtariST/Video.cpp | 102 ++++++++++++++++++++++++++++++++++- Machines/AtariST/Video.hpp | 19 +++++-- 3 files changed, 118 insertions(+), 4 deletions(-) diff --git a/Machines/AtariST/AtariST.cpp b/Machines/AtariST/AtariST.cpp index 042261312..68303d23c 100644 --- a/Machines/AtariST/AtariST.cpp +++ b/Machines/AtariST/AtariST.cpp @@ -267,6 +267,7 @@ class ConcreteMachine: void flush() { audio_queue_.perform(); + video_.flush(); } private: diff --git a/Machines/AtariST/Video.cpp b/Machines/AtariST/Video.cpp index 5387799fd..bad48c124 100644 --- a/Machines/AtariST/Video.cpp +++ b/Machines/AtariST/Video.cpp @@ -8,10 +8,47 @@ #include "Video.hpp" +#include + using namespace Atari::ST; +namespace { + +struct ModeParams { + const int lines_per_frame; + + const int first_video_line; + const int final_video_line; + + const int line_length; + + const int end_of_blank; + + const int start_of_display_enable; + const int end_of_display_enable; + + const int start_of_output; + const int end_of_output; + + const int start_of_blank; + + const int start_of_hsync; + const int end_of_hsync; +} modes[3] = { + {313, 56, 256, 1024, 64, 116, 116+640, 116+48, 116+48+640, 904, 928, 1008 }, + {}, + {} +}; + +const ModeParams &mode_params_for_mode() { + // TODO: rest of potential combinations, and accept mode as a paramter. + return modes[0]; +} + +} + Video::Video() : - crt_(512, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4) { + crt_(1024, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4) { } void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) { @@ -19,4 +56,67 @@ void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) { } void Video::run_for(HalfCycles duration) { + int integer_duration = duration.as_int(); + const auto mode_params = mode_params_for_mode(); + +#define Period(lower, upper, type) \ + if(x >= lower && x < upper) { \ + const auto target = std::min(upper, final_x); \ + type(target - x); \ + x = target; \ + } + + while(integer_duration) { + const int final_x = std::min(x + integer_duration, mode_params.line_length); + integer_duration -= (final_x - x); + + if(y >= mode_params.first_video_line && y < mode_params.final_video_line) { + // TODO: Prior to output: collect all necessary data, obeying start_of_display_enable and end_of_display_enable. + + Period(0, mode_params.end_of_blank, crt_.output_blank); + Period(mode_params.end_of_blank, mode_params.start_of_output, output_border); + + if(x >= mode_params.start_of_output && x < mode_params.end_of_output) { + const auto target = std::min(mode_params.end_of_output, final_x); + x = target; + + if(x == mode_params.end_of_output) { + uint16_t *colour_pointer = reinterpret_cast(crt_.begin_data(1)); + if(colour_pointer) *colour_pointer = 0xffff; + crt_.output_level(mode_params.end_of_output - mode_params.start_of_output); + } + } + + Period(mode_params.end_of_output, mode_params.start_of_blank, output_border); + Period(mode_params.start_of_blank, mode_params.start_of_hsync, crt_.output_blank); + Period(mode_params.start_of_hsync, mode_params.start_of_blank, crt_.output_sync); + Period(mode_params.start_of_blank, mode_params.line_length, crt_.output_blank); + } else { + // Hard code the first three lines as vertical sync. + if(y < 3) { + Period(0, mode_params.start_of_hsync, crt_.output_sync); + Period(mode_params.start_of_hsync, mode_params.end_of_hsync, crt_.output_blank); + Period(mode_params.end_of_hsync, mode_params.line_length, crt_.output_sync); + } else { + Period(0, mode_params.end_of_blank, crt_.output_blank); + Period(mode_params.end_of_blank, mode_params.start_of_blank, output_border); + Period(mode_params.start_of_blank, mode_params.start_of_hsync, crt_.output_blank); + Period(mode_params.start_of_hsync, mode_params.start_of_blank, crt_.output_sync); + Period(mode_params.start_of_blank, mode_params.line_length, crt_.output_blank); + } + } + + if(x == mode_params.line_length) { + x = 0; + y = (y + 1) % mode_params.lines_per_frame; + } + } + +#undef Period +} + +void Video::output_border(int duration) { + uint16_t *colour_pointer = reinterpret_cast(crt_.begin_data(1)); + if(colour_pointer) *colour_pointer = 0x333; + crt_.output_level(duration); } diff --git a/Machines/AtariST/Video.hpp b/Machines/AtariST/Video.hpp index 8a93e5c6b..d27dd5b3b 100644 --- a/Machines/AtariST/Video.hpp +++ b/Machines/AtariST/Video.hpp @@ -6,8 +6,8 @@ // Copyright © 2019 Thomas Harte. All rights reserved. // -#ifndef Video_hpp -#define Video_hpp +#ifndef Atari_ST_Video_hpp +#define Atari_ST_Video_hpp #include "../../Outputs/CRT/CRT.hpp" #include "../../ClockReceiver/ClockReceiver.hpp" @@ -29,11 +29,24 @@ class Video { */ void run_for(HalfCycles duration); + /*! + @returns the number of cycles until there is next a change in the hsync, + vsync or display_enable outputs. + */ + HalfCycles get_next_sequence_point(); + + bool hsync(); + bool vsync(); + bool display_enabled(); + private: Outputs::CRT::CRT crt_; + + int x = 0, y = 0; + void output_border(int duration); }; } } -#endif /* Video_hpp */ +#endif /* Atari_ST_Video_hpp */ From 5d06930df44b3133235521ad7f2568b326023518 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 8 Oct 2019 21:29:17 -0400 Subject: [PATCH 13/39] Corrects sync generation. --- Machines/AtariST/Video.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Machines/AtariST/Video.cpp b/Machines/AtariST/Video.cpp index bad48c124..27d8ea189 100644 --- a/Machines/AtariST/Video.cpp +++ b/Machines/AtariST/Video.cpp @@ -89,8 +89,8 @@ void Video::run_for(HalfCycles duration) { Period(mode_params.end_of_output, mode_params.start_of_blank, output_border); Period(mode_params.start_of_blank, mode_params.start_of_hsync, crt_.output_blank); - Period(mode_params.start_of_hsync, mode_params.start_of_blank, crt_.output_sync); - Period(mode_params.start_of_blank, mode_params.line_length, crt_.output_blank); + Period(mode_params.start_of_hsync, mode_params.end_of_hsync, crt_.output_sync); + Period(mode_params.end_of_hsync, mode_params.line_length, crt_.output_blank); } else { // Hard code the first three lines as vertical sync. if(y < 3) { @@ -101,8 +101,8 @@ void Video::run_for(HalfCycles duration) { Period(0, mode_params.end_of_blank, crt_.output_blank); Period(mode_params.end_of_blank, mode_params.start_of_blank, output_border); Period(mode_params.start_of_blank, mode_params.start_of_hsync, crt_.output_blank); - Period(mode_params.start_of_hsync, mode_params.start_of_blank, crt_.output_sync); - Period(mode_params.start_of_blank, mode_params.line_length, crt_.output_blank); + Period(mode_params.start_of_hsync, mode_params.end_of_hsync, crt_.output_sync); + Period(mode_params.end_of_hsync, mode_params.line_length, crt_.output_blank); } } From dbde8f2ee72f7e13bea95663e31d6a13f1328c18 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 8 Oct 2019 22:29:58 -0400 Subject: [PATCH 14/39] Takes a shot at other display outputs. --- Machines/AtariST/Video.cpp | 57 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/Machines/AtariST/Video.cpp b/Machines/AtariST/Video.cpp index 27d8ea189..9c5e2a2d4 100644 --- a/Machines/AtariST/Video.cpp +++ b/Machines/AtariST/Video.cpp @@ -120,3 +120,60 @@ void Video::output_border(int duration) { if(colour_pointer) *colour_pointer = 0x333; crt_.output_level(duration); } + +bool Video::hsync() { + const auto mode_params = mode_params_for_mode(); + return x >= mode_params.start_of_hsync && x < mode_params.end_of_hsync; +} + +bool Video::vsync() { + return y < 3; +} + +bool Video::display_enabled() { + const auto mode_params = mode_params_for_mode(); + return y >= mode_params.first_video_line && y < mode_params.final_video_line && x >= mode_params.start_of_display_enable && x < mode_params.end_of_display_enable; +} + +HalfCycles Video::get_next_sequence_point() { + // The next hsync transition will occur either this line or the next. + const auto mode_params = mode_params_for_mode(); + HalfCycles cycles_until_hsync; + if(x < mode_params.start_of_hsync) { + cycles_until_hsync = HalfCycles(mode_params.start_of_hsync - x); + } else if(x < mode_params.end_of_hsync) { + cycles_until_hsync = HalfCycles(mode_params.end_of_hsync - x); + } else { + cycles_until_hsync = HalfCycles(mode_params.start_of_hsync + mode_params.line_length - x); + } + + // The next vsync transition depends purely on the current y. + HalfCycles cycles_until_vsync; + if(y < 3) { + cycles_until_vsync = HalfCycles(mode_params.line_length - x + (2 - y)*mode_params.line_length); + } else { + cycles_until_vsync = HalfCycles(mode_params.line_length - x + (mode_params.lines_per_frame - 1 - y)*mode_params.line_length); + } + + // The next display enable transition will occur only in the visible area. + HalfCycles cycles_until_display_enable; + if(display_enabled()) { + cycles_until_display_enable = HalfCycles(mode_params.end_of_display_enable - x); + } else { + const auto horizontal_cycles = mode_params.start_of_display_enable - x; + int vertical_lines = 0; + if(y < mode_params.first_video_line) { + vertical_lines = mode_params.first_video_line - y; + } else if(y >= mode_params.final_video_line ) { + vertical_lines = mode_params.first_video_line + mode_params.lines_per_frame - y; + } + cycles_until_display_enable = HalfCycles(horizontal_cycles + vertical_lines * mode_params.line_length); + } + + // Determine the minimum of the three + if(cycles_until_hsync < cycles_until_vsync && cycles_until_hsync < cycles_until_display_enable) { + return cycles_until_hsync; + } else { + return (cycles_until_vsync < cycles_until_display_enable) ? cycles_until_vsync : cycles_until_display_enable; + } +} From 021d4dbaf10d931c107534e088fcfd8d88a389f9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 8 Oct 2019 23:06:50 -0400 Subject: [PATCH 15/39] Makes an attempt at tracking video sequence points. --- Machines/AtariST/AtariST.cpp | 12 +++++++++++- Machines/AtariST/Video.cpp | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Machines/AtariST/AtariST.cpp b/Machines/AtariST/AtariST.cpp index 68303d23c..cfb29d714 100644 --- a/Machines/AtariST/AtariST.cpp +++ b/Machines/AtariST/AtariST.cpp @@ -272,9 +272,18 @@ class ConcreteMachine: private: forceinline void advance_time(HalfCycles length) { - video_ += length; cycles_since_audio_update_ += length; mfp_ += length; + + while(length >= cycles_until_video_event_) { + length -= cycles_until_video_event_; + video_ += cycles_until_video_event_; + cycles_until_video_event_ = video_->get_next_sequence_point(); + + // TODO: push v/hsync/display_enable elsewhere. + } + cycles_until_video_event_ -= length; + video_ += length; } void update_audio() { @@ -284,6 +293,7 @@ class ConcreteMachine: CPU::MC68000::Processor mc68000_; JustInTimeActor video_; JustInTimeActor mfp_; + HalfCycles cycles_until_video_event_; Concurrency::DeferringAsyncTaskQueue audio_queue_; GI::AY38910::AY38910 ay_; diff --git a/Machines/AtariST/Video.cpp b/Machines/AtariST/Video.cpp index 9c5e2a2d4..976e8e5c6 100644 --- a/Machines/AtariST/Video.cpp +++ b/Machines/AtariST/Video.cpp @@ -167,6 +167,7 @@ HalfCycles Video::get_next_sequence_point() { } else if(y >= mode_params.final_video_line ) { vertical_lines = mode_params.first_video_line + mode_params.lines_per_frame - y; } + if(horizontal_cycles < 0) ++vertical_lines; cycles_until_display_enable = HalfCycles(horizontal_cycles + vertical_lines * mode_params.line_length); } From f88e1b1373ffd7906aa37463f42a2196e30203dc Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 9 Oct 2019 23:01:11 -0400 Subject: [PATCH 16/39] Adds enough logic to advance to an ACIA access error. --- Components/68901/MFP68901.cpp | 22 ++++++++++++++----- Components/68901/MFP68901.hpp | 4 ++++ Machines/AtariST/AtariST.cpp | 41 +++++++++++++++++++++++++++++++---- Machines/AtariST/Video.cpp | 30 ++++++++++++++++++++++++- Machines/AtariST/Video.hpp | 5 +++++ 5 files changed, 92 insertions(+), 10 deletions(-) diff --git a/Components/68901/MFP68901.cpp b/Components/68901/MFP68901.cpp index 5466ad959..7d17b4dcb 100644 --- a/Components/68901/MFP68901.cpp +++ b/Components/68901/MFP68901.cpp @@ -123,11 +123,7 @@ void MFP68901::run_for(HalfCycles time) { --timers_[c].divisor; if(!timers_[c].divisor) { timers_[c].divisor = timers_[c].prescale; - - --timers_[c].value; - if(!timers_[c].value) { - // TODO: interrupt. - } + decrement_timer(c); } } } @@ -159,3 +155,19 @@ void MFP68901::set_timer_data(int timer, uint8_t value) { uint8_t MFP68901::get_timer_data(int timer) { return timers_[timer].value; } + +void MFP68901::set_timer_event_input(int channel, bool value) { + if(timers_[channel].event_input == value) return; + + timers_[channel].event_input = value; + if(timers_[channel].mode == TimerMode::EventCount && !value) { /* TODO: which edge is counted? "as defined by the associated Interrupt Channel’s edge bit"? */ + decrement_timer(channel); + } +} + +void MFP68901::decrement_timer(int timer) { + --timers_[timer].value; + if(!timers_[timer].value) { + // TODO: interrupt. Reload, possibly. + } +} diff --git a/Components/68901/MFP68901.hpp b/Components/68901/MFP68901.hpp index d94cc04c5..13d5f0b1c 100644 --- a/Components/68901/MFP68901.hpp +++ b/Components/68901/MFP68901.hpp @@ -23,6 +23,8 @@ class MFP68901 { void run_for(HalfCycles); HalfCycles get_next_sequence_point(); + void set_timer_event_input(int channel, bool value); + private: // MARK: - Timers enum class TimerMode { @@ -31,6 +33,7 @@ class MFP68901 { void set_timer_mode(int timer, TimerMode, int prescale, bool reset_timer); void set_timer_data(int timer, uint8_t); uint8_t get_timer_data(int timer); + void decrement_timer(int timer); struct Timer { TimerMode mode = TimerMode::Stopped; @@ -38,6 +41,7 @@ class MFP68901 { uint8_t reload_value = 0; int prescale = 1; int divisor = 0; + bool event_input = false; } timers_[4]; HalfCycles cycles_left_; diff --git a/Machines/AtariST/AtariST.cpp b/Machines/AtariST/AtariST.cpp index cfb29d714..d2bec7bbc 100644 --- a/Machines/AtariST/AtariST.cpp +++ b/Machines/AtariST/AtariST.cpp @@ -115,6 +115,8 @@ class ConcreteMachine: address %= rom_.size(); break; + case BusDevice::Unassigned: + // TODO: figure out the rules about bus errors. case BusDevice::Cartridge: /* TOS 1.0 appears to attempt to read from the catridge before it has setup @@ -131,10 +133,6 @@ class ConcreteMachine: } return HalfCycles(0); - case BusDevice::Unassigned: - assert(false); - return HalfCycles(0); - case BusDevice::IO: switch(address) { default: @@ -236,6 +234,39 @@ class ConcreteMachine: GPIP 0: centronics busy */ break; + + // Video controls. + case 0x7fc100: case 0x7fc101: case 0x7fc102: case 0x7fc103: + case 0x7fc104: case 0x7fc105: case 0x7fc106: case 0x7fc107: + case 0x7fc108: case 0x7fc109: case 0x7fc10a: case 0x7fc10b: + case 0x7fc10c: case 0x7fc10d: case 0x7fc10e: case 0x7fc10f: + case 0x7fc110: case 0x7fc111: case 0x7fc112: case 0x7fc113: + case 0x7fc114: case 0x7fc115: case 0x7fc116: case 0x7fc117: + case 0x7fc118: case 0x7fc119: case 0x7fc11a: case 0x7fc11b: + case 0x7fc11c: case 0x7fc11d: case 0x7fc11e: case 0x7fc11f: + case 0x7fc120: case 0x7fc121: case 0x7fc122: case 0x7fc123: + case 0x7fc124: case 0x7fc125: case 0x7fc126: case 0x7fc127: + case 0x7fc128: case 0x7fc129: case 0x7fc12a: case 0x7fc12b: + case 0x7fc12c: case 0x7fc12d: case 0x7fc12e: case 0x7fc12f: + case 0x7fc130: case 0x7fc131: + if(!cycle.data_select_active()) return HalfCycles(0); + + if(cycle.operation & Microcycle::Read) { + const uint8_t value = video_->read(int(address)); + if(cycle.operation & Microcycle::SelectByte) { + cycle.value->halves.low = value; + } else { + cycle.value->halves.high = value; + cycle.value->halves.low = 0xff; + } + } else { + if(cycle.operation & Microcycle::SelectByte) { + video_->write(int(address), cycle.value->halves.low); + } else { + video_->write(int(address), cycle.value->halves.high); + } + } + break; } return HalfCycles(0); } @@ -281,6 +312,8 @@ class ConcreteMachine: cycles_until_video_event_ = video_->get_next_sequence_point(); // TODO: push v/hsync/display_enable elsewhere. + mfp_->set_timer_event_input(1, video_->display_enabled()); +// printf("%c%c%c\n", video_->display_enabled() ? 'e' : '-', video_->hsync() ? 'h' : '-', video_->vsync() ? 'v' : '-'); } cycles_until_video_event_ -= length; video_ += length; diff --git a/Machines/AtariST/Video.cpp b/Machines/AtariST/Video.cpp index 976e8e5c6..99bf8efc1 100644 --- a/Machines/AtariST/Video.cpp +++ b/Machines/AtariST/Video.cpp @@ -8,6 +8,8 @@ #include "Video.hpp" +#include "../../Outputs/Log.hpp" + #include using namespace Atari::ST; @@ -66,6 +68,9 @@ void Video::run_for(HalfCycles duration) { x = target; \ } + // TODO: the below is **way off**. The real hardware does what you'd expect with ongoing state and + // exact equality tests. Fixes to come. + while(integer_duration) { const int final_x = std::min(x + integer_duration, mode_params.line_length); integer_duration -= (final_x - x); @@ -117,7 +122,7 @@ void Video::run_for(HalfCycles duration) { void Video::output_border(int duration) { uint16_t *colour_pointer = reinterpret_cast(crt_.begin_data(1)); - if(colour_pointer) *colour_pointer = 0x333; + if(colour_pointer) *colour_pointer = palette_[0]; crt_.output_level(duration); } @@ -178,3 +183,26 @@ HalfCycles Video::get_next_sequence_point() { return (cycles_until_vsync < cycles_until_display_enable) ? cycles_until_vsync : cycles_until_display_enable; } } + +// MARK: - IO dispatch + +uint8_t Video::read(int address) { + LOG("[Video] read " << (address & 0x3f)); + return 0xff; +} + +void Video::write(int address, uint8_t value) { + LOG("[Video] write " << PADHEX(2) << int(value) << " to " << PADHEX(2) << (address & 0x3f)); + address &= 0x3f; + switch(address) { + default: break; + + // Palette. + case 0x20: case 0x21: case 0x22: case 0x23: + case 0x24: case 0x25: case 0x26: case 0x27: + case 0x28: case 0x29: case 0x2a: case 0x2b: + case 0x2c: case 0x2d: case 0x2e: case 0x2f: + palette_[address - 0x20] = uint16_t((value & 0x777) << 5); + break; + } +} diff --git a/Machines/AtariST/Video.hpp b/Machines/AtariST/Video.hpp index d27dd5b3b..e825ff1da 100644 --- a/Machines/AtariST/Video.hpp +++ b/Machines/AtariST/Video.hpp @@ -39,9 +39,14 @@ class Video { bool vsync(); bool display_enabled(); + uint8_t read(int address); + void write(int address, uint8_t value); + private: Outputs::CRT::CRT crt_; + uint16_t palette_[16]; + int x = 0, y = 0; void output_border(int duration); }; From d7ce2c26e8b48287bed3b0313488c85a45badf24 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Oct 2019 20:54:29 -0400 Subject: [PATCH 17/39] Adds an empty shell for the ACIA. --- Components/6850/6850.cpp | 21 +++++++++++++ Components/6850/6850.hpp | 29 +++++++++++++++++ Machines/AtariST/AtariST.cpp | 31 ++++++++++++++++++- .../Clock Signal.xcodeproj/project.pbxproj | 16 ++++++++++ 4 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 Components/6850/6850.cpp create mode 100644 Components/6850/6850.hpp diff --git a/Components/6850/6850.cpp b/Components/6850/6850.cpp new file mode 100644 index 000000000..dfdfd9a4c --- /dev/null +++ b/Components/6850/6850.cpp @@ -0,0 +1,21 @@ +// +// 6850.cpp +// Clock Signal +// +// Created by Thomas Harte on 10/10/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#include "6850.hpp" + +using namespace Motorola::ACIA; + +uint8_t ACIA::read(int address) { + return 0xff; +} + +void ACIA::write(int address, uint8_t value) { +} + +void ACIA::run_for(HalfCycles) { +} diff --git a/Components/6850/6850.hpp b/Components/6850/6850.hpp new file mode 100644 index 000000000..a833a906c --- /dev/null +++ b/Components/6850/6850.hpp @@ -0,0 +1,29 @@ +// +// 6850.hpp +// Clock Signal +// +// Created by Thomas Harte on 10/10/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef Motorola_ACIA_6850_hpp +#define Motorola_ACIA_6850_hpp + +#include +#include "../../ClockReceiver/ClockReceiver.hpp" + +namespace Motorola { +namespace ACIA { + +class ACIA { + public: + uint8_t read(int address); + void write(int address, uint8_t value); + + void run_for(HalfCycles); +}; + +} +} + +#endif /* Motorola_ACIA_6850_hpp */ diff --git a/Machines/AtariST/AtariST.cpp b/Machines/AtariST/AtariST.cpp index d2bec7bbc..9b839d7ea 100644 --- a/Machines/AtariST/AtariST.cpp +++ b/Machines/AtariST/AtariST.cpp @@ -14,6 +14,7 @@ #include "../../Components/AY38910/AY38910.hpp" #include "../../Components/68901/MFP68901.hpp" +#include "../../Components/6850/6850.hpp" #include "Video.hpp" #include "../../ClockReceiver/JustInTime.hpp" @@ -267,6 +268,31 @@ class ConcreteMachine: } } break; + + // ACIAs. + case 0x7ffe00: case 0x7ffe01: case 0x7ffe02: case 0x7ffe03: { + // Set VPA. + mc68000_.set_is_peripheral_address(!cycle.data_select_active()); + if(!cycle.data_select_active()) return HalfCycles(0); + + const auto acia_ = (address < 0x7ffe02) ? &keyboard_acia_ : &midi_acia_; + + if(cycle.operation & Microcycle::Read) { + const uint8_t value = (*acia_)->read(int(address)); + if(cycle.operation & Microcycle::SelectByte) { + cycle.value->halves.low = value; + } else { + cycle.value->halves.high = value; + cycle.value->halves.low = 0xff; + } + } else { + if(cycle.operation & Microcycle::SelectByte) { + (*acia_)->write(int(address), cycle.value->halves.low); + } else { + (*acia_)->write(int(address), cycle.value->halves.high); + } + } + } break; } return HalfCycles(0); } @@ -325,9 +351,12 @@ class ConcreteMachine: CPU::MC68000::Processor mc68000_; JustInTimeActor video_; - JustInTimeActor mfp_; HalfCycles cycles_until_video_event_; + JustInTimeActor mfp_; + JustInTimeActor keyboard_acia_; + JustInTimeActor midi_acia_; + Concurrency::DeferringAsyncTaskQueue audio_queue_; GI::AY38910::AY38910 ay_; Outputs::Speaker::LowpassSpeaker speaker_; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index e8ab56003..5aca36465 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -616,6 +616,8 @@ 4BB299F81B587D8400A49093 /* txsn in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EC1B587D8400A49093 /* txsn */; }; 4BB299F91B587D8400A49093 /* tyan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298ED1B587D8400A49093 /* tyan */; }; 4BB2A9AF1E13367E001A5C23 /* CRCTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */; }; + 4BB307BB235001C300457D33 /* 6850.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB307BA235001C300457D33 /* 6850.cpp */; }; + 4BB307BC235001C300457D33 /* 6850.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB307BA235001C300457D33 /* 6850.cpp */; }; 4BB4BFAD22A33DE50069048D /* DriveSpeedAccumulator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB4BFAC22A33DE50069048D /* DriveSpeedAccumulator.cpp */; }; 4BB4BFB022A42F290069048D /* MacintoshIMG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB4BFAE22A42F290069048D /* MacintoshIMG.cpp */; }; 4BB4BFB922A4372F0069048D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB4BFB822A4372E0069048D /* StaticAnalyser.cpp */; }; @@ -1415,6 +1417,8 @@ 4BB298EC1B587D8400A49093 /* txsn */ = {isa = PBXFileReference; lastKnownFileType = file; path = txsn; sourceTree = ""; }; 4BB298ED1B587D8400A49093 /* tyan */ = {isa = PBXFileReference; lastKnownFileType = file; path = tyan; sourceTree = ""; }; 4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CRCTests.mm; sourceTree = ""; }; + 4BB307B9235001C300457D33 /* 6850.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6850.hpp; sourceTree = ""; }; + 4BB307BA235001C300457D33 /* 6850.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6850.cpp; sourceTree = ""; }; 4BB4BFAA22A300710069048D /* DeferredAudio.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DeferredAudio.hpp; sourceTree = ""; }; 4BB4BFAB22A33D710069048D /* DriveSpeedAccumulator.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DriveSpeedAccumulator.hpp; sourceTree = ""; }; 4BB4BFAC22A33DE50069048D /* DriveSpeedAccumulator.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DriveSpeedAccumulator.cpp; sourceTree = ""; }; @@ -2956,6 +2960,15 @@ path = "Wolfgang Lorenz 6502 test suite"; sourceTree = ""; }; + 4BB307B8235001C300457D33 /* 6850 */ = { + isa = PBXGroup; + children = ( + 4BB307B9235001C300457D33 /* 6850.hpp */, + 4BB307BA235001C300457D33 /* 6850.cpp */, + ); + path = 6850; + sourceTree = ""; + }; 4BB4BFB622A4372E0069048D /* Macintosh */ = { isa = PBXGroup; children = ( @@ -3243,6 +3256,7 @@ 4B1E85791D174DEC001EF87D /* 6532 */, 4BC9DF4C1D04691600F44158 /* 6560 */, 4BE845221F2FF7F400A5EA22 /* 6845 */, + 4BB307B8235001C300457D33 /* 6850 */, 4BD9137C1F3115AC009BCF85 /* 8255 */, 4BBC951F1F368D87008F4C34 /* 8272 */, 4BB244D222AABAF500BE20E5 /* 8530 */, @@ -4088,6 +4102,7 @@ 4B055A8E1FAE85920060FFFF /* BestEffortUpdater.cpp in Sources */, 4B055AB01FAE86070060FFFF /* PulseQueuedTape.cpp in Sources */, 4B055AAC1FAE85FD0060FFFF /* PCMSegment.cpp in Sources */, + 4BB307BC235001C300457D33 /* 6850.cpp in Sources */, 4B055AB31FAE860F0060FFFF /* CSW.cpp in Sources */, 4B89451D201967B4007DE474 /* Disk.cpp in Sources */, 4BDACBED22FFA5D20045EF7E /* ncr5380.cpp in Sources */, @@ -4376,6 +4391,7 @@ 4B54C0C81F8D91E50050900F /* Keyboard.cpp in Sources */, 4B79A5011FC913C900EEDAD5 /* MSX.cpp in Sources */, 4BEE0A701D72496600532C7B /* PRG.cpp in Sources */, + 4BB307BB235001C300457D33 /* 6850.cpp in Sources */, 4BF437EE209D0F7E008CBD6B /* SegmentParser.cpp in Sources */, 4BC131762346DE9100E4FF3D /* StaticAnalyser.cpp in Sources */, 4B8334861F5DA3780097E338 /* 6502Storage.cpp in Sources */, From 52e5296544a2144febc9be8d490a3e2b27816fec Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Oct 2019 21:04:41 -0400 Subject: [PATCH 18/39] Provides a token something where DMA should be. --- Machines/AtariST/AtariST.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Machines/AtariST/AtariST.cpp b/Machines/AtariST/AtariST.cpp index 9b839d7ea..50bdfdecc 100644 --- a/Machines/AtariST/AtariST.cpp +++ b/Machines/AtariST/AtariST.cpp @@ -293,6 +293,19 @@ class ConcreteMachine: } } } break; + + // DMA. + case 0x7fc302: case 0x7fc303: case 0x7fc304: case 0x7fc305: case 0x7fc306: + if(cycle.operation & Microcycle::Read) { + const uint8_t value = 0; + if(cycle.operation & Microcycle::SelectByte) { + cycle.value->halves.low = value; + } else { + cycle.value->halves.high = value; + cycle.value->halves.low = 0xff; + } + } + break; } return HalfCycles(0); } From 2581b520af85f36c7d7d80b1deb00836b5f1a73f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Oct 2019 22:45:03 -0400 Subject: [PATCH 19/39] Adds the option to affix a standard prefix to log messages. --- Outputs/Log.hpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Outputs/Log.hpp b/Outputs/Log.hpp index 88beab326..8d3ed3f75 100644 --- a/Outputs/Log.hpp +++ b/Outputs/Log.hpp @@ -26,6 +26,16 @@ #define PADHEX(n) std::hex << std::setfill('0') << std::setw(n) #define PADDEC(n) std::dec << std::setfill('0') << std::setw(n) +#ifdef LOG_PREFIX + +#define LOG(x) std::cout << LOG_PREFIX << x << std::endl +#define LOGNBR(x) std::cout << LOG_PREFIX << x + +#define ERROR(x) std::cerr << LOG_PREFIX << x << std::endl +#define ERRORNBR(x) std::cerr << LOG_PREFIX << x + +#else + #define LOG(x) std::cout << x << std::endl #define LOGNBR(x) std::cout << x @@ -34,5 +44,7 @@ #endif +#endif + #endif /* Log_h */ From c5ebf7535116849bc1e09a715750938a317ea6f6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Oct 2019 22:46:58 -0400 Subject: [PATCH 20/39] Attempts to start producing actual video. --- Components/68901/MFP68901.cpp | 3 +- Machines/AtariST/AtariST.cpp | 1 + Machines/AtariST/Video.cpp | 54 ++++++++++++++++++++++++++++++++--- Machines/AtariST/Video.hpp | 8 ++++++ 4 files changed, 61 insertions(+), 5 deletions(-) diff --git a/Components/68901/MFP68901.cpp b/Components/68901/MFP68901.cpp index 7d17b4dcb..dd2b2f39a 100644 --- a/Components/68901/MFP68901.cpp +++ b/Components/68901/MFP68901.cpp @@ -8,6 +8,7 @@ #include "MFP68901.hpp" +#define LOG_PREFIX "[MFP] " #include "../../Outputs/Log.hpp" using namespace Motorola::MFP68901; @@ -38,7 +39,7 @@ uint8_t MFP68901::read(int address) { case 0x16: LOG("Read: transmitter status"); break; case 0x17: LOG("Read: USART data"); break; } - return 0xff; + return 0x00; } void MFP68901::write(int address, uint8_t value) { diff --git a/Machines/AtariST/AtariST.cpp b/Machines/AtariST/AtariST.cpp index 50bdfdecc..4485366a4 100644 --- a/Machines/AtariST/AtariST.cpp +++ b/Machines/AtariST/AtariST.cpp @@ -45,6 +45,7 @@ class ConcreteMachine: speaker_.set_input_rate(CLOCK_RATE / 4); ram_.resize(512 * 512); + video_->set_ram(ram_.data()); Memory::Fuzz(ram_); std::vector rom_descriptions = { diff --git a/Machines/AtariST/Video.cpp b/Machines/AtariST/Video.cpp index 99bf8efc1..aea6f8cd4 100644 --- a/Machines/AtariST/Video.cpp +++ b/Machines/AtariST/Video.cpp @@ -53,6 +53,10 @@ Video::Video() : crt_(1024, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4) { } +void Video::set_ram(uint16_t *ram) { + ram_ = ram; +} + void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) { crt_.set_scan_target(scan_target); } @@ -82,13 +86,42 @@ void Video::run_for(HalfCycles duration) { Period(mode_params.end_of_blank, mode_params.start_of_output, output_border); if(x >= mode_params.start_of_output && x < mode_params.end_of_output) { + if(x == mode_params.start_of_output) { + // TODO: resolutions other than 320. + pixel_pointer_ = reinterpret_cast(crt_.begin_data(320)); + } + const auto target = std::min(mode_params.end_of_output, final_x); - x = target; + while(x < target) { + if(!(x&31) && pixel_pointer_) { + uint16_t source[4] = { + ram_[current_address_ + 0], + ram_[current_address_ + 1], + ram_[current_address_ + 2], + ram_[current_address_ + 3], + }; + current_address_ += 4; + + for(int c = 0; c < 16; ++c) { + *pixel_pointer_ = palette_[ + ((source[0] >> 12) & 0x8) | + ((source[1] >> 13) & 0x4) | + ((source[2] >> 14) & 0x2) | + ((source[3] >> 15) & 0x1) + ]; + source[0] <<= 1; + source[1] <<= 1; + source[2] <<= 1; + source[3] <<= 1; + ++pixel_pointer_; + } + } + ++x; + } if(x == mode_params.end_of_output) { - uint16_t *colour_pointer = reinterpret_cast(crt_.begin_data(1)); - if(colour_pointer) *colour_pointer = 0xffff; - crt_.output_level(mode_params.end_of_output - mode_params.start_of_output); + crt_.output_data(mode_params.end_of_output - mode_params.start_of_output, 320); + pixel_pointer_ = nullptr; } } @@ -114,6 +147,7 @@ void Video::run_for(HalfCycles duration) { if(x == mode_params.line_length) { x = 0; y = (y + 1) % mode_params.lines_per_frame; + if(!y) current_address_ = base_address_; } } @@ -188,6 +222,14 @@ HalfCycles Video::get_next_sequence_point() { uint8_t Video::read(int address) { LOG("[Video] read " << (address & 0x3f)); + address &= 0x3f; + switch(address) { + case 0x00: return uint8_t(base_address_ >> 16); + case 0x01: return uint8_t(base_address_ >> 8); + case 0x02: return uint8_t(current_address_ >> 16); + case 0x03: return uint8_t(current_address_ >> 8); + case 0x04: return uint8_t(current_address_); + } return 0xff; } @@ -197,6 +239,10 @@ void Video::write(int address, uint8_t value) { switch(address) { default: break; + // Start address. + case 0x00: base_address_ = (base_address_ & 0x00ffff) | (value << 16); break; + case 0x01: base_address_ = (base_address_ & 0xff00ff) | (value << 8); break; + // Palette. case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: diff --git a/Machines/AtariST/Video.hpp b/Machines/AtariST/Video.hpp index e825ff1da..feecc1ff3 100644 --- a/Machines/AtariST/Video.hpp +++ b/Machines/AtariST/Video.hpp @@ -39,6 +39,8 @@ class Video { bool vsync(); bool display_enabled(); + void set_ram(uint16_t *); + uint8_t read(int address); void write(int address, uint8_t value); @@ -46,6 +48,12 @@ class Video { Outputs::CRT::CRT crt_; uint16_t palette_[16]; + int base_address_ = 0; + int current_address_ = 0; + + uint16_t *ram_; + uint16_t line_buffer_[256]; + uint16_t *pixel_pointer_; int x = 0, y = 0; void output_border(int duration); From fda99d9c5fbd9a53972ad3f3b6236593a08a2867 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 10 Oct 2019 23:29:46 -0400 Subject: [PATCH 21/39] Ensures all 16 data lines reach the video. --- Machines/AtariST/AtariST.cpp | 4 ++-- Machines/AtariST/Video.cpp | 10 ++++++---- Machines/AtariST/Video.hpp | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Machines/AtariST/AtariST.cpp b/Machines/AtariST/AtariST.cpp index 4485366a4..e550d4ac4 100644 --- a/Machines/AtariST/AtariST.cpp +++ b/Machines/AtariST/AtariST.cpp @@ -263,9 +263,9 @@ class ConcreteMachine: } } else { if(cycle.operation & Microcycle::SelectByte) { - video_->write(int(address), cycle.value->halves.low); + video_->write(int(address), uint16_t(cycle.value->halves.low << cycle.byte_shift())); } else { - video_->write(int(address), cycle.value->halves.high); + video_->write(int(address), cycle.value->full); } } break; diff --git a/Machines/AtariST/Video.cpp b/Machines/AtariST/Video.cpp index aea6f8cd4..09dab4b8a 100644 --- a/Machines/AtariST/Video.cpp +++ b/Machines/AtariST/Video.cpp @@ -233,7 +233,7 @@ uint8_t Video::read(int address) { return 0xff; } -void Video::write(int address, uint8_t value) { +void Video::write(int address, uint16_t value) { LOG("[Video] write " << PADHEX(2) << int(value) << " to " << PADHEX(2) << (address & 0x3f)); address &= 0x3f; switch(address) { @@ -247,8 +247,10 @@ void Video::write(int address, uint8_t value) { case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: case 0x2a: case 0x2b: - case 0x2c: case 0x2d: case 0x2e: case 0x2f: - palette_[address - 0x20] = uint16_t((value & 0x777) << 5); - break; + case 0x2c: case 0x2d: case 0x2e: case 0x2f: { + uint8_t *const entry = reinterpret_cast(&palette_[address - 0x20]); + entry[0] = uint8_t((value & 0x700) >> 7); + entry[1] = uint8_t((value & 0x77) << 1); + } break; } } diff --git a/Machines/AtariST/Video.hpp b/Machines/AtariST/Video.hpp index feecc1ff3..7eb4f8be6 100644 --- a/Machines/AtariST/Video.hpp +++ b/Machines/AtariST/Video.hpp @@ -42,7 +42,7 @@ class Video { void set_ram(uint16_t *); uint8_t read(int address); - void write(int address, uint8_t value); + void write(int address, uint16_t value); private: Outputs::CRT::CRT crt_; From cd75978e4e76cf143b5fc3a3178974e06ffed49f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 12 Oct 2019 00:04:02 -0400 Subject: [PATCH 22/39] Nudges 6850 towards coherence. --- Components/6850/6850.cpp | 20 +++++++++++++++++++- Components/6850/6850.hpp | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/Components/6850/6850.cpp b/Components/6850/6850.cpp index dfdfd9a4c..3809dd97e 100644 --- a/Components/6850/6850.cpp +++ b/Components/6850/6850.cpp @@ -8,13 +8,31 @@ #include "6850.hpp" +#define LOG_PREFIX "[6850] " +#include "../../Outputs/Log.hpp" + using namespace Motorola::ACIA; uint8_t ACIA::read(int address) { - return 0xff; + if(address&1) { + LOG("Read from receive register"); + } else { + LOG("Read status"); + return status_; + } + return 0x00; } void ACIA::write(int address, uint8_t value) { + if(address&1) { + LOG("Write to transmit register"); + } else { + if((value&3) == 3) { + LOG("Reset"); + } else { + LOG("Write to control register"); + } + } } void ACIA::run_for(HalfCycles) { diff --git a/Components/6850/6850.hpp b/Components/6850/6850.hpp index a833a906c..b210ca1b8 100644 --- a/Components/6850/6850.hpp +++ b/Components/6850/6850.hpp @@ -17,10 +17,29 @@ namespace ACIA { class ACIA { public: + /*! + Reads from the ACIA. + + Bit 0 of the address is used as the ACIA's register select line — + so even addresses select control/status registers, odd addresses + select transmit/receive data registers. + */ uint8_t read(int address); + + /*! + Writes to the ACIA. + + Bit 0 of the address is used as the ACIA's register select line — + so even addresses select control/status registers, odd addresses + select transmit/receive data registers. + */ void write(int address, uint8_t value); void run_for(HalfCycles); + + private: + int divider_ = 1; + uint8_t status_ = 0x00; }; } From 4bf81d3b90a03ac9e7dd815dfe842fc7fa7a60fb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 12 Oct 2019 18:19:55 -0400 Subject: [PATCH 23/39] Decodes the 6850 control register, and starts working on standardised serial ports. --- Components/6850/6850.cpp | 28 ++++++++ Components/6850/6850.hpp | 8 +++ Components/SerialPort/SerialPort.cpp | 9 +++ Components/SerialPort/SerialPort.hpp | 71 +++++++++++++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 16 +++++ 5 files changed, 132 insertions(+) create mode 100644 Components/SerialPort/SerialPort.cpp create mode 100644 Components/SerialPort/SerialPort.hpp diff --git a/Components/6850/6850.cpp b/Components/6850/6850.cpp index 3809dd97e..5e2571a5c 100644 --- a/Components/6850/6850.cpp +++ b/Components/6850/6850.cpp @@ -30,6 +30,30 @@ void ACIA::write(int address, uint8_t value) { if((value&3) == 3) { LOG("Reset"); } else { + switch(value & 3) { + default: + case 0: divider_ = 1; break; + case 1: divider_ = 16; break; + case 2: divider_ = 64; break; + } + switch((value >> 2) & 7) { + default: + case 0: word_size_ = 7; stop_bits_ = 2; parity_ = Parity::Even; break; + case 1: word_size_ = 7; stop_bits_ = 2; parity_ = Parity::Odd; break; + case 2: word_size_ = 7; stop_bits_ = 1; parity_ = Parity::Even; break; + case 3: word_size_ = 7; stop_bits_ = 1; parity_ = Parity::Odd; break; + case 4: word_size_ = 8; stop_bits_ = 2; parity_ = Parity::None; break; + case 5: word_size_ = 8; stop_bits_ = 1; parity_ = Parity::None; break; + case 6: word_size_ = 8; stop_bits_ = 1; parity_ = Parity::Even; break; + case 7: word_size_ = 8; stop_bits_ = 1; parity_ = Parity::Odd; break; + } + switch((value >> 5) & 3) { + case 0: set_ready_to_transmit(false); transmit_interrupt_enabled_ = false; break; + case 1: set_ready_to_transmit(false); transmit_interrupt_enabled_ = true; break; + case 2: set_ready_to_transmit(true); transmit_interrupt_enabled_ = false; break; + case 3: set_ready_to_transmit(false); transmit_interrupt_enabled_ = false; break; /* TODO: transmit a break level on the transmit output. */ + } + receive_interrupt_enabled_ = value & 0x80; LOG("Write to control register"); } } @@ -37,3 +61,7 @@ void ACIA::write(int address, uint8_t value) { void ACIA::run_for(HalfCycles) { } + +void ACIA::set_ready_to_transmit(bool) { + +} diff --git a/Components/6850/6850.hpp b/Components/6850/6850.hpp index b210ca1b8..df8eafa0e 100644 --- a/Components/6850/6850.hpp +++ b/Components/6850/6850.hpp @@ -40,6 +40,14 @@ class ACIA { private: int divider_ = 1; uint8_t status_ = 0x00; + enum class Parity { + Even, Odd, None + } parity_ = Parity::None; + int word_size_ = 7, stop_bits_ = 2; + bool receive_interrupt_enabled_ = false; + bool transmit_interrupt_enabled_ = false; + + void set_ready_to_transmit(bool); }; } diff --git a/Components/SerialPort/SerialPort.cpp b/Components/SerialPort/SerialPort.cpp new file mode 100644 index 000000000..4927e60ce --- /dev/null +++ b/Components/SerialPort/SerialPort.cpp @@ -0,0 +1,9 @@ +// +// SerialPort.cpp +// Clock Signal +// +// Created by Thomas Harte on 12/10/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#include "SerialPort.hpp" diff --git a/Components/SerialPort/SerialPort.hpp b/Components/SerialPort/SerialPort.hpp new file mode 100644 index 000000000..e78061c25 --- /dev/null +++ b/Components/SerialPort/SerialPort.hpp @@ -0,0 +1,71 @@ +// +// SerialPort.hpp +// Clock Signal +// +// Created by Thomas Harte on 12/10/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef SerialPort_hpp +#define SerialPort_hpp + +namespace Serial { + +/// Signal is an amalgamation of the RS-232-esque signals and those available on the Macintosh +/// and therefore often associated with RS-422. +enum class Signal { + Receive, + Transmit, + ClearToSend, + RequestToSend, + DataCarrierDetect, + OutputHandshake, + InputHandshake +}; + +/*! + @c Line connects a single reader and a single writer, allowing timestamped events to be + published and consumed, potentially with a clock conversion in between. It allows line + levels to be written and read in larger collections. + + It is assumed that the owner of the reader and writer will ensure that the reader will never + get ahead of the writer. If the writer posts events behind the reader they will simply be + given instanteous effect. +*/ +class Line { + public: + void connect_reader(int clock_rate); + void disconnect_reader(); + + void connect_writer(int clock_rate); + void disconnect_writer(); + + /// Sets the line to @c level after @c cycles relative to the writer's + /// clock rate have elapsed from the final event currently posted. + void write(int cycles, bool level); + + /// Enqueues @c count level changes, the first occurring @c cycles + /// after the final event currently posted and each subsequent event + /// occurring @c cycles after the previous. The levels to output are + /// taken from @c levels which is read from lsb to msb. @c cycles is + /// relative to the writer's clock rate. + void write(int cycles, int count, int levels); + + /// Advances the read position by @c cycles relative to the reader's + /// clock rate. + void advance_reader(int cycles); + + /// @returns The instantaneous level of this line at the current read position. + bool read(); +}; + +/*! + Defines an RS-232-esque srial port. +*/ +class Port { + public: +}; + +} + +#endif /* SerialPort_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 5aca36465..fe95f0d47 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -308,6 +308,8 @@ 4B89453D201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894516201967B4007DE474 /* StaticAnalyser.cpp */; }; 4B89453E201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894517201967B4007DE474 /* StaticAnalyser.cpp */; }; 4B89453F201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894517201967B4007DE474 /* StaticAnalyser.cpp */; }; + 4B8F5D962352125200C775ED /* SerialPort.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8F5D942352125200C775ED /* SerialPort.cpp */; }; + 4B8F5D972352125200C775ED /* SerialPort.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8F5D942352125200C775ED /* SerialPort.cpp */; }; 4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */; }; 4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */; }; 4B8FE21D1DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */; }; @@ -1096,6 +1098,8 @@ 4B8D287E1F77207100645199 /* TrackSerialiser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TrackSerialiser.hpp; sourceTree = ""; }; 4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardMachine.hpp; sourceTree = ""; }; 4B8EF6071FE5AF830076CCDD /* LowpassSpeaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LowpassSpeaker.hpp; sourceTree = ""; }; + 4B8F5D942352125200C775ED /* SerialPort.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SerialPort.cpp; sourceTree = ""; }; + 4B8F5D952352125200C775ED /* SerialPort.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SerialPort.hpp; sourceTree = ""; }; 4B8FE2141DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Atari2600Options.xib"; sourceTree = SOURCE_ROOT; }; 4B8FE2161DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/MachineDocument.xib"; sourceTree = SOURCE_ROOT; }; 4B8FE2181DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/QuickLoadCompositeOptions.xib"; sourceTree = SOURCE_ROOT; }; @@ -2629,6 +2633,15 @@ path = Implementation; sourceTree = ""; }; + 4B8F5D932352125200C775ED /* SerialPort */ = { + isa = PBXGroup; + children = ( + 4B8F5D942352125200C775ED /* SerialPort.cpp */, + 4B8F5D952352125200C775ED /* SerialPort.hpp */, + ); + path = SerialPort; + sourceTree = ""; + }; 4B92294922B064FD00A1458F /* QuadratureMouse */ = { isa = PBXGroup; children = ( @@ -3266,6 +3279,7 @@ 4B4A762D1DB1A35C007AAE2E /* AY38910 */, 4B302181208A550100773308 /* DiskII */, 4B4B1A39200198C900A0F866 /* KonamiSCC */, + 4B8F5D932352125200C775ED /* SerialPort */, 4BB0A6582044FD3000FB3688 /* SN76489 */, ); name = Components; @@ -4142,6 +4156,7 @@ 4B8318B022D3E531006DB630 /* AppleII.cpp in Sources */, 4B055AB11FAE86070060FFFF /* Tape.cpp in Sources */, 4BC1317B2346DF2B00E4FF3D /* MSA.cpp in Sources */, + 4B8F5D972352125200C775ED /* SerialPort.cpp in Sources */, 4BFE7B881FC39D8900160B38 /* StandardOptions.cpp in Sources */, 4B894533201967B4007DE474 /* 6502.cpp in Sources */, 4B055AA91FAE85EF0060FFFF /* CommodoreGCR.cpp in Sources */, @@ -4253,6 +4268,7 @@ 4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */, 4B07835A1FC11D10001D12BB /* Configurable.cpp in Sources */, 4B8334951F5E25B60097E338 /* C1540.cpp in Sources */, + 4B8F5D962352125200C775ED /* SerialPort.cpp in Sources */, 4B89453C201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4B595FAD2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */, 4B1497921EE4B5A800CE2596 /* ZX8081.cpp in Sources */, From 8b50a7d6e3373f8d31b9c547a68b715b0536ee3b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 12 Oct 2019 23:14:29 -0400 Subject: [PATCH 24/39] Attempts mostly to implement 6850 output. --- Components/6850/6850.cpp | 100 ++++++++++++++++++++++----- Components/6850/6850.hpp | 22 ++++-- Components/SerialPort/SerialPort.cpp | 65 +++++++++++++++++ Components/SerialPort/SerialPort.hpp | 56 ++++++++------- Machines/AtariST/AtariST.cpp | 2 + 5 files changed, 197 insertions(+), 48 deletions(-) diff --git a/Components/6850/6850.cpp b/Components/6850/6850.cpp index 5e2571a5c..f86989c5d 100644 --- a/Components/6850/6850.cpp +++ b/Components/6850/6850.cpp @@ -13,22 +13,39 @@ using namespace Motorola::ACIA; +ACIA::ACIA() {} + uint8_t ACIA::read(int address) { if(address&1) { LOG("Read from receive register"); } else { LOG("Read status"); - return status_; + return + ((next_transmission_ == NoTransmission) ? 0x02 : 0x00) | + (data_carrier_detect.read() ? 0x04 : 0x00) | + (clear_to_send.read() ? 0x08 : 0x00) + ; + + // b0: receive data full. + // b1: transmit data empty. + // b2: DCD. + // b3: CTS. + // b4: framing error (i.e. no first stop bit where expected). + // b5: receiver overran. + // b6: parity error. + // b7: IRQ state. } return 0x00; } void ACIA::write(int address, uint8_t value) { if(address&1) { - LOG("Write to transmit register"); + next_transmission_ = value; + consider_transmission(); } else { if((value&3) == 3) { - LOG("Reset"); + transmit.reset_writing(); + request_to_send.reset_writing(); } else { switch(value & 3) { default: @@ -38,30 +55,77 @@ void ACIA::write(int address, uint8_t value) { } switch((value >> 2) & 7) { default: - case 0: word_size_ = 7; stop_bits_ = 2; parity_ = Parity::Even; break; - case 1: word_size_ = 7; stop_bits_ = 2; parity_ = Parity::Odd; break; - case 2: word_size_ = 7; stop_bits_ = 1; parity_ = Parity::Even; break; - case 3: word_size_ = 7; stop_bits_ = 1; parity_ = Parity::Odd; break; - case 4: word_size_ = 8; stop_bits_ = 2; parity_ = Parity::None; break; - case 5: word_size_ = 8; stop_bits_ = 1; parity_ = Parity::None; break; - case 6: word_size_ = 8; stop_bits_ = 1; parity_ = Parity::Even; break; - case 7: word_size_ = 8; stop_bits_ = 1; parity_ = Parity::Odd; break; + case 0: data_bits_ = 7; stop_bits_ = 2; parity_ = Parity::Even; break; + case 1: data_bits_ = 7; stop_bits_ = 2; parity_ = Parity::Odd; break; + case 2: data_bits_ = 7; stop_bits_ = 1; parity_ = Parity::Even; break; + case 3: data_bits_ = 7; stop_bits_ = 1; parity_ = Parity::Odd; break; + case 4: data_bits_ = 8; stop_bits_ = 2; parity_ = Parity::None; break; + case 5: data_bits_ = 8; stop_bits_ = 1; parity_ = Parity::None; break; + case 6: data_bits_ = 8; stop_bits_ = 1; parity_ = Parity::Even; break; + case 7: data_bits_ = 8; stop_bits_ = 1; parity_ = Parity::Odd; break; } switch((value >> 5) & 3) { - case 0: set_ready_to_transmit(false); transmit_interrupt_enabled_ = false; break; - case 1: set_ready_to_transmit(false); transmit_interrupt_enabled_ = true; break; - case 2: set_ready_to_transmit(true); transmit_interrupt_enabled_ = false; break; - case 3: set_ready_to_transmit(false); transmit_interrupt_enabled_ = false; break; /* TODO: transmit a break level on the transmit output. */ + case 0: request_to_send.write(false); transmit_interrupt_enabled_ = false; break; + case 1: request_to_send.write(false); transmit_interrupt_enabled_ = true; break; + case 2: request_to_send.write(true); transmit_interrupt_enabled_ = false; break; + case 3: + request_to_send.write(false); + transmit_interrupt_enabled_ = false; + transmit.reset_writing(); + transmit.write(false); + break; } receive_interrupt_enabled_ = value & 0x80; - LOG("Write to control register"); } } } -void ACIA::run_for(HalfCycles) { +void ACIA::run_for(HalfCycles length) { + // Transmission. + int transmit_advance = length.as_int(); + if(next_transmission_ != NoTransmission) { + const auto write_data_time_remaining = transmit.write_data_time_remaining(); + if(transmit_advance > write_data_time_remaining) { + transmit.flush_writing(); + transmit_advance -= write_data_time_remaining; + consider_transmission(); + } + } + transmit.advance_writer(transmit_advance); + + // Reception. } -void ACIA::set_ready_to_transmit(bool) { +void ACIA::consider_transmission() { + if(next_transmission_ != NoTransmission && !transmit.write_data_time_remaining()) { + // Establish start bit and [7 or 8] data bits. + if(data_bits_ == 7) next_transmission_ &= 0x7f; + int transmission = next_transmission_ << 1; + // Add a parity bit, if any. + int mask = 0x2 << data_bits_; + if(parity_ != Parity::None) { + next_transmission_ ^= next_transmission_ >> 4; + next_transmission_ ^= next_transmission_ >> 2; + next_transmission_ ^= next_transmission_ >> 1; + + if((next_transmission_&1) != (parity_ == Parity::Even)) { + transmission |= mask; + } + mask <<= 1; + } + + // Add stop bits. + for(int c = 0; c < stop_bits_; ++c) { + transmission |= mask; + mask <<= 1; + } + + // Output all that. + const int total_bits = 1 + data_bits_ + stop_bits_ + (parity_ != Parity::None); + transmit.write(divider_, total_bits, transmission); + + // Mark the transmit register as empty again. + next_transmission_ = NoTransmission; + } } diff --git a/Components/6850/6850.hpp b/Components/6850/6850.hpp index df8eafa0e..bb7aec9c2 100644 --- a/Components/6850/6850.hpp +++ b/Components/6850/6850.hpp @@ -11,12 +11,15 @@ #include #include "../../ClockReceiver/ClockReceiver.hpp" +#include "../SerialPort/SerialPort.hpp" namespace Motorola { namespace ACIA { class ACIA { public: + ACIA(); + /*! Reads from the ACIA. @@ -37,17 +40,28 @@ class ACIA { void run_for(HalfCycles); + // Input lines. + Serial::Line receive; + Serial::Line clear_to_send; + Serial::Line data_carrier_detect; + + // Output lines. + Serial::Line transmit; + Serial::Line request_to_send; + private: int divider_ = 1; - uint8_t status_ = 0x00; enum class Parity { Even, Odd, None } parity_ = Parity::None; - int word_size_ = 7, stop_bits_ = 2; + int data_bits_ = 7, stop_bits_ = 2; + + static const int NoTransmission = 0x100; + int next_transmission_ = NoTransmission; + void consider_transmission(); + bool receive_interrupt_enabled_ = false; bool transmit_interrupt_enabled_ = false; - - void set_ready_to_transmit(bool); }; } diff --git a/Components/SerialPort/SerialPort.cpp b/Components/SerialPort/SerialPort.cpp index 4927e60ce..1b3c388fe 100644 --- a/Components/SerialPort/SerialPort.cpp +++ b/Components/SerialPort/SerialPort.cpp @@ -7,3 +7,68 @@ // #include "SerialPort.hpp" + +using namespace Serial; + +void Line::advance_writer(int cycles) { + remaining_delays_ = std::max(remaining_delays_ - cycles, 0); + while(!events_.empty()) { + if(events_.front().delay < cycles) { + cycles -= events_.front().delay; + auto iterator = events_.begin() + 1; + while(iterator != events_.end() && iterator->type != Event::Delay) { + level_ = iterator->type == Event::SetHigh; + ++iterator; + } + events_.erase(events_.begin(), iterator); + } else { + events_.front().delay -= cycles; + break; + } + } +} + +void Line::write(bool level) { + if(!events_.empty()) { + events_.emplace_back(); + events_.back().type = level ? Event::SetHigh : Event::SetLow; + } else { + level_ = level; + } +} + +void Line::write(int cycles, int count, int levels) { + remaining_delays_ += count*cycles; + + auto event = events_.size(); + events_.resize(events_.size() + size_t(count)*2); + while(count--) { + events_[event].type = Event::Delay; + events_[event].delay = cycles; + events_[event+1].type = (levels&1) ? Event::SetHigh : Event::SetLow; + event += 2; + } +} + +int Line::write_data_time_remaining() { + return remaining_delays_; +} + +void Line::reset_writing() { + events_.clear(); +} + +void Line::flush_writing() { + for(const auto &event : events_) { + switch(event.type) { + default: break; + case Event::SetHigh: level_ = true; break; + case Event::SetLow: level_ = false; break; + } + } + events_.clear(); +} + +bool Line::read() { + return level_; +} diff --git a/Components/SerialPort/SerialPort.hpp b/Components/SerialPort/SerialPort.hpp index e78061c25..055eba32d 100644 --- a/Components/SerialPort/SerialPort.hpp +++ b/Components/SerialPort/SerialPort.hpp @@ -9,19 +9,9 @@ #ifndef SerialPort_hpp #define SerialPort_hpp -namespace Serial { +#include -/// Signal is an amalgamation of the RS-232-esque signals and those available on the Macintosh -/// and therefore often associated with RS-422. -enum class Signal { - Receive, - Transmit, - ClearToSend, - RequestToSend, - DataCarrierDetect, - OutputHandshake, - InputHandshake -}; +namespace Serial { /*! @c Line connects a single reader and a single writer, allowing timestamped events to be @@ -34,29 +24,43 @@ enum class Signal { */ class Line { public: - void connect_reader(int clock_rate); - void disconnect_reader(); + /// Advances the read position by @c cycles relative to the writer's + /// clock rate. + void advance_writer(int cycles); - void connect_writer(int clock_rate); - void disconnect_writer(); + /// Sets the line to @c level. + void write(bool level); - /// Sets the line to @c level after @c cycles relative to the writer's - /// clock rate have elapsed from the final event currently posted. - void write(int cycles, bool level); - - /// Enqueues @c count level changes, the first occurring @c cycles + /// Enqueues @c count level changes, the first occurring immediately /// after the final event currently posted and each subsequent event - /// occurring @c cycles after the previous. The levels to output are + /// occurring @c cycles after the previous. An additional gap of @c cycles + /// is scheduled after the final output. The levels to output are /// taken from @c levels which is read from lsb to msb. @c cycles is /// relative to the writer's clock rate. void write(int cycles, int count, int levels); - /// Advances the read position by @c cycles relative to the reader's - /// clock rate. - void advance_reader(int cycles); + /// @returns the number of cycles until currently enqueued write data is exhausted. + int write_data_time_remaining(); - /// @returns The instantaneous level of this line at the current read position. + /// Eliminates all future write states, leaving the output at whatever it is now. + void reset_writing(); + + /// Applies all pending write changes instantly. + void flush_writing(); + + /// @returns The instantaneous level of this line. bool read(); + + private: + struct Event { + enum Type { + Delay, SetHigh, SetLow + } type; + int delay; + }; + std::vector events_; + int remaining_delays_ = 0; + bool level_ = false; }; /*! diff --git a/Machines/AtariST/AtariST.cpp b/Machines/AtariST/AtariST.cpp index e550d4ac4..1a4d829a1 100644 --- a/Machines/AtariST/AtariST.cpp +++ b/Machines/AtariST/AtariST.cpp @@ -39,6 +39,8 @@ class ConcreteMachine: public: ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : mc68000_(*this), + keyboard_acia_(), + midi_acia_(), ay_(audio_queue_), speaker_(ay_) { set_clock_rate(CLOCK_RATE); From 516d78f5a890c99815335ef0171301555bf59eef Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 12 Oct 2019 23:46:57 -0400 Subject: [PATCH 25/39] Attempts to implement transmission interrupts and ClockingHint::Source. --- Components/6850/6850.cpp | 32 +++++++++++++++++++++++++------- Components/6850/6850.hpp | 9 ++++++++- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/Components/6850/6850.cpp b/Components/6850/6850.cpp index f86989c5d..e935590ff 100644 --- a/Components/6850/6850.cpp +++ b/Components/6850/6850.cpp @@ -18,12 +18,14 @@ ACIA::ACIA() {} uint8_t ACIA::read(int address) { if(address&1) { LOG("Read from receive register"); + interrupt_request_ = false; } else { LOG("Read status"); return ((next_transmission_ == NoTransmission) ? 0x02 : 0x00) | (data_carrier_detect.read() ? 0x04 : 0x00) | - (clear_to_send.read() ? 0x08 : 0x00) + (clear_to_send.read() ? 0x08 : 0x00) | + (interrupt_request_ ? 0x80 : 0x00) ; // b0: receive data full. @@ -42,6 +44,8 @@ void ACIA::write(int address, uint8_t value) { if(address&1) { next_transmission_ = value; consider_transmission(); + update_clocking_observer(); + interrupt_request_ = false; } else { if((value&3) == 3) { transmit.reset_writing(); @@ -82,16 +86,22 @@ void ACIA::write(int address, uint8_t value) { void ACIA::run_for(HalfCycles length) { // Transmission. - int transmit_advance = length.as_int(); - if(next_transmission_ != NoTransmission) { - const auto write_data_time_remaining = transmit.write_data_time_remaining(); - if(transmit_advance > write_data_time_remaining) { + const int transmit_advance = length.as_int(); + const auto write_data_time_remaining = transmit.write_data_time_remaining(); + + if(transmit_advance > write_data_time_remaining) { + if(next_transmission_ != NoTransmission) { transmit.flush_writing(); - transmit_advance -= write_data_time_remaining; consider_transmission(); + transmit.advance_writer(transmit_advance - write_data_time_remaining); + } else { + transmit.advance_writer(transmit_advance); + update_clocking_observer(); + interrupt_request_ |= transmit_interrupt_enabled_; } + } else { + transmit.advance_writer(transmit_advance); } - transmit.advance_writer(transmit_advance); // Reception. } @@ -129,3 +139,11 @@ void ACIA::consider_transmission() { next_transmission_ = NoTransmission; } } + +ClockingHint::Preference ACIA::preferred_clocking() { + return (transmit.write_data_time_remaining() > 0) ? ClockingHint::Preference::JustInTime : ClockingHint::Preference::None; +} + +bool ACIA::get_interrupt_line() { + return interrupt_request_; +} diff --git a/Components/6850/6850.hpp b/Components/6850/6850.hpp index bb7aec9c2..1b76726a9 100644 --- a/Components/6850/6850.hpp +++ b/Components/6850/6850.hpp @@ -11,12 +11,13 @@ #include #include "../../ClockReceiver/ClockReceiver.hpp" +#include "../../ClockReceiver/ClockingHintSource.hpp" #include "../SerialPort/SerialPort.hpp" namespace Motorola { namespace ACIA { -class ACIA { +class ACIA: public ClockingHint::Source { public: ACIA(); @@ -40,6 +41,8 @@ class ACIA { void run_for(HalfCycles); + bool get_interrupt_line(); + // Input lines. Serial::Line receive; Serial::Line clear_to_send; @@ -62,6 +65,10 @@ class ACIA { bool receive_interrupt_enabled_ = false; bool transmit_interrupt_enabled_ = false; + + bool interrupt_request_ = false; + + ClockingHint::Preference preferred_clocking() final; }; } From d7982aa84e17c4492b163ec4b82c25caeeebe9e8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 13 Oct 2019 18:19:39 -0400 Subject: [PATCH 26/39] JustInTimeActors can now specify a clock divider. --- ClockReceiver/ClockReceiver.hpp | 23 +++++++++++++++++++---- ClockReceiver/JustInTime.hpp | 12 +++++++++--- Components/6850/6850.cpp | 20 +++++++++++--------- Machines/Apple/Macintosh/Macintosh.cpp | 8 +++++--- Machines/AtariST/AtariST.cpp | 10 ++++++---- Machines/ColecoVision/ColecoVision.cpp | 2 +- Machines/MSX/MSX.cpp | 2 +- Machines/MasterSystem/MasterSystem.cpp | 2 +- 8 files changed, 53 insertions(+), 26 deletions(-) diff --git a/ClockReceiver/ClockReceiver.hpp b/ClockReceiver/ClockReceiver.hpp index 6fe49cf1d..a621cbf1b 100644 --- a/ClockReceiver/ClockReceiver.hpp +++ b/ClockReceiver/ClockReceiver.hpp @@ -139,10 +139,10 @@ template class WrappedInt { Severs from @c this the effect of dividing by @c divisor; @c this will end up with the value of @c this modulo @c divisor and @c divided by @c divisor is returned. */ - forceinline T divide(const T &divisor) { - T result(length_ / divisor.length_); - length_ %= divisor.length_; - return result; + template forceinline Result divide(const T &divisor) { + Result r; + static_cast(this)->fill(r, divisor); + return r; } /*! @@ -177,6 +177,11 @@ class Cycles: public WrappedInt { result.length_ = length_; length_ = 0; } + + void fill(Cycles &result, const Cycles &divisor) { + result.length_ = length_ / divisor.length_; + length_ %= divisor.length_; + } }; /// Describes an integer number of half cycles: single clock signal transitions. @@ -215,6 +220,16 @@ class HalfCycles: public WrappedInt { result.length_ = length_; length_ = 0; } + + void fill(Cycles &result, const HalfCycles &divisor) { + result = Cycles(length_ / (divisor.length_ << 1)); + length_ %= (divisor.length_ << 1); + } + + void fill(HalfCycles &result, const HalfCycles &divisor) { + result.length_ = length_ / divisor.length_; + length_ %= divisor.length_; + } }; // Create a specialisation of WrappedInt::flush for converting HalfCycles to Cycles diff --git a/ClockReceiver/JustInTime.hpp b/ClockReceiver/JustInTime.hpp index 4f7bafe05..778ba93e2 100644 --- a/ClockReceiver/JustInTime.hpp +++ b/ClockReceiver/JustInTime.hpp @@ -21,7 +21,7 @@ Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a separate @c TargetTimeScale at template declaration. */ -template class JustInTimeActor { +template class JustInTimeActor { public: /// Constructs a new JustInTimeActor using the same construction arguments as the included object. template JustInTimeActor(Args&&... args) : object_(std::forward(args)...) {} @@ -44,8 +44,14 @@ template ()); + void flush() { + if(!is_flushed_) { + if(divider == 1) { + object_.run_for(time_since_update_.template flush()); + } else { + object_.run_for(time_since_update_.template divide(LocalTimeScale(divider))); + } + } is_flushed_ = true; } diff --git a/Components/6850/6850.cpp b/Components/6850/6850.cpp index e935590ff..5b20be2cc 100644 --- a/Components/6850/6850.cpp +++ b/Components/6850/6850.cpp @@ -89,18 +89,20 @@ void ACIA::run_for(HalfCycles length) { const int transmit_advance = length.as_int(); const auto write_data_time_remaining = transmit.write_data_time_remaining(); - if(transmit_advance > write_data_time_remaining) { - if(next_transmission_ != NoTransmission) { - transmit.flush_writing(); - consider_transmission(); - transmit.advance_writer(transmit_advance - write_data_time_remaining); + if(write_data_time_remaining) { + if(transmit_advance > write_data_time_remaining) { + if(next_transmission_ != NoTransmission) { + transmit.flush_writing(); + consider_transmission(); + transmit.advance_writer(transmit_advance - write_data_time_remaining); + } else { + transmit.advance_writer(transmit_advance); + update_clocking_observer(); + interrupt_request_ |= transmit_interrupt_enabled_; + } } else { transmit.advance_writer(transmit_advance); - update_clocking_observer(); - interrupt_request_ |= transmit_interrupt_enabled_; } - } else { - transmit.advance_writer(transmit_advance); } // Reception. diff --git a/Machines/Apple/Macintosh/Macintosh.cpp b/Machines/Apple/Macintosh/Macintosh.cpp index 09d91a47d..231b5f242 100644 --- a/Machines/Apple/Macintosh/Macintosh.cpp +++ b/Machines/Apple/Macintosh/Macintosh.cpp @@ -656,9 +656,11 @@ template class ConcreteMachin return mouse_; } + using IWMActor = JustInTimeActor; + class VIAPortHandler: public MOS::MOS6522::PortHandler { public: - VIAPortHandler(ConcreteMachine &machine, RealTimeClock &clock, Keyboard &keyboard, DeferredAudio &audio, JustInTimeActor &iwm, Inputs::QuadratureMouse &mouse) : + VIAPortHandler(ConcreteMachine &machine, RealTimeClock &clock, Keyboard &keyboard, DeferredAudio &audio, IWMActor &iwm, Inputs::QuadratureMouse &mouse) : machine_(machine), clock_(clock), keyboard_(keyboard), audio_(audio), iwm_(iwm), mouse_(mouse) {} using Port = MOS::MOS6522::Port; @@ -764,14 +766,14 @@ template class ConcreteMachin RealTimeClock &clock_; Keyboard &keyboard_; DeferredAudio &audio_; - JustInTimeActor &iwm_; + IWMActor &iwm_; Inputs::QuadratureMouse &mouse_; }; CPU::MC68000::Processor mc68000_; DriveSpeedAccumulator drive_speed_accumulator_; - JustInTimeActor iwm_; + IWMActor iwm_; DeferredAudio audio_; Video video_; diff --git a/Machines/AtariST/AtariST.cpp b/Machines/AtariST/AtariST.cpp index 1a4d829a1..f161cfb8a 100644 --- a/Machines/AtariST/AtariST.cpp +++ b/Machines/AtariST/AtariST.cpp @@ -347,6 +347,8 @@ class ConcreteMachine: forceinline void advance_time(HalfCycles length) { cycles_since_audio_update_ += length; mfp_ += length; + keyboard_acia_ += length; + midi_acia_ += length; while(length >= cycles_until_video_event_) { length -= cycles_until_video_event_; @@ -366,12 +368,12 @@ class ConcreteMachine: } CPU::MC68000::Processor mc68000_; - JustInTimeActor video_; + JustInTimeActor