From 5530b964466c3a85a1fa1d00e836be3760c6e0fb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 23 Feb 2018 22:47:15 -0500 Subject: [PATCH 01/14] Wired up a class and analyser for a ColecoVision. --- Analyser/Machines.hpp | 1 + Analyser/Static/Coleco/StaticAnalyser.cpp | 45 +++++++++++++++++++ Analyser/Static/Coleco/StaticAnalyser.hpp | 25 +++++++++++ Analyser/Static/StaticAnalyser.cpp | 8 +++- Machines/ColecoVision/ColecoVision.cpp | 27 +++++++++++ Machines/ColecoVision/ColecoVision.hpp | 24 ++++++++++ Machines/Utility/MachineForTarget.cpp | 18 +++++--- .../Clock Signal.xcodeproj/project.pbxproj | 28 ++++++++++++ OSBindings/Mac/Clock Signal/Info.plist | 20 +++++++++ Storage/TargetPlatforms.hpp | 11 ++--- 10 files changed, 194 insertions(+), 13 deletions(-) create mode 100644 Analyser/Static/Coleco/StaticAnalyser.cpp create mode 100644 Analyser/Static/Coleco/StaticAnalyser.hpp create mode 100644 Machines/ColecoVision/ColecoVision.cpp create mode 100644 Machines/ColecoVision/ColecoVision.hpp diff --git a/Analyser/Machines.hpp b/Analyser/Machines.hpp index 8ccbc5226..a71d16748 100644 --- a/Analyser/Machines.hpp +++ b/Analyser/Machines.hpp @@ -14,6 +14,7 @@ namespace Analyser { enum class Machine { AmstradCPC, Atari2600, + ColecoVision, Electron, MSX, Oric, diff --git a/Analyser/Static/Coleco/StaticAnalyser.cpp b/Analyser/Static/Coleco/StaticAnalyser.cpp new file mode 100644 index 000000000..0cced2590 --- /dev/null +++ b/Analyser/Static/Coleco/StaticAnalyser.cpp @@ -0,0 +1,45 @@ +// +// StaticAnalyser.cpp +// Clock Signal +// +// Created by Thomas Harte on 23/02/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#include "StaticAnalyser.hpp" + +static std::vector> + ColecoCartridgesFrom(const std::vector> &cartridges) { + std::vector> coleco_cartridges; + + for(const auto &cartridge : cartridges) { + const auto &segments = cartridge->get_segments(); + + // only one mapped item is allowed + if(segments.size() != 1) continue; + + // which must be 8, 12, 16, 24 or 32 kb in size + const Storage::Cartridge::Cartridge::Segment &segment = segments.front(); + const std::size_t data_size = segment.data.size(); + if((data_size&8191) && (data_size != 12*1024)) continue; + if(data_size < 8192 || data_size > 32768) continue; + + // the two first bytes must be 0xaa and 0x55, either way around + if(segment.data[0] != 0xaa && segment.data[0] != 0x55 && segment.data[1] != 0xaa && segment.data[1] != 0x55) continue; + if(segment.data[0] == segment.data[1]) continue; + + // probability of a random binary blob that isn't a Coleco ROM proceeding to here is 1 - 1/32768. + coleco_cartridges.push_back(cartridge); + } + + return coleco_cartridges; +} + +void Analyser::Static::Coleco::AddTargets(const Media &media, std::vector> &destination) { + std::unique_ptr target(new Target); + target->machine = Machine::ColecoVision; + target->confidence = 0.5; + target->media.cartridges = ColecoCartridgesFrom(media.cartridges); + if(!target->media.empty()) + destination.push_back(std::move(target)); +} diff --git a/Analyser/Static/Coleco/StaticAnalyser.hpp b/Analyser/Static/Coleco/StaticAnalyser.hpp new file mode 100644 index 000000000..91014b1c4 --- /dev/null +++ b/Analyser/Static/Coleco/StaticAnalyser.hpp @@ -0,0 +1,25 @@ +// +// StaticAnalyser.hpp +// Clock Signal +// +// Created by Thomas Harte on 23/02/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#ifndef StaticAnalyser_Coleco_StaticAnalyser_hpp +#define StaticAnalyser_Coleco_StaticAnalyser_hpp + +#include "../StaticAnalyser.hpp" + +namespace Analyser { +namespace Static { +namespace Coleco { + +void AddTargets(const Media &media, std::vector> &destination); + +} +} +} + + +#endif /* StaticAnalyser_hpp */ diff --git a/Analyser/Static/StaticAnalyser.cpp b/Analyser/Static/StaticAnalyser.cpp index c773d07d3..daae232ba 100644 --- a/Analyser/Static/StaticAnalyser.cpp +++ b/Analyser/Static/StaticAnalyser.cpp @@ -16,6 +16,7 @@ #include "Acorn/StaticAnalyser.hpp" #include "AmstradCPC/StaticAnalyser.hpp" #include "Atari/StaticAnalyser.hpp" +#include "Coleco/StaticAnalyser.hpp" #include "Commodore/StaticAnalyser.hpp" #include "MSX/StaticAnalyser.hpp" #include "Oric/StaticAnalyser.hpp" @@ -90,6 +91,7 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // BIN Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT + Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW Format("d64", result.disks, Disk::DiskImageHolder, TargetPlatform::Commodore) // D64 Format("dmk", result.disks, Disk::DiskImageHolder, TargetPlatform::MSX) // DMK @@ -115,7 +117,10 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType } } - Format("rom", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Acorn | TargetPlatform::MSX) // ROM + Format( "rom", + result.cartridges, + Cartridge::BinaryDump, + TargetPlatform::Acorn | TargetPlatform::MSX | TargetPlatform::ColecoVision) // ROM Format("ssd", result.disks, Disk::DiskImageHolder, TargetPlatform::Acorn) // SSD Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore) Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric) @@ -153,6 +158,7 @@ std::vector> Analyser::Static::GetTargets(const char *fi if(potential_platforms & TargetPlatform::Acorn) Acorn::AddTargets(media, targets); if(potential_platforms & TargetPlatform::AmstradCPC) AmstradCPC::AddTargets(media, targets); if(potential_platforms & TargetPlatform::Atari2600) Atari::AddTargets(media, targets); + if(potential_platforms & TargetPlatform::ColecoVision) Coleco::AddTargets(media, targets); if(potential_platforms & TargetPlatform::Commodore) Commodore::AddTargets(media, targets); if(potential_platforms & TargetPlatform::MSX) MSX::AddTargets(media, targets); if(potential_platforms & TargetPlatform::Oric) Oric::AddTargets(media, targets); diff --git a/Machines/ColecoVision/ColecoVision.cpp b/Machines/ColecoVision/ColecoVision.cpp new file mode 100644 index 000000000..d54bab118 --- /dev/null +++ b/Machines/ColecoVision/ColecoVision.cpp @@ -0,0 +1,27 @@ +// +// ColecoVision.cpp +// Clock Signal +// +// Created by Thomas Harte on 23/02/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#include "ColecoVision.hpp" + +namespace Coleco { +namespace Vision { + +class ConcreteMachine: + public Machine { +}; + +} +} + +using namespace Coleco::Vision; + +Machine *Machine::ColecoVision() { + return new ConcreteMachine; +} + +Machine::~Machine() {} diff --git a/Machines/ColecoVision/ColecoVision.hpp b/Machines/ColecoVision/ColecoVision.hpp new file mode 100644 index 000000000..fc6f41573 --- /dev/null +++ b/Machines/ColecoVision/ColecoVision.hpp @@ -0,0 +1,24 @@ +// +// ColecoVision.hpp +// Clock Signal +// +// Created by Thomas Harte on 23/02/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#ifndef ColecoVision_hpp +#define ColecoVision_hpp + +namespace Coleco { +namespace Vision { + +class Machine { + public: + virtual ~Machine(); + static Machine *ColecoVision(); +}; + +} +} + +#endif /* ColecoVision_hpp */ diff --git a/Machines/Utility/MachineForTarget.cpp b/Machines/Utility/MachineForTarget.cpp index 3c1641fcc..dd8f205e1 100644 --- a/Machines/Utility/MachineForTarget.cpp +++ b/Machines/Utility/MachineForTarget.cpp @@ -10,6 +10,7 @@ #include "../AmstradCPC/AmstradCPC.hpp" #include "../Atari2600/Atari2600.hpp" +#include "../ColecoVision/ColecoVision.hpp" #include "../Commodore/Vic-20/Vic20.hpp" #include "../Electron/Electron.hpp" #include "../MSX/MSX.hpp" @@ -25,13 +26,14 @@ namespace { error = Machine::Error::None; ::Machine::DynamicMachine *machine = nullptr; switch(target.machine) { - case Analyser::Machine::AmstradCPC: machine = new Machine::TypedDynamicMachine(AmstradCPC::Machine::AmstradCPC()); break; - case Analyser::Machine::Atari2600: machine = new Machine::TypedDynamicMachine(Atari2600::Machine::Atari2600()); break; - case Analyser::Machine::Electron: machine = new Machine::TypedDynamicMachine(Electron::Machine::Electron()); break; - case Analyser::Machine::MSX: machine = new Machine::TypedDynamicMachine(MSX::Machine::MSX()); break; - case Analyser::Machine::Oric: machine = new Machine::TypedDynamicMachine(Oric::Machine::Oric()); break; - case Analyser::Machine::Vic20: machine = new Machine::TypedDynamicMachine(Commodore::Vic20::Machine::Vic20()); break; - case Analyser::Machine::ZX8081: machine = new Machine::TypedDynamicMachine(ZX8081::Machine::ZX8081(target)); break; + case Analyser::Machine::AmstradCPC: machine = new Machine::TypedDynamicMachine(AmstradCPC::Machine::AmstradCPC()); break; + case Analyser::Machine::Atari2600: machine = new Machine::TypedDynamicMachine(Atari2600::Machine::Atari2600()); break; + case Analyser::Machine::ColecoVision: machine = new Machine::TypedDynamicMachine(Coleco::Vision::Machine::ColecoVision()); break; + case Analyser::Machine::Electron: machine = new Machine::TypedDynamicMachine(Electron::Machine::Electron()); break; + case Analyser::Machine::MSX: machine = new Machine::TypedDynamicMachine(MSX::Machine::MSX()); break; + case Analyser::Machine::Oric: machine = new Machine::TypedDynamicMachine(Oric::Machine::Oric()); break; + case Analyser::Machine::Vic20: machine = new Machine::TypedDynamicMachine(Commodore::Vic20::Machine::Vic20()); break; + case Analyser::Machine::ZX8081: machine = new Machine::TypedDynamicMachine(ZX8081::Machine::ZX8081(target)); break; default: error = Machine::Error::UnknownMachine; @@ -88,6 +90,7 @@ std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine) switch(machine) { case Analyser::Machine::AmstradCPC: return "AmstradCPC"; case Analyser::Machine::Atari2600: return "Atari2600"; + case Analyser::Machine::ColecoVision: return "ColecoVision"; case Analyser::Machine::Electron: return "Electron"; case Analyser::Machine::MSX: return "MSX"; case Analyser::Machine::Oric: return "Oric"; @@ -102,6 +105,7 @@ std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) { switch(machine) { case Analyser::Machine::AmstradCPC: return "Amstrad CPC"; case Analyser::Machine::Atari2600: return "Atari 2600"; + case Analyser::Machine::ColecoVision: return "ColecoVision"; case Analyser::Machine::Electron: return "Acorn Electron"; case Analyser::Machine::MSX: return "MSX"; case Analyser::Machine::Oric: return "Oric"; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 5176aaf6b..290153c0a 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -220,6 +220,8 @@ 4B79E4441E3AF38600141F11 /* cassette.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4411E3AF38600141F11 /* cassette.png */; }; 4B79E4451E3AF38600141F11 /* floppy35.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4421E3AF38600141F11 /* floppy35.png */; }; 4B79E4461E3AF38600141F11 /* floppy525.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4431E3AF38600141F11 /* floppy525.png */; }; + 4B7A90E52041097C008514A2 /* ColecoVision.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A90E42041097C008514A2 /* ColecoVision.cpp */; }; + 4B7A90ED20410A85008514A2 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A90EC20410A85008514A2 /* StaticAnalyser.cpp */; }; 4B7BC7F51F58F27800D1B1B4 /* 6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C911F58F09E00E3F787 /* 6502AllRAM.cpp */; }; 4B7BC7F61F58F7D200D1B1B4 /* 6502Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */; }; 4B80AD001F85CACA00176895 /* BestEffortUpdater.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B80ACFE1F85CAC900176895 /* BestEffortUpdater.cpp */; }; @@ -880,6 +882,10 @@ 4B79E4411E3AF38600141F11 /* cassette.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cassette.png; sourceTree = ""; }; 4B79E4421E3AF38600141F11 /* floppy35.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy35.png; sourceTree = ""; }; 4B79E4431E3AF38600141F11 /* floppy525.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy525.png; sourceTree = ""; }; + 4B7A90E32041097C008514A2 /* ColecoVision.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ColecoVision.hpp; sourceTree = ""; }; + 4B7A90E42041097C008514A2 /* ColecoVision.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ColecoVision.cpp; sourceTree = ""; }; + 4B7A90EB20410A85008514A2 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = ""; }; + 4B7A90EC20410A85008514A2 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = ""; }; 4B80ACFE1F85CAC900176895 /* BestEffortUpdater.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BestEffortUpdater.cpp; path = ../../Concurrency/BestEffortUpdater.cpp; sourceTree = ""; }; 4B80ACFF1F85CACA00176895 /* BestEffortUpdater.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = BestEffortUpdater.hpp; path = ../../Concurrency/BestEffortUpdater.hpp; sourceTree = ""; }; 4B8334811F5D9FF70097E338 /* PartialMachineCycle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PartialMachineCycle.cpp; sourceTree = ""; }; @@ -2020,6 +2026,24 @@ name = MSX; sourceTree = ""; }; + 4B7A90E22041097C008514A2 /* ColecoVision */ = { + isa = PBXGroup; + children = ( + 4B7A90E32041097C008514A2 /* ColecoVision.hpp */, + 4B7A90E42041097C008514A2 /* ColecoVision.cpp */, + ); + path = ColecoVision; + sourceTree = ""; + }; + 4B7A90EA20410A85008514A2 /* Coleco */ = { + isa = PBXGroup; + children = ( + 4B7A90EC20410A85008514A2 /* StaticAnalyser.cpp */, + 4B7A90EB20410A85008514A2 /* StaticAnalyser.hpp */, + ); + path = Coleco; + sourceTree = ""; + }; 4B8334881F5DB8470097E338 /* Implementation */ = { isa = PBXGroup; children = ( @@ -2114,6 +2138,7 @@ 4B8944EB201967B4007DE474 /* Acorn */, 4B894514201967B4007DE474 /* AmstradCPC */, 4B8944F3201967B4007DE474 /* Atari */, + 4B7A90EA20410A85008514A2 /* Coleco */, 4B8944FB201967B4007DE474 /* Commodore */, 4B894507201967B4007DE474 /* Disassembler */, 4B89450F201967B4007DE474 /* MSX */, @@ -2647,6 +2672,7 @@ 4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */, 4B38F3491F2EC12000D9235D /* AmstradCPC */, 4B2E2D961C3A06EC00138695 /* Atari2600 */, + 4B7A90E22041097C008514A2 /* ColecoVision */, 4B4DC81D1D2C2425003C5BF8 /* Commodore */, 4B2E2D9E1C3A070900138695 /* Electron */, 4B79A4FC1FC8FF9800EEDAD5 /* MSX */, @@ -3520,6 +3546,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4B7A90E52041097C008514A2 /* ColecoVision.cpp in Sources */, 4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */, 4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */, 4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */, @@ -3528,6 +3555,7 @@ 4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */, 4B7136861F78724F008B8ED9 /* Encoder.cpp in Sources */, 4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */, + 4B7A90ED20410A85008514A2 /* StaticAnalyser.cpp in Sources */, 4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */, 4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */, 4B0E61071FF34737002A9DBD /* MSX.cpp in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index b588b9cf7..12a8aee31 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -373,6 +373,26 @@ NSDocumentClass $(PRODUCT_MODULE_NAME).MachineDocument + + CFBundleTypeExtensions + + col + + CFBundleTypeIconFile + cartridge + CFBundleTypeName + ColecoVision Cartridge + CFBundleTypeRole + Viewer + LSItemContentTypes + + public.item + + LSTypeIsPackage + 0 + NSDocumentClass + $(PRODUCT_MODULE_NAME).MachineDocument + CFBundleExecutable $(EXECUTABLE_NAME) diff --git a/Storage/TargetPlatforms.hpp b/Storage/TargetPlatforms.hpp index 560abf0be..b6b222cc8 100644 --- a/Storage/TargetPlatforms.hpp +++ b/Storage/TargetPlatforms.hpp @@ -20,11 +20,12 @@ enum Type: IntType { BBCMaster = 1 << 5, BBCModelA = 1 << 6, BBCModelB = 1 << 7, - Commodore = 1 << 8, - MSX = 1 << 9, - Oric = 1 << 10, - ZX80 = 1 << 11, - ZX81 = 1 << 12, + ColecoVision = 1 << 8, + Commodore = 1 << 9, + MSX = 1 << 10, + Oric = 1 << 11, + ZX80 = 1 << 12, + ZX81 = 1 << 13, Acorn = AcornAtom | AcornElectron | BBCMaster | BBCModelA | BBCModelB, ZX8081 = ZX80 | ZX81, From 23c47e21de459efa8be771e323a990758eaff374 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 24 Feb 2018 18:14:38 -0500 Subject: [PATCH 02/14] Proceeds the ColecoVision to booting. --- Machines/ColecoVision/ColecoVision.cpp | 132 +++++++++++++++++- .../Z80/Implementation/Z80Implementation.hpp | 4 +- Processors/Z80/Z80.hpp | 2 +- ROMImages/ColecoVision/readme.txt | 5 + 4 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 ROMImages/ColecoVision/readme.txt diff --git a/Machines/ColecoVision/ColecoVision.cpp b/Machines/ColecoVision/ColecoVision.cpp index d54bab118..a043d21a7 100644 --- a/Machines/ColecoVision/ColecoVision.cpp +++ b/Machines/ColecoVision/ColecoVision.cpp @@ -8,11 +8,141 @@ #include "ColecoVision.hpp" +#include "../../Processors/Z80/Z80.hpp" + +#include "../../Components/9918/9918.hpp" + +#include "../CRTMachine.hpp" + +#include "../../ClockReceiver/ForceInline.hpp" + namespace Coleco { namespace Vision { class ConcreteMachine: - public Machine { + public Machine, + public CPU::Z80::BusHandler, + public CRTMachine::Machine { + + public: + ConcreteMachine() : z80_(*this) { + set_clock_rate(3579545); + } + + void setup_output(float aspect_ratio) override { + vdp_.reset(new TI::TMS9918(TI::TMS9918::TMS9918A)); + get_crt()->set_output_device(Outputs::CRT::OutputDevice::Television); + } + + void close_output() override { + vdp_.reset(); + } + + Outputs::CRT::CRT *get_crt() override { + return vdp_->get_crt(); + } + + Outputs::Speaker::Speaker *get_speaker() override { + return nullptr; + } + + void run_for(const Cycles cycles) override { + z80_.run_for(cycles); + } + + // Obtains the system ROMs. + bool set_rom_fetcher(const std::function>>(const std::string &machine, const std::vector &names)> &roms_with_names) override { + auto roms = roms_with_names( + "ColecoVision", + { + "coleco.rom" + }); + + if(!roms[0]) return false; + + bios_ = *roms[0]; + bios_.resize(8192); + + return true; + } + + // MARK: Z80::BusHandler + forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { + if(time_until_interrupt_ > 0) { + time_until_interrupt_ -= cycle.length; + if(time_until_interrupt_ <= HalfCycles(0)) { + z80_.set_non_maskable_interrupt_line(true, time_until_interrupt_); + } + } + + uint16_t address = cycle.address ? *cycle.address : 0x0000; + switch(cycle.operation) { + case CPU::Z80::PartialMachineCycle::ReadOpcode: + case CPU::Z80::PartialMachineCycle::Read: + if(address < 0x2000) { + *cycle.value = bios_[address]; + } else if(address >= 0x6000 && address < 0x8000) { + *cycle.value = ram_[address & 1023]; + } else { + *cycle.value = 0xff; + } + break; + + case CPU::Z80::PartialMachineCycle::Write: + if(address >= 0x6000 && address < 0x8000) { + ram_[address & 1023] = *cycle.value; + } + break; + + case CPU::Z80::PartialMachineCycle::Input: + switch((address >> 5) & 7) { + case 5: + vdp_->run_for(time_since_vdp_update_.flush()); + *cycle.value = vdp_->get_register(address); + z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line()); + time_until_interrupt_ = vdp_->get_time_until_interrupt(); + break; + + default: + *cycle.value = 0xff; + break; + } + break; + + case CPU::Z80::PartialMachineCycle::Output: + switch((address >> 5) & 7) { + case 5: + vdp_->run_for(time_since_vdp_update_.flush()); + vdp_->set_register(address, *cycle.value); + z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line()); + time_until_interrupt_ = vdp_->get_time_until_interrupt(); + break; + + default: break; + } + break; + + default: + break; + } + + time_since_vdp_update_ += cycle.length; + return HalfCycles(0); + } + + void flush() { + vdp_->run_for(time_since_vdp_update_.flush()); + } + + private: + CPU::Z80::Processor z80_; + std::unique_ptr vdp_; + + std::vector bios_; + uint8_t ram_[1024]; + + HalfCycles time_since_vdp_update_; + HalfCycles time_until_interrupt_; }; } diff --git a/Processors/Z80/Implementation/Z80Implementation.hpp b/Processors/Z80/Implementation/Z80Implementation.hpp index 0aea4029c..b1e48be49 100644 --- a/Processors/Z80/Implementation/Z80Implementation.hpp +++ b/Processors/Z80/Implementation/Z80Implementation.hpp @@ -1001,12 +1001,12 @@ bool ProcessorBase::get_interrupt_line() { return irq_line_; } -void ProcessorBase::set_non_maskable_interrupt_line(bool value, int offset) { +void ProcessorBase::set_non_maskable_interrupt_line(bool value, HalfCycles offset) { // NMIs are edge triggered and cannot be masked. nmi_line_ = value; if(value) { request_status_ |= Interrupt::NMI; - if(offset < 0) { + if(offset.as_int() < 0) { last_request_status_ |= Interrupt::NMI; } } diff --git a/Processors/Z80/Z80.hpp b/Processors/Z80/Z80.hpp index 216dbc512..7a7dbbbe1 100644 --- a/Processors/Z80/Z80.hpp +++ b/Processors/Z80/Z80.hpp @@ -206,7 +206,7 @@ class ProcessorBase: public ProcessorStorage { @param offset See discussion in set_interrupt_line. */ - inline void set_non_maskable_interrupt_line(bool value, int offset = 0); + inline void set_non_maskable_interrupt_line(bool value, HalfCycles offset = 0); /*! Gets the value of the non-maskable interrupt line. diff --git a/ROMImages/ColecoVision/readme.txt b/ROMImages/ColecoVision/readme.txt new file mode 100644 index 000000000..39b85b605 --- /dev/null +++ b/ROMImages/ColecoVision/readme.txt @@ -0,0 +1,5 @@ +ROM files would ordinarily go here; the copyright status of these is uncertain so they have not been included in this repository. + +Expected files: + +coleco.rom; an 8kb image of the ColecoVision's BIOS ROM. From 23d15a4d6cc0b983dbbd566373df454b1fc78bb8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 24 Feb 2018 18:26:44 -0500 Subject: [PATCH 03/14] The ColecoVision now accepts and loads cartridges. --- Machines/ColecoVision/ColecoVision.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Machines/ColecoVision/ColecoVision.cpp b/Machines/ColecoVision/ColecoVision.cpp index a043d21a7..d38f0dd9b 100644 --- a/Machines/ColecoVision/ColecoVision.cpp +++ b/Machines/ColecoVision/ColecoVision.cpp @@ -13,6 +13,7 @@ #include "../../Components/9918/9918.hpp" #include "../CRTMachine.hpp" +#include "../ConfigurationTarget.hpp" #include "../../ClockReceiver/ForceInline.hpp" @@ -22,7 +23,8 @@ namespace Vision { class ConcreteMachine: public Machine, public CPU::Z80::BusHandler, - public CRTMachine::Machine { + public CRTMachine::Machine, + public ConfigurationTarget::Machine { public: ConcreteMachine() : z80_(*this) { @@ -50,6 +52,20 @@ class ConcreteMachine: z80_.run_for(cycles); } + void configure_as_target(const Analyser::Static::Target &target) override { + // Insert the media. + insert_media(target.media); + } + + bool insert_media(const Analyser::Static::Media &media) override { + if(!media.cartridges.empty()) { + const auto &segment = media.cartridges.front()->get_segments().front(); + cartridge_ = segment.data; + } + + return true; + } + // Obtains the system ROMs. bool set_rom_fetcher(const std::function>>(const std::string &machine, const std::vector &names)> &roms_with_names) override { auto roms = roms_with_names( @@ -83,6 +99,8 @@ class ConcreteMachine: *cycle.value = bios_[address]; } else if(address >= 0x6000 && address < 0x8000) { *cycle.value = ram_[address & 1023]; + } else if(address >= 0x8000 && address < 0x8000 + cartridge_.size()) { + *cycle.value = cartridge_[address - 0x8000]; } else { *cycle.value = 0xff; } @@ -139,6 +157,7 @@ class ConcreteMachine: std::unique_ptr vdp_; std::vector bios_; + std::vector cartridge_; uint8_t ram_[1024]; HalfCycles time_since_vdp_update_; From 204d5cc96458b28da90655bbcaa91c51400d16a8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Feb 2018 19:08:50 -0500 Subject: [PATCH 04/14] Extends JoystickMachine protocol to cover ColecoVision use case. Also thereby implements input on the ColecoVision, in theory at least. No input is being fed though, so... --- Inputs/Joystick.hpp | 43 ++++++++-- Machines/Atari2600/Atari2600.cpp | 16 +++- Machines/ColecoVision/ColecoVision.cpp | 104 ++++++++++++++++++++++++- Machines/Commodore/Vic-20/Vic20.cpp | 14 +++- 4 files changed, 163 insertions(+), 14 deletions(-) diff --git a/Inputs/Joystick.hpp b/Inputs/Joystick.hpp index 364941e8d..f7c995cf6 100644 --- a/Inputs/Joystick.hpp +++ b/Inputs/Joystick.hpp @@ -21,18 +21,45 @@ class Joystick { public: virtual ~Joystick() {} - enum class DigitalInput { - Up, Down, Left, Right, Fire + struct DigitalInput { + enum Type { + Up, Down, Left, Right, Fire, + Key + } type; + union { + struct { + int index; + } control; + struct { + wchar_t symbol; + } key; + } info; + + DigitalInput(Type type, int index = 0) : type(type) { + info.control.index = index; + } + DigitalInput(wchar_t symbol) : type(Key) { + info.key.symbol = symbol; + } + + bool operator == (const DigitalInput &rhs) { + if(rhs.type != type) return false; + if(rhs.type == Key) { + return rhs.info.key.symbol == info.key.symbol; + } else { + return rhs.info.control.index == info.control.index; + } + } }; + virtual std::vector get_inputs() = 0; + // Host interface. - virtual void set_digital_input(DigitalInput digital_input, bool is_active) = 0; + virtual void set_digital_input(const DigitalInput &digital_input, bool is_active) = 0; virtual void reset_all_inputs() { - set_digital_input(DigitalInput::Up, false); - set_digital_input(DigitalInput::Down, false); - set_digital_input(DigitalInput::Left, false); - set_digital_input(DigitalInput::Right, false); - set_digital_input(DigitalInput::Fire, false); + for(const auto &input: get_inputs()) { + set_digital_input(input, false); + } } }; diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 255d99b45..70aa8753d 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -36,8 +36,18 @@ class Joystick: public Inputs::Joystick { Joystick(Bus *bus, std::size_t shift, std::size_t fire_tia_input) : bus_(bus), shift_(shift), fire_tia_input_(fire_tia_input) {} - void set_digital_input(DigitalInput digital_input, bool is_active) { - switch(digital_input) { + std::vector get_inputs() override { + return { + DigitalInput(DigitalInput::Up), + DigitalInput(DigitalInput::Down), + DigitalInput(DigitalInput::Left), + DigitalInput(DigitalInput::Right), + DigitalInput(DigitalInput::Fire) + }; + } + + void set_digital_input(const DigitalInput &digital_input, bool is_active) override { + switch(digital_input.type) { case DigitalInput::Up: bus_->mos6532_.update_port_input(0, 0x10 >> shift_, is_active); break; case DigitalInput::Down: bus_->mos6532_.update_port_input(0, 0x20 >> shift_, is_active); break; case DigitalInput::Left: bus_->mos6532_.update_port_input(0, 0x40 >> shift_, is_active); break; @@ -50,6 +60,8 @@ class Joystick: public Inputs::Joystick { else bus_->tia_input_value_[fire_tia_input_] |= 0x80; break; + + default: break; } } diff --git a/Machines/ColecoVision/ColecoVision.cpp b/Machines/ColecoVision/ColecoVision.cpp index d38f0dd9b..c8593b4cf 100644 --- a/Machines/ColecoVision/ColecoVision.cpp +++ b/Machines/ColecoVision/ColecoVision.cpp @@ -12,23 +12,104 @@ #include "../../Components/9918/9918.hpp" -#include "../CRTMachine.hpp" #include "../ConfigurationTarget.hpp" +#include "../CRTMachine.hpp" +#include "../JoystickMachine.hpp" #include "../../ClockReceiver/ForceInline.hpp" namespace Coleco { namespace Vision { +class Joystick: public Inputs::Joystick { + public: + std::vector get_inputs() override { + return { + DigitalInput(DigitalInput::Up), + DigitalInput(DigitalInput::Down), + DigitalInput(DigitalInput::Left), + DigitalInput(DigitalInput::Right), + + DigitalInput(DigitalInput::Fire, 0), + DigitalInput(DigitalInput::Fire, 1), + + DigitalInput('0'), DigitalInput('1'), DigitalInput('2'), + DigitalInput('3'), DigitalInput('4'), DigitalInput('5'), + DigitalInput('6'), DigitalInput('7'), DigitalInput('8'), + DigitalInput('9'), DigitalInput('*'), DigitalInput('#'), + }; + } + + void set_digital_input(const DigitalInput &digital_input, bool is_active) override { + switch(digital_input.type) { + default: return; + + case DigitalInput::Key: + if(!is_active) keypad_ |= 0xf; + else { + uint8_t mask = 0xf; + switch(digital_input.info.key.symbol) { + case '0': mask = 0x5; break; + case '1': mask = 0xb; break; + case '2': mask = 0xe; break; + case '3': mask = 0x3; break; + case '4': mask = 0x4; break; + case '5': mask = 0xc; break; + case '6': mask = 0x7; break; + case '7': mask = 0xa; break; + case '8': mask = 0x8; break; + case '9': mask = 0xd; break; + case '*': mask = 0x9; break; + case '#': mask = 0x6; break; + default: break; + } + keypad_ = (keypad_ & 0xf0) | mask; + } + break; + + case DigitalInput::Up: if(is_active) direction_ = direction_ &= ~0x08; else direction_ |= 0x08; break; + case DigitalInput::Down: if(is_active) direction_ = direction_ &= ~0x02; else direction_ |= 0x02; break; + case DigitalInput::Left: if(is_active) direction_ = direction_ &= ~0x01; else direction_ |= 0x01; break; + case DigitalInput::Right: if(is_active) direction_ = direction_ &= ~0x04; else direction_ |= 0x04; break; + case DigitalInput::Fire: + switch(digital_input.info.control.index) { + default: break; + case 0: if(is_active) direction_ = direction_ &= ~0x10; else direction_ |= 0x10; break; + case 1: if(is_active) keypad_ = keypad_ &= ~0x10; else keypad_ |= 0x10; break; + } + break; + } + } + + uint8_t get_direction_input() { + return direction_; + } + + uint8_t get_keypad_input() { + return keypad_; + } + + private: + uint8_t direction_ = 0xff; + uint8_t keypad_ = 0xff; +}; + class ConcreteMachine: public Machine, public CPU::Z80::BusHandler, public CRTMachine::Machine, - public ConfigurationTarget::Machine { + public ConfigurationTarget::Machine, + public JoystickMachine::Machine { public: ConcreteMachine() : z80_(*this) { set_clock_rate(3579545); + joysticks_.emplace_back(new Joystick); + joysticks_.emplace_back(new Joystick); + } + + std::vector> &get_joysticks() override { + return joysticks_; } void setup_output(float aspect_ratio) override { @@ -121,6 +202,14 @@ class ConcreteMachine: time_until_interrupt_ = vdp_->get_time_until_interrupt(); break; + case 7: + if(joysticks_in_keypad_mode_) { + *cycle.value = static_cast(joysticks_[address&1].get())->get_keypad_input(); + } else { + *cycle.value = static_cast(joysticks_[address&1].get())->get_direction_input(); + } + break; + default: *cycle.value = 0xff; break; @@ -129,6 +218,10 @@ class ConcreteMachine: case CPU::Z80::PartialMachineCycle::Output: switch((address >> 5) & 7) { + case 4: case 6: + joysticks_in_keypad_mode_ = ((address >> 5) & 7) == 4; + break; + case 5: vdp_->run_for(time_since_vdp_update_.flush()); vdp_->set_register(address, *cycle.value); @@ -136,6 +229,10 @@ class ConcreteMachine: time_until_interrupt_ = vdp_->get_time_until_interrupt(); break; + case 7: + // TODO: write to audio. + break; + default: break; } break; @@ -160,6 +257,9 @@ class ConcreteMachine: std::vector cartridge_; uint8_t ram_[1024]; + std::vector> joysticks_; + bool joysticks_in_keypad_mode_ = false; + HalfCycles time_since_vdp_update_; HalfCycles time_until_interrupt_; }; diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index d0edf7cf0..f047f0780 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -252,9 +252,19 @@ class Joystick: public Inputs::Joystick { user_port_via_port_handler_(user_port_via_port_handler), keyboard_via_port_handler_(keyboard_via_port_handler) {} - void set_digital_input(DigitalInput digital_input, bool is_active) override { + std::vector get_inputs() override { + return { + DigitalInput(DigitalInput::Up), + DigitalInput(DigitalInput::Down), + DigitalInput(DigitalInput::Left), + DigitalInput(DigitalInput::Right), + DigitalInput(DigitalInput::Fire) + }; + } + + void set_digital_input(const DigitalInput &digital_input, bool is_active) override { JoystickInput mapped_input; - switch (digital_input) { + switch(digital_input.type) { default: return; case DigitalInput::Up: mapped_input = Up; break; case DigitalInput::Down: mapped_input = Down; break; From a074ee207110c3723984e7ff1b827f4b72c265a2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 25 Feb 2018 22:47:47 -0500 Subject: [PATCH 05/14] Possibly fixes ColecoVision input mapping. Also provides symbolic input from the Mac. --- Machines/ColecoVision/ColecoVision.cpp | 58 ++++++++++--------- .../Documents/MachineDocument.swift | 12 ++-- .../Mac/Clock Signal/Machine/CSMachine.h | 2 +- .../Mac/Clock Signal/Machine/CSMachine.mm | 11 +++- 4 files changed, 46 insertions(+), 37 deletions(-) diff --git a/Machines/ColecoVision/ColecoVision.cpp b/Machines/ColecoVision/ColecoVision.cpp index c8593b4cf..57c0b25a1 100644 --- a/Machines/ColecoVision/ColecoVision.cpp +++ b/Machines/ColecoVision/ColecoVision.cpp @@ -49,33 +49,33 @@ class Joystick: public Inputs::Joystick { else { uint8_t mask = 0xf; switch(digital_input.info.key.symbol) { - case '0': mask = 0x5; break; - case '1': mask = 0xb; break; - case '2': mask = 0xe; break; - case '3': mask = 0x3; break; - case '4': mask = 0x4; break; - case '5': mask = 0xc; break; - case '6': mask = 0x7; break; - case '7': mask = 0xa; break; - case '8': mask = 0x8; break; - case '9': mask = 0xd; break; - case '*': mask = 0x9; break; + case '8': mask = 0x1; break; + case '4': mask = 0x2; break; + case '5': mask = 0x3; break; + case '7': mask = 0x5; break; case '#': mask = 0x6; break; + case '2': mask = 0x7; break; + case '*': mask = 0x9; break; + case '0': mask = 0xa; break; + case '9': mask = 0xb; break; + case '3': mask = 0xc; break; + case '1': mask = 0xd; break; + case '6': mask = 0xe; break; default: break; } - keypad_ = (keypad_ & 0xf0) | mask; + keypad_ = (keypad_ & 0xf0) | (mask ^ 0xf); } break; - case DigitalInput::Up: if(is_active) direction_ = direction_ &= ~0x08; else direction_ |= 0x08; break; - case DigitalInput::Down: if(is_active) direction_ = direction_ &= ~0x02; else direction_ |= 0x02; break; - case DigitalInput::Left: if(is_active) direction_ = direction_ &= ~0x01; else direction_ |= 0x01; break; - case DigitalInput::Right: if(is_active) direction_ = direction_ &= ~0x04; else direction_ |= 0x04; break; + case DigitalInput::Up: if(is_active) direction_ &= ~0x01; else direction_ |= 0x01; break; + case DigitalInput::Right: if(is_active) direction_ &= ~0x02; else direction_ |= 0x02; break; + case DigitalInput::Down: if(is_active) direction_ &= ~0x04; else direction_ |= 0x04; break; + case DigitalInput::Left: if(is_active) direction_ &= ~0x08; else direction_ |= 0x08; break; case DigitalInput::Fire: switch(digital_input.info.control.index) { default: break; - case 0: if(is_active) direction_ = direction_ &= ~0x10; else direction_ |= 0x10; break; - case 1: if(is_active) keypad_ = keypad_ &= ~0x10; else keypad_ |= 0x10; break; + case 0: if(is_active) direction_ &= ~0x40; else direction_ |= 0x40; break; + case 1: if(is_active) keypad_ &= ~0x40; else keypad_ |= 0x40; break; } break; } @@ -202,13 +202,15 @@ class ConcreteMachine: time_until_interrupt_ = vdp_->get_time_until_interrupt(); break; - case 7: + case 7: { + const std::size_t joystick_id = (address&2) >> 1; + Joystick *joystick = static_cast(joysticks_[joystick_id].get()); if(joysticks_in_keypad_mode_) { - *cycle.value = static_cast(joysticks_[address&1].get())->get_keypad_input(); + *cycle.value = joystick->get_keypad_input(); } else { - *cycle.value = static_cast(joysticks_[address&1].get())->get_direction_input(); + *cycle.value = joystick->get_direction_input(); } - break; + } break; default: *cycle.value = 0xff; @@ -216,10 +218,11 @@ class ConcreteMachine: } break; - case CPU::Z80::PartialMachineCycle::Output: - switch((address >> 5) & 7) { + case CPU::Z80::PartialMachineCycle::Output: { + const int eighth = (address >> 5) & 7; + switch(eighth) { case 4: case 6: - joysticks_in_keypad_mode_ = ((address >> 5) & 7) == 4; + joysticks_in_keypad_mode_ = eighth == 4; break; case 5: @@ -235,10 +238,9 @@ class ConcreteMachine: default: break; } - break; + } break; - default: - break; + default: break; } time_since_vdp_update_ += cycle.length; diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 95076a01d..0c24166bd 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -209,17 +209,17 @@ class MachineDocument: } func keyDown(_ event: NSEvent) { - self.machine.setKey(event.keyCode, isPressed: true) + self.machine.setKey(event.keyCode, characters: event.characters, isPressed: true) } func keyUp(_ event: NSEvent) { - self.machine.setKey(event.keyCode, isPressed: false) + self.machine.setKey(event.keyCode, characters: event.characters, isPressed: false) } func flagsChanged(_ newModifiers: NSEvent) { - self.machine.setKey(VK_Shift, isPressed: newModifiers.modifierFlags.contains(.shift)) - self.machine.setKey(VK_Control, isPressed: newModifiers.modifierFlags.contains(.control)) - self.machine.setKey(VK_Command, isPressed: newModifiers.modifierFlags.contains(.command)) - self.machine.setKey(VK_Option, isPressed: newModifiers.modifierFlags.contains(.option)) + self.machine.setKey(VK_Shift, characters: nil, isPressed: newModifiers.modifierFlags.contains(.shift)) + self.machine.setKey(VK_Control, characters: nil, isPressed: newModifiers.modifierFlags.contains(.control)) + self.machine.setKey(VK_Command, characters: nil, isPressed: newModifiers.modifierFlags.contains(.command)) + self.machine.setKey(VK_Option, characters: nil, isPressed: newModifiers.modifierFlags.contains(.option)) } } diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h index 86ec34381..a9be3056b 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h @@ -41,7 +41,7 @@ - (void)setView:(CSOpenGLView *)view aspectRatio:(float)aspectRatio; - (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty; -- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed; +- (void)setKey:(uint16_t)key characters:(NSString *)characters isPressed:(BOOL)isPressed; - (void)clearAllKeys; @property (nonatomic, strong) CSAudioQueue *audioQueue; diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm index 4f6e41745..5eb669aa0 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm @@ -192,7 +192,7 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg } } -- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed { +- (void)setKey:(uint16_t)key characters:(NSString *)characters isPressed:(BOOL)isPressed { auto keyboard_machine = _machine->keyboard_machine(); if(keyboard_machine) { @synchronized(self) { @@ -267,8 +267,15 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg case VK_RightArrow: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Right, isPressed); break; case VK_UpArrow: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Up, isPressed); break; case VK_DownArrow: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Down, isPressed); break; + case VK_Space: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Fire, isPressed); break; + case VK_ANSI_A: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput(Inputs::Joystick::DigitalInput::Fire, 0), isPressed); break; + case VK_ANSI_S: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput(Inputs::Joystick::DigitalInput::Fire, 1), isPressed); break; default: - joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Fire, isPressed); break; + if(characters) { + joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput([characters characterAtIndex:0]), isPressed); + } else { + joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Fire, isPressed); + } break; } } From 0ad267664022d13d866fd7151d9870c7458c0791 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 26 Feb 2018 22:04:34 -0500 Subject: [PATCH 06/14] Adds a class for the SN76489 and wires it into the ColecoVision. --- Components/SN76489/SN76489.cpp | 16 ++++++++++ Components/SN76489/SN76489.hpp | 31 +++++++++++++++++++ Machines/ColecoVision/ColecoVision.cpp | 23 ++++++++++++-- .../Clock Signal.xcodeproj/project.pbxproj | 20 ++++++++++++ 4 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 Components/SN76489/SN76489.cpp create mode 100644 Components/SN76489/SN76489.hpp diff --git a/Components/SN76489/SN76489.cpp b/Components/SN76489/SN76489.cpp new file mode 100644 index 000000000..fb49704e4 --- /dev/null +++ b/Components/SN76489/SN76489.cpp @@ -0,0 +1,16 @@ +// +// SN76489.cpp +// Clock Signal +// +// Created by Thomas Harte on 26/02/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#include "SN76489.hpp" + +using namespace TI; + +SN76489::SN76489(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {} + +void SN76489::write(uint8_t value) { +} diff --git a/Components/SN76489/SN76489.hpp b/Components/SN76489/SN76489.hpp new file mode 100644 index 000000000..9212b75de --- /dev/null +++ b/Components/SN76489/SN76489.hpp @@ -0,0 +1,31 @@ +// +// SN76489.hpp +// Clock Signal +// +// Created by Thomas Harte on 26/02/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#ifndef SN76489_hpp +#define SN76489_hpp + +#include "../../Outputs/Speaker/Implementation/SampleSource.hpp" +#include "../../Concurrency/AsyncTaskQueue.hpp" + +namespace TI { + +class SN76489: public Outputs::Speaker::SampleSource { + public: + /// Creates a new SN76489. + SN76489(Concurrency::DeferringAsyncTaskQueue &task_queue); + + /// Writes a new value to the SN76489. + void write(uint8_t value); + + private: + Concurrency::DeferringAsyncTaskQueue &task_queue_; +}; + +} + +#endif /* SN76489_hpp */ diff --git a/Machines/ColecoVision/ColecoVision.cpp b/Machines/ColecoVision/ColecoVision.cpp index 57c0b25a1..375359c1d 100644 --- a/Machines/ColecoVision/ColecoVision.cpp +++ b/Machines/ColecoVision/ColecoVision.cpp @@ -11,6 +11,7 @@ #include "../../Processors/Z80/Z80.hpp" #include "../../Components/9918/9918.hpp" +#include "../../Components/SN76489/SN76489.hpp" #include "../ConfigurationTarget.hpp" #include "../CRTMachine.hpp" @@ -18,6 +19,8 @@ #include "../../ClockReceiver/ForceInline.hpp" +#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" + namespace Coleco { namespace Vision { @@ -102,7 +105,10 @@ class ConcreteMachine: public JoystickMachine::Machine { public: - ConcreteMachine() : z80_(*this) { + ConcreteMachine() : + z80_(*this), + sn76489_(audio_queue_), + speaker_(sn76489_) { set_clock_rate(3579545); joysticks_.emplace_back(new Joystick); joysticks_.emplace_back(new Joystick); @@ -233,7 +239,8 @@ class ConcreteMachine: break; case 7: - // TODO: write to audio. + update_audio(); + sn76489_.write(*cycle.value); break; default: break; @@ -244,17 +251,28 @@ class ConcreteMachine: } time_since_vdp_update_ += cycle.length; + time_since_sn76489_update_ += cycle.length; return HalfCycles(0); } void flush() { vdp_->run_for(time_since_vdp_update_.flush()); + update_audio(); + audio_queue_.perform(); } private: + void update_audio() { + speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(2))); + } + CPU::Z80::Processor z80_; std::unique_ptr vdp_; + Concurrency::DeferringAsyncTaskQueue audio_queue_; + TI::SN76489 sn76489_; + Outputs::Speaker::LowpassSpeaker speaker_; + std::vector bios_; std::vector cartridge_; uint8_t ram_[1024]; @@ -263,6 +281,7 @@ class ConcreteMachine: bool joysticks_in_keypad_mode_ = false; HalfCycles time_since_vdp_update_; + HalfCycles time_since_sn76489_update_; HalfCycles time_until_interrupt_; }; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 290153c0a..0bc7b7618 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -299,6 +299,10 @@ 4BAE495920328897004BE78E /* ZX8081OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */; }; 4BAF2B4E2004580C00480230 /* DMK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAF2B4C2004580C00480230 /* DMK.cpp */; }; 4BAF2B4F2004580C00480230 /* DMK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAF2B4C2004580C00480230 /* DMK.cpp */; }; + 4BB0A65B2044FD3000FB3688 /* SN76489.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB0A6592044FD3000FB3688 /* SN76489.cpp */; }; + 4BB0A65C2044FD3000FB3688 /* SN76489.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB0A6592044FD3000FB3688 /* SN76489.cpp */; }; + 4BB0A65D2045009000FB3688 /* ColecoVision.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A90E42041097C008514A2 /* ColecoVision.cpp */; }; + 4BB0A65E204500A900FB3688 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7A90EC20410A85008514A2 /* StaticAnalyser.cpp */; }; 4BB17D4E1ED7909F00ABD1E1 /* tests.expected.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */; }; 4BB17D4F1ED7909F00ABD1E1 /* tests.in.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */; }; 4BB298F11B587D8400A49093 /* start in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E51B587D8300A49093 /* start */; }; @@ -984,6 +988,8 @@ 4BAF2B4C2004580C00480230 /* DMK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DMK.cpp; sourceTree = ""; }; 4BAF2B4D2004580C00480230 /* DMK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DMK.hpp; sourceTree = ""; }; 4BB06B211F316A3F00600C7A /* ForceInline.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ForceInline.hpp; sourceTree = ""; }; + 4BB0A6592044FD3000FB3688 /* SN76489.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SN76489.cpp; sourceTree = ""; }; + 4BB0A65A2044FD3000FB3688 /* SN76489.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SN76489.hpp; sourceTree = ""; }; 4BB146C61F49D7D700253439 /* Sleeper.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sleeper.hpp; sourceTree = ""; }; 4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.expected.json; path = FUSE/tests.expected.json; sourceTree = ""; }; 4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.in.json; path = FUSE/tests.in.json; sourceTree = ""; }; @@ -2267,6 +2273,15 @@ path = Disk; sourceTree = ""; }; + 4BB0A6582044FD3000FB3688 /* SN76489 */ = { + isa = PBXGroup; + children = ( + 4BB0A6592044FD3000FB3688 /* SN76489.cpp */, + 4BB0A65A2044FD3000FB3688 /* SN76489.hpp */, + ); + path = SN76489; + sourceTree = ""; + }; 4BB297E41B587D8300A49093 /* Wolfgang Lorenz 6502 test suite */ = { isa = PBXGroup; children = ( @@ -2779,6 +2794,7 @@ 4B0E04F71FC9F2C800F43484 /* 9918 */, 4B4A762D1DB1A35C007AAE2E /* AY38910 */, 4B4B1A39200198C900A0F866 /* KonamiSCC */, + 4BB0A6582044FD3000FB3688 /* SN76489 */, ); name = Components; path = ../../Components; @@ -3421,6 +3437,7 @@ 4B894529201967B4007DE474 /* Disk.cpp in Sources */, 4B055AEA1FAE9B990060FFFF /* 6502Storage.cpp in Sources */, 4B055AA71FAE85EF0060FFFF /* SegmentParser.cpp in Sources */, + 4BB0A65E204500A900FB3688 /* StaticAnalyser.cpp in Sources */, 4B055AC11FAE98DC0060FFFF /* MachineForTarget.cpp in Sources */, 4BBB70A9202014E2002FE009 /* MultiCRTMachine.cpp in Sources */, 4B055AD81FAE9B180060FFFF /* Video.cpp in Sources */, @@ -3531,6 +3548,8 @@ 4B055AEC1FAE9BA20060FFFF /* Z80Base.cpp in Sources */, 4B0E04EB1FC9E78800F43484 /* CAS.cpp in Sources */, 4B055AE31FAE9B6F0060FFFF /* TextureBuilder.cpp in Sources */, + 4BB0A65D2045009000FB3688 /* ColecoVision.cpp in Sources */, + 4BB0A65C2044FD3000FB3688 /* SN76489.cpp in Sources */, 4B055AB91FAE86170060FFFF /* Acorn.cpp in Sources */, 4B055A931FAE85B50060FFFF /* BinaryDump.cpp in Sources */, 4B89452D201967B4007DE474 /* Tape.cpp in Sources */, @@ -3593,6 +3612,7 @@ 4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */, 4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */, 4B4B1A3C200198CA00A0F866 /* KonamiSCC.cpp in Sources */, + 4BB0A65B2044FD3000FB3688 /* SN76489.cpp in Sources */, 4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.mm in Sources */, 4B894532201967B4007DE474 /* 6502.cpp in Sources */, 4BDB61EC203285AE0048AF91 /* Atari2600OptionsPanel.swift in Sources */, From d4df101ab6ccd3733a2379adde39d3103fc1ba10 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 27 Feb 2018 22:25:12 -0500 Subject: [PATCH 07/14] Makes a first attempt at implementing the SN76489. --- Components/AY38910/AY38910.cpp | 1 + Components/SN76489/SN76489.cpp | 125 ++++++++++++++++++++++++- Components/SN76489/SN76489.hpp | 30 +++++- Machines/ColecoVision/ColecoVision.cpp | 25 +++-- 4 files changed, 168 insertions(+), 13 deletions(-) diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp index 205fd39e0..67e74b9a1 100644 --- a/Components/AY38910/AY38910.cpp +++ b/Components/AY38910/AY38910.cpp @@ -108,6 +108,7 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) { } evaluate_output_volume(); + printf("%d ", output_volume_); for(int ic = 0; ic < 8 && c < number_of_samples; ic++) { target[c] = output_volume_; diff --git a/Components/SN76489/SN76489.cpp b/Components/SN76489/SN76489.cpp index fb49704e4..a6e8f3330 100644 --- a/Components/SN76489/SN76489.cpp +++ b/Components/SN76489/SN76489.cpp @@ -8,9 +8,128 @@ #include "SN76489.hpp" +#include + using namespace TI; -SN76489::SN76489(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {} - -void SN76489::write(uint8_t value) { +SN76489::SN76489(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) { + // Build a volume table. + double multiplier = pow(10.0, -0.1); + double volume = 8191.0f; + for(int c = 0; c < 16; ++c) { + volumes_[c] = (int)round(volume); + volume *= multiplier; + } + volumes_[15] = 0; + evaluate_output_volume(); +} + +void SN76489::set_register(uint8_t value) { + task_queue_.defer([value, this] () { + if(value & 0x80) { + active_register_ = value; + } + + const int channel = (active_register_ >> 5)&3; + if(active_register_ & 0x10) { + // latch for volume + channels_[channel].volume = value & 0xf; + evaluate_output_volume(); + } else { + // latch for tone/data + if(channel < 3) { + if(value & 0x80) { + channels_[channel].divider = (channels_[channel].divider & ~0xf) | (value & 0xf); + } else { + channels_[channel].divider = static_cast((channels_[channel].divider & 0xf) | ((value & 0x3f) << 4)); + } + } else { + // writes to the noise register always reset the shifter + noise_shifter_ = shifter_is_16bit_ ? 0x8000 : 0x4000; + + if(value & 4) { + noise_mode_ = shifter_is_16bit_ ? Noise16 : Noise15; + } else { + noise_mode_ = shifter_is_16bit_ ? Periodic16 : Periodic15; + } + + channels_[3].divider = static_cast(0x10 << (value & 3)); + // Special case: if these bits are both set, the noise channel should track channel 2, + // which is marked with a divider of 0xffff. + if(channels_[3].divider == 0x80) channels_[3].divider = 0xffff; + } + } + }); +} + +void SN76489::evaluate_output_volume() { + output_volume_ = static_cast( + channels_[0].level * volumes_[channels_[0].volume] + + channels_[1].level * volumes_[channels_[1].volume] + + channels_[2].level * volumes_[channels_[2].volume] + + channels_[3].level * volumes_[channels_[3].volume] + ); +} + +void SN76489::get_samples(std::size_t number_of_samples, std::int16_t *target) { + // For now: assume a divide by eight. + + std::size_t c = 0; + while((master_divider_&7) && c < number_of_samples) { + target[c] = output_volume_; + master_divider_++; + c++; + } + + while(c < number_of_samples) { + bool did_flip = false; + +#define step_channel(c) \ + if(channels_[c].counter) channels_[c].counter--;\ + else {\ + channels_[c].level ^= 1;\ + channels_[c].counter = channels_[c].divider;\ + did_flip = true;\ + } + + step_channel(0); + step_channel(1); + step_channel(2); + +#undef step_channel + + if(channels_[c].divider != 0xffff) { + if(channels_[3].counter) channels_[3].counter--; + else { + did_flip = true; + channels_[c].counter = channels_[c].divider; + } + } + + if(did_flip) { + channels_[3].level = noise_shifter_ & 1; + int new_bit = channels_[3].level; + switch(noise_mode_) { + default: break; + case Noise15: + new_bit ^= (noise_shifter_ >> 1); + break; + case Noise16: + new_bit ^= (noise_shifter_ >> 3); + break; + } + noise_shifter_ >>= 1; + noise_shifter_ |= (new_bit & 1) << (shifter_is_16bit_ ? 15 : 14); + } + + evaluate_output_volume(); + + for(int ic = 0; ic < 8 && c < number_of_samples; ic++) { + target[c] = output_volume_; + c++; + master_divider_++; + } + } + + master_divider_ &= 7; } diff --git a/Components/SN76489/SN76489.hpp b/Components/SN76489/SN76489.hpp index 9212b75de..409df0428 100644 --- a/Components/SN76489/SN76489.hpp +++ b/Components/SN76489/SN76489.hpp @@ -20,10 +20,38 @@ class SN76489: public Outputs::Speaker::SampleSource { SN76489(Concurrency::DeferringAsyncTaskQueue &task_queue); /// Writes a new value to the SN76489. - void write(uint8_t value); + void set_register(uint8_t value); + + // As per SampleSource. + void get_samples(std::size_t number_of_samples, std::int16_t *target); private: + std::size_t master_divider_ = 0; + int16_t output_volume_ = 0;; + void evaluate_output_volume(); + int volumes_[16]; + Concurrency::DeferringAsyncTaskQueue &task_queue_; + + struct ToneChannel { + // Programmatically-set state; updated by the processor. + uint16_t divider = 0; + uint8_t volume = 0xf; + + // Active state; self-evolving as a function of time. + uint16_t counter = 0; + int level = 0; + } channels_[4]; + enum { + Periodic15, + Periodic16, + Noise15, + Noise16 + } noise_mode_ = Periodic15; + uint16_t noise_shifter_ = 0; + int active_register_ = 0; + + bool shifter_is_16bit_ = false; }; } diff --git a/Machines/ColecoVision/ColecoVision.cpp b/Machines/ColecoVision/ColecoVision.cpp index 375359c1d..91242f97d 100644 --- a/Machines/ColecoVision/ColecoVision.cpp +++ b/Machines/ColecoVision/ColecoVision.cpp @@ -66,7 +66,7 @@ class Joystick: public Inputs::Joystick { case '6': mask = 0xe; break; default: break; } - keypad_ = (keypad_ & 0xf0) | (mask ^ 0xf); + keypad_ = (keypad_ & 0xf0) | mask; } break; @@ -109,6 +109,7 @@ class ConcreteMachine: z80_(*this), sn76489_(audio_queue_), speaker_(sn76489_) { + speaker_.set_input_rate(3579545.0f); // TODO: try to find out whether this is correct. set_clock_rate(3579545); joysticks_.emplace_back(new Joystick); joysticks_.emplace_back(new Joystick); @@ -132,7 +133,7 @@ class ConcreteMachine: } Outputs::Speaker::Speaker *get_speaker() override { - return nullptr; + return &speaker_; } void run_for(const Cycles cycles) override { @@ -171,10 +172,15 @@ class ConcreteMachine: // MARK: Z80::BusHandler forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { + time_since_vdp_update_ += cycle.length; + time_since_sn76489_update_ += cycle.length; + if(time_until_interrupt_ > 0) { time_until_interrupt_ -= cycle.length; if(time_until_interrupt_ <= HalfCycles(0)) { z80_.set_non_maskable_interrupt_line(true, time_until_interrupt_); + update_video(); + time_until_interrupt_ = vdp_->get_time_until_interrupt(); } } @@ -186,8 +192,8 @@ class ConcreteMachine: *cycle.value = bios_[address]; } else if(address >= 0x6000 && address < 0x8000) { *cycle.value = ram_[address & 1023]; - } else if(address >= 0x8000 && address < 0x8000 + cartridge_.size()) { - *cycle.value = cartridge_[address - 0x8000]; + } else if(address >= 0x8000) { + *cycle.value = cartridge_[(address - 0x8000) % cartridge_.size()]; // This probably isn't how 24kb ROMs work? } else { *cycle.value = 0xff; } @@ -202,7 +208,7 @@ class ConcreteMachine: case CPU::Z80::PartialMachineCycle::Input: switch((address >> 5) & 7) { case 5: - vdp_->run_for(time_since_vdp_update_.flush()); + update_video(); *cycle.value = vdp_->get_register(address); z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line()); time_until_interrupt_ = vdp_->get_time_until_interrupt(); @@ -232,7 +238,7 @@ class ConcreteMachine: break; case 5: - vdp_->run_for(time_since_vdp_update_.flush()); + update_video(); vdp_->set_register(address, *cycle.value); z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line()); time_until_interrupt_ = vdp_->get_time_until_interrupt(); @@ -240,7 +246,7 @@ class ConcreteMachine: case 7: update_audio(); - sn76489_.write(*cycle.value); + sn76489_.set_register(*cycle.value); break; default: break; @@ -250,8 +256,6 @@ class ConcreteMachine: default: break; } - time_since_vdp_update_ += cycle.length; - time_since_sn76489_update_ += cycle.length; return HalfCycles(0); } @@ -265,6 +269,9 @@ class ConcreteMachine: void update_audio() { speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(2))); } + void update_video() { + vdp_->run_for(time_since_vdp_update_.flush()); + } CPU::Z80::Processor z80_; std::unique_ptr vdp_; From 5b854d51e75c17c2a4b574d38ed652c137f0ffcb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 27 Feb 2018 22:45:45 -0500 Subject: [PATCH 08/14] Corrects out-of-bounds access. --- Components/SN76489/SN76489.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Components/SN76489/SN76489.cpp b/Components/SN76489/SN76489.cpp index a6e8f3330..2cf081f3c 100644 --- a/Components/SN76489/SN76489.cpp +++ b/Components/SN76489/SN76489.cpp @@ -84,25 +84,25 @@ void SN76489::get_samples(std::size_t number_of_samples, std::int16_t *target) { while(c < number_of_samples) { bool did_flip = false; -#define step_channel(c) \ - if(channels_[c].counter) channels_[c].counter--;\ +#define step_channel(x, s) \ + if(channels_[x].counter) channels_[x].counter--;\ else {\ - channels_[c].level ^= 1;\ - channels_[c].counter = channels_[c].divider;\ - did_flip = true;\ + channels_[x].level ^= 1;\ + channels_[x].counter = channels_[x].divider;\ + s;\ } - step_channel(0); - step_channel(1); - step_channel(2); + step_channel(0, /**/); + step_channel(1, /**/); + step_channel(2, did_flip = true); #undef step_channel - if(channels_[c].divider != 0xffff) { + if(channels_[3].divider != 0xffff) { if(channels_[3].counter) channels_[3].counter--; else { did_flip = true; - channels_[c].counter = channels_[c].divider; + channels_[3].counter = channels_[3].divider; } } From 87760297fcf2a04074c22e4b4f8680ec0362fff1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 27 Feb 2018 22:59:29 -0500 Subject: [PATCH 09/14] Fixes underpumping of SN76489. Audio works now. Though I still need properly to confirm who owns dividers in practice. I think probably all division should be within the SN. --- Machines/ColecoVision/ColecoVision.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/ColecoVision/ColecoVision.cpp b/Machines/ColecoVision/ColecoVision.cpp index 91242f97d..40b569f52 100644 --- a/Machines/ColecoVision/ColecoVision.cpp +++ b/Machines/ColecoVision/ColecoVision.cpp @@ -109,7 +109,7 @@ class ConcreteMachine: z80_(*this), sn76489_(audio_queue_), speaker_(sn76489_) { - speaker_.set_input_rate(3579545.0f); // TODO: try to find out whether this is correct. + speaker_.set_input_rate(3579545.0f / 2.0f); // TODO: try to find out whether this is correct. set_clock_rate(3579545); joysticks_.emplace_back(new Joystick); joysticks_.emplace_back(new Joystick); From 83f73c3f02216266d6e33c426c6d17cfe464be50 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 28 Feb 2018 22:15:22 -0500 Subject: [PATCH 10/14] Installs additional safeguards against unsafe deconstruction. --- Concurrency/AsyncTaskQueue.cpp | 4 ++++ Concurrency/AsyncTaskQueue.hpp | 4 +++- Concurrency/BestEffortUpdater.cpp | 5 +++++ Concurrency/BestEffortUpdater.hpp | 1 + OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift | 1 + 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Concurrency/AsyncTaskQueue.cpp b/Concurrency/AsyncTaskQueue.cpp index 6b8fd90f8..8775c4309 100644 --- a/Concurrency/AsyncTaskQueue.cpp +++ b/Concurrency/AsyncTaskQueue.cpp @@ -80,6 +80,10 @@ void AsyncTaskQueue::flush() { #endif } +DeferringAsyncTaskQueue::~DeferringAsyncTaskQueue() { + perform(); +} + void DeferringAsyncTaskQueue::defer(std::function function) { if(!deferred_tasks_) { deferred_tasks_.reset(new std::list>); diff --git a/Concurrency/AsyncTaskQueue.hpp b/Concurrency/AsyncTaskQueue.hpp index be8517454..577f97b1a 100644 --- a/Concurrency/AsyncTaskQueue.hpp +++ b/Concurrency/AsyncTaskQueue.hpp @@ -30,7 +30,7 @@ namespace Concurrency { class AsyncTaskQueue { public: AsyncTaskQueue(); - ~AsyncTaskQueue(); + virtual ~AsyncTaskQueue(); /*! Adds @c function to the queue. @@ -69,6 +69,8 @@ class AsyncTaskQueue { */ class DeferringAsyncTaskQueue: public AsyncTaskQueue { public: + ~DeferringAsyncTaskQueue(); + /*! Adds a function to the deferral list. diff --git a/Concurrency/BestEffortUpdater.cpp b/Concurrency/BestEffortUpdater.cpp index a3ea15067..96156b742 100644 --- a/Concurrency/BestEffortUpdater.cpp +++ b/Concurrency/BestEffortUpdater.cpp @@ -17,6 +17,11 @@ BestEffortUpdater::BestEffortUpdater() { update_is_ongoing_.clear(); } +BestEffortUpdater::~BestEffortUpdater() { + // Don't allow further deconstruction until the task queue is stopped. + flush(); +} + void BestEffortUpdater::update() { // Perform an update only if one is not currently ongoing. if(!update_is_ongoing_.test_and_set()) { diff --git a/Concurrency/BestEffortUpdater.hpp b/Concurrency/BestEffortUpdater.hpp index 6fb21edc3..c75877dac 100644 --- a/Concurrency/BestEffortUpdater.hpp +++ b/Concurrency/BestEffortUpdater.hpp @@ -26,6 +26,7 @@ namespace Concurrency { class BestEffortUpdater { public: BestEffortUpdater(); + ~BestEffortUpdater(); /// A delegate receives timing cues. struct Delegate { diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 0c24166bd..f94963372 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -106,6 +106,7 @@ class MachineDocument: bestEffortLock.lock() bestEffortUpdater!.delegate = nil + bestEffortUpdater!.flush() bestEffortUpdater = nil bestEffortLock.unlock() From 7890506b16505b8d37eb7565caa879e1af58241a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 28 Feb 2018 22:36:03 -0500 Subject: [PATCH 11/14] Gives the SN76489 its proper dividers and personalities. --- Components/SN76489/SN76489.cpp | 25 ++++++++++++++----- Components/SN76489/SN76489.hpp | 13 +++++++--- Machines/ColecoVision/ColecoVision.cpp | 34 ++++++++++++-------------- 3 files changed, 45 insertions(+), 27 deletions(-) diff --git a/Components/SN76489/SN76489.cpp b/Components/SN76489/SN76489.cpp index 2cf081f3c..495a43607 100644 --- a/Components/SN76489/SN76489.cpp +++ b/Components/SN76489/SN76489.cpp @@ -12,7 +12,7 @@ using namespace TI; -SN76489::SN76489(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) { +SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) { // Build a volume table. double multiplier = pow(10.0, -0.1); double volume = 8191.0f; @@ -22,6 +22,21 @@ SN76489::SN76489(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_ } volumes_[15] = 0; evaluate_output_volume(); + + switch(personality) { + case Personality::SN76494: + master_divider_period_ = 2; + shifter_is_16bit_ = false; + break; + case Personality::SN76489: + master_divider_period_ = 16; + shifter_is_16bit_ = false; + break; + case Personality::SMS: + master_divider_period_ = 16; + shifter_is_16bit_ = true; + break; + } } void SN76489::set_register(uint8_t value) { @@ -72,10 +87,8 @@ void SN76489::evaluate_output_volume() { } void SN76489::get_samples(std::size_t number_of_samples, std::int16_t *target) { - // For now: assume a divide by eight. - std::size_t c = 0; - while((master_divider_&7) && c < number_of_samples) { + while((master_divider_& (master_divider_period_ - 1)) && c < number_of_samples) { target[c] = output_volume_; master_divider_++; c++; @@ -124,12 +137,12 @@ void SN76489::get_samples(std::size_t number_of_samples, std::int16_t *target) { evaluate_output_volume(); - for(int ic = 0; ic < 8 && c < number_of_samples; ic++) { + for(int ic = 0; ic < master_divider_period_ && c < number_of_samples; ++ic) { target[c] = output_volume_; c++; master_divider_++; } } - master_divider_ &= 7; + master_divider_ &= (master_divider_period_ - 1); } diff --git a/Components/SN76489/SN76489.hpp b/Components/SN76489/SN76489.hpp index 409df0428..eb58af331 100644 --- a/Components/SN76489/SN76489.hpp +++ b/Components/SN76489/SN76489.hpp @@ -16,8 +16,14 @@ namespace TI { class SN76489: public Outputs::Speaker::SampleSource { public: + enum class Personality { + SN76489, + SN76494, + SMS + }; + /// Creates a new SN76489. - SN76489(Concurrency::DeferringAsyncTaskQueue &task_queue); + SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue); /// Writes a new value to the SN76489. void set_register(uint8_t value); @@ -26,8 +32,9 @@ class SN76489: public Outputs::Speaker::SampleSource { void get_samples(std::size_t number_of_samples, std::int16_t *target); private: - std::size_t master_divider_ = 0; - int16_t output_volume_ = 0;; + int master_divider_ = 0; + int master_divider_period_ = 16; + int16_t output_volume_ = 0; void evaluate_output_volume(); int volumes_[16]; diff --git a/Machines/ColecoVision/ColecoVision.cpp b/Machines/ColecoVision/ColecoVision.cpp index 40b569f52..77ecded30 100644 --- a/Machines/ColecoVision/ColecoVision.cpp +++ b/Machines/ColecoVision/ColecoVision.cpp @@ -107,9 +107,10 @@ class ConcreteMachine: public: ConcreteMachine() : z80_(*this), - sn76489_(audio_queue_), + sn76489_(TI::SN76489::Personality::SN76489, audio_queue_), speaker_(sn76489_) { - speaker_.set_input_rate(3579545.0f / 2.0f); // TODO: try to find out whether this is correct. +// speaker_.set_input_rate(3579545.0f); + speaker_.set_input_rate(3579545.0f / 2.0f); set_clock_rate(3579545); joysticks_.emplace_back(new Joystick); joysticks_.emplace_back(new Joystick); @@ -158,9 +159,7 @@ class ConcreteMachine: bool set_rom_fetcher(const std::function>>(const std::string &machine, const std::vector &names)> &roms_with_names) override { auto roms = roms_with_names( "ColecoVision", - { - "coleco.rom" - }); + { "coleco.rom" }); if(!roms[0]) return false; @@ -172,18 +171,6 @@ class ConcreteMachine: // MARK: Z80::BusHandler forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { - time_since_vdp_update_ += cycle.length; - time_since_sn76489_update_ += cycle.length; - - if(time_until_interrupt_ > 0) { - time_until_interrupt_ -= cycle.length; - if(time_until_interrupt_ <= HalfCycles(0)) { - z80_.set_non_maskable_interrupt_line(true, time_until_interrupt_); - update_video(); - time_until_interrupt_ = vdp_->get_time_until_interrupt(); - } - } - uint16_t address = cycle.address ? *cycle.address : 0x0000; switch(cycle.operation) { case CPU::Z80::PartialMachineCycle::ReadOpcode: @@ -256,11 +243,21 @@ class ConcreteMachine: default: break; } + time_since_vdp_update_ += cycle.length; + time_since_sn76489_update_ += cycle.length; + + if(time_until_interrupt_ > 0) { + time_until_interrupt_ -= cycle.length; + if(time_until_interrupt_ <= HalfCycles(0)) { + z80_.set_non_maskable_interrupt_line(true, time_until_interrupt_); + } + } + return HalfCycles(0); } void flush() { - vdp_->run_for(time_since_vdp_update_.flush()); + update_video(); update_audio(); audio_queue_.perform(); } @@ -268,6 +265,7 @@ class ConcreteMachine: private: void update_audio() { speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(2))); +// speaker_.run_for(audio_queue_, time_since_sn76489_update_.cycles()); } void update_video() { vdp_->run_for(time_since_vdp_update_.flush()); From 86239469e729a7c2ddb2ff32d5866d0d232b1649 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 1 Mar 2018 18:51:05 -0500 Subject: [PATCH 12/14] Allows SN76489 consumers to apply an additional divider that reduces computation. --- Components/SN76489/SN76489.cpp | 7 ++++++- Components/SN76489/SN76489.hpp | 2 +- Machines/ColecoVision/ColecoVision.cpp | 14 ++++++++------ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Components/SN76489/SN76489.cpp b/Components/SN76489/SN76489.cpp index 495a43607..a321b3898 100644 --- a/Components/SN76489/SN76489.cpp +++ b/Components/SN76489/SN76489.cpp @@ -8,11 +8,12 @@ #include "SN76489.hpp" +#include #include using namespace TI; -SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) { +SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider) : task_queue_(task_queue) { // Build a volume table. double multiplier = pow(10.0, -0.1); double volume = 8191.0f; @@ -37,6 +38,10 @@ SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue & shifter_is_16bit_ = true; break; } + + assert((master_divider_period_ % additional_divider) == 0); + assert(additional_divider < master_divider_period_); + master_divider_period_ /= additional_divider; } void SN76489::set_register(uint8_t value) { diff --git a/Components/SN76489/SN76489.hpp b/Components/SN76489/SN76489.hpp index eb58af331..12f2b2d3f 100644 --- a/Components/SN76489/SN76489.hpp +++ b/Components/SN76489/SN76489.hpp @@ -23,7 +23,7 @@ class SN76489: public Outputs::Speaker::SampleSource { }; /// Creates a new SN76489. - SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue); + SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider = 1); /// Writes a new value to the SN76489. void set_register(uint8_t value); diff --git a/Machines/ColecoVision/ColecoVision.cpp b/Machines/ColecoVision/ColecoVision.cpp index 77ecded30..d742baa74 100644 --- a/Machines/ColecoVision/ColecoVision.cpp +++ b/Machines/ColecoVision/ColecoVision.cpp @@ -21,6 +21,10 @@ #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" +namespace { +const int sn76489_divider = 4; +} + namespace Coleco { namespace Vision { @@ -107,10 +111,9 @@ class ConcreteMachine: public: ConcreteMachine() : z80_(*this), - sn76489_(TI::SN76489::Personality::SN76489, audio_queue_), + sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, sn76489_divider), speaker_(sn76489_) { -// speaker_.set_input_rate(3579545.0f); - speaker_.set_input_rate(3579545.0f / 2.0f); + speaker_.set_input_rate(3579545.0f / static_cast(sn76489_divider)); set_clock_rate(3579545); joysticks_.emplace_back(new Joystick); joysticks_.emplace_back(new Joystick); @@ -176,7 +179,7 @@ class ConcreteMachine: case CPU::Z80::PartialMachineCycle::ReadOpcode: case CPU::Z80::PartialMachineCycle::Read: if(address < 0x2000) { - *cycle.value = bios_[address]; + *cycle.value = bios_[address & 0x1fff]; } else if(address >= 0x6000 && address < 0x8000) { *cycle.value = ram_[address & 1023]; } else if(address >= 0x8000) { @@ -264,8 +267,7 @@ class ConcreteMachine: private: void update_audio() { - speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(2))); -// speaker_.run_for(audio_queue_, time_since_sn76489_update_.cycles()); + speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider))); } void update_video() { vdp_->run_for(time_since_vdp_update_.flush()); From b02e4fbbf64f33d9a264e064ba8cace1308156ad Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 1 Mar 2018 22:04:56 -0500 Subject: [PATCH 13/14] Corrects NMI receipt to be genuinely edge triggered. Previously a caller that signalled NMI set multiple times would trigger multiple NMIs. --- Processors/Z80/Implementation/Z80Implementation.hpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Processors/Z80/Implementation/Z80Implementation.hpp b/Processors/Z80/Implementation/Z80Implementation.hpp index b1e48be49..c3d8d9557 100644 --- a/Processors/Z80/Implementation/Z80Implementation.hpp +++ b/Processors/Z80/Implementation/Z80Implementation.hpp @@ -1003,11 +1003,13 @@ bool ProcessorBase::get_interrupt_line() { void ProcessorBase::set_non_maskable_interrupt_line(bool value, HalfCycles offset) { // NMIs are edge triggered and cannot be masked. - nmi_line_ = value; - if(value) { - request_status_ |= Interrupt::NMI; - if(offset.as_int() < 0) { - last_request_status_ |= Interrupt::NMI; + if(nmi_line_ != value) { + nmi_line_ = value; + if(value) { + request_status_ |= Interrupt::NMI; + if(offset.as_int() < 0) { + last_request_status_ |= Interrupt::NMI; + } } } } From ba68b7247bd3a1e558df9bc6d7b19b6b231da605 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 1 Mar 2018 22:19:50 -0500 Subject: [PATCH 14/14] Adds latest files to SConstruct. --- OSBindings/SDL/SConstruct | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OSBindings/SDL/SConstruct b/OSBindings/SDL/SConstruct index e2559b148..4909e6beb 100644 --- a/OSBindings/SDL/SConstruct +++ b/OSBindings/SDL/SConstruct @@ -18,6 +18,7 @@ SOURCES += glob.glob('../../Analyser/Static/*.cpp') SOURCES += glob.glob('../../Analyser/Static/Acorn/*.cpp') SOURCES += glob.glob('../../Analyser/Static/AmstradCPC/*.cpp') SOURCES += glob.glob('../../Analyser/Static/Atari/*.cpp') +SOURCES += glob.glob('../../Analyser/Static/Coleco/*.cpp') SOURCES += glob.glob('../../Analyser/Static/Commodore/*.cpp') SOURCES += glob.glob('../../Analyser/Static/Disassembler/*.cpp') SOURCES += glob.glob('../../Analyser/Static/MSX/*.cpp') @@ -32,6 +33,7 @@ SOURCES += glob.glob('../../Components/9918/*.cpp') SOURCES += glob.glob('../../Components/9918/Implementation/*.cpp') SOURCES += glob.glob('../../Components/AY38910/*.cpp') SOURCES += glob.glob('../../Components/KonamiSCC/*.cpp') +SOURCES += glob.glob('../../Components/SN76489/*.cpp') SOURCES += glob.glob('../../Concurrency/*.cpp') @@ -42,6 +44,7 @@ SOURCES += glob.glob('../../Inputs/*.cpp') SOURCES += glob.glob('../../Machines/*.cpp') SOURCES += glob.glob('../../Machines/AmstradCPC/*.cpp') SOURCES += glob.glob('../../Machines/Atari2600/*.cpp') +SOURCES += glob.glob('../../Machines/ColecoVision/*.cpp') SOURCES += glob.glob('../../Machines/Commodore/*.cpp') SOURCES += glob.glob('../../Machines/Commodore/1540/Implementation/*.cpp') SOURCES += glob.glob('../../Machines/Commodore/Vic-20/*.cpp')