diff --git a/Analyser/Dynamic/MultiMachine/MultiMachine.cpp b/Analyser/Dynamic/MultiMachine/MultiMachine.cpp index ba6328b31..4f057c3cf 100644 --- a/Analyser/Dynamic/MultiMachine/MultiMachine.cpp +++ b/Analyser/Dynamic/MultiMachine/MultiMachine.cpp @@ -59,6 +59,11 @@ KeyboardMachine::Machine *MultiMachine::keyboard_machine() { } } +MouseMachine::Machine *MultiMachine::mouse_machine() { + // TODO. + return nullptr; +} + Configurable::Device *MultiMachine::configurable_device() { if(has_picked_) { return machines_.front()->configurable_device(); diff --git a/Analyser/Dynamic/MultiMachine/MultiMachine.hpp b/Analyser/Dynamic/MultiMachine/MultiMachine.hpp index e520c6fad..464c80b0d 100644 --- a/Analyser/Dynamic/MultiMachine/MultiMachine.hpp +++ b/Analyser/Dynamic/MultiMachine/MultiMachine.hpp @@ -54,6 +54,7 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De Configurable::Device *configurable_device() override; CRTMachine::Machine *crt_machine() override; JoystickMachine::Machine *joystick_machine() override; + MouseMachine::Machine *mouse_machine() override; KeyboardMachine::Machine *keyboard_machine() override; MediaTarget::Machine *media_target() override; void *raw_pointer() override; diff --git a/Analyser/Machines.hpp b/Analyser/Machines.hpp index fde24ec46..796c376de 100644 --- a/Analyser/Machines.hpp +++ b/Analyser/Machines.hpp @@ -17,6 +17,7 @@ enum class Machine { Atari2600, ColecoVision, Electron, + Macintosh, MasterSystem, MSX, Oric, diff --git a/Analyser/Static/MSX/StaticAnalyser.cpp b/Analyser/Static/MSX/StaticAnalyser.cpp index 9e361c460..79a869ccd 100644 --- a/Analyser/Static/MSX/StaticAnalyser.cpp +++ b/Analyser/Static/MSX/StaticAnalyser.cpp @@ -290,6 +290,7 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &medi target->region = target->media.tapes.empty() ? Target::Region::USA : Target::Region::Europe; // Blindly accept disks for now. + // TODO: how to spot an MSX disk? target->media.disks = media.disks; target->has_disk_drive = !media.disks.empty(); diff --git a/Analyser/Static/Macintosh/StaticAnalyser.cpp b/Analyser/Static/Macintosh/StaticAnalyser.cpp new file mode 100644 index 000000000..3c20dc7be --- /dev/null +++ b/Analyser/Static/Macintosh/StaticAnalyser.cpp @@ -0,0 +1,26 @@ +// +// StaticAnalyser.cpp +// Clock Signal +// +// Created by Thomas Harte on 02/06/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#include "StaticAnalyser.hpp" +#include "Target.hpp" + +Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { + // This analyser can comprehend disks only. + if(media.disks.empty()) return {}; + + // If there is at least one disk, wave it through. + Analyser::Static::TargetList targets; + + using Target = Analyser::Static::Macintosh::Target; + auto *target = new Target; + target->machine = Analyser::Machine::Macintosh; + target->media = media; + targets.push_back(std::unique_ptr(target)); + + return targets; +} diff --git a/Analyser/Static/Macintosh/StaticAnalyser.hpp b/Analyser/Static/Macintosh/StaticAnalyser.hpp new file mode 100644 index 000000000..8b40c0120 --- /dev/null +++ b/Analyser/Static/Macintosh/StaticAnalyser.hpp @@ -0,0 +1,27 @@ +// +// StaticAnalyser.hpp +// Clock Signal +// +// Created by Thomas Harte on 02/06/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef Analyser_Static_Macintosh_StaticAnalyser_hpp +#define Analyser_Static_Macintosh_StaticAnalyser_hpp + +#include "../StaticAnalyser.hpp" +#include "../../../Storage/TargetPlatforms.hpp" +#include + +namespace Analyser { +namespace Static { +namespace Macintosh { + +TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms); + +} +} +} + + +#endif /* Analyser_Static_Macintosh_StaticAnalyser_hpp */ diff --git a/Analyser/Static/Macintosh/Target.hpp b/Analyser/Static/Macintosh/Target.hpp new file mode 100644 index 000000000..fd2434ca9 --- /dev/null +++ b/Analyser/Static/Macintosh/Target.hpp @@ -0,0 +1,31 @@ +// +// Target.hpp +// Clock Signal +// +// Created by Thomas Harte on 03/06/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef Analyser_Static_Macintosh_Target_h +#define Analyser_Static_Macintosh_Target_h + +namespace Analyser { +namespace Static { +namespace Macintosh { + +struct Target: public ::Analyser::Static::Target { + enum class Model { + Mac128k, + Mac512k, + Mac512ke, + MacPlus + }; + + Model model = Model::Mac512ke; +}; + +} +} +} + +#endif /* Analyser_Static_Macintosh_Target_h */ diff --git a/Analyser/Static/StaticAnalyser.cpp b/Analyser/Static/StaticAnalyser.cpp index a65e9582b..fa25cdadc 100644 --- a/Analyser/Static/StaticAnalyser.cpp +++ b/Analyser/Static/StaticAnalyser.cpp @@ -21,6 +21,7 @@ #include "Coleco/StaticAnalyser.hpp" #include "Commodore/StaticAnalyser.hpp" #include "DiskII/StaticAnalyser.hpp" +#include "Macintosh/StaticAnalyser.hpp" #include "MSX/StaticAnalyser.hpp" #include "Oric/StaticAnalyser.hpp" #include "Sega/StaticAnalyser.hpp" @@ -35,12 +36,14 @@ #include "../../Storage/Disk/DiskImage/Formats/AppleDSK.hpp" #include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp" #include "../../Storage/Disk/DiskImage/Formats/D64.hpp" +#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp" #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/MSXDSK.hpp" #include "../../Storage/Disk/DiskImage/Formats/NIB.hpp" #include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp" +#include "../../Storage/Disk/DiskImage/Formats/PlusTooBIN.hpp" #include "../../Storage/Disk/DiskImage/Formats/SSD.hpp" #include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp" @@ -85,34 +88,38 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: TryInsert(list, class, platforms) \ } - Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80 - Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81 - Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26 - Format("adf", result.disks, Disk::DiskImageHolder, TargetPlatform::Acorn) // ADF - Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // 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 - Format("do", result.disks, Disk::DiskImageHolder, TargetPlatform::DiskII) // DO - Format("dsd", result.disks, Disk::DiskImageHolder, TargetPlatform::Acorn) // DSD - Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC) - Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::DiskII) // DSK (Apple) - Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::MSX) // DSK (MSX) - Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::Oric) // DSK (Oric) - Format("g64", result.disks, Disk::DiskImageHolder, TargetPlatform::Commodore) // G64 + Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80 + Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81 + Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26 + Format("adf", result.disks, Disk::DiskImageHolder, TargetPlatform::Acorn) // ADF +// Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN (cartridge dump) + Format("bin", result.disks, Disk::DiskImageHolder, TargetPlatform::Macintosh) // BIN (PlusToo disk image) + 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 + Format("do", result.disks, Disk::DiskImageHolder, TargetPlatform::DiskII) // DO + Format("dsd", result.disks, Disk::DiskImageHolder, TargetPlatform::Acorn) // DSD + Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC) + Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::DiskII) // DSK (Apple II) + Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::Macintosh) // DSK (Macintosh) + Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::MSX) // DSK (MSX) + Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::Oric) // DSK (Oric) + Format("g64", result.disks, Disk::DiskImageHolder, TargetPlatform::Commodore) // G64 Format( "hfe", result.disks, Disk::DiskImageHolder, TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric) // HFE (TODO: switch to AllDisk once the MSX stops being so greedy) - 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 - Format("po", result.disks, Disk::DiskImageHolder, TargetPlatform::DiskII) // PO - Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81 + 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("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 + Format("po", result.disks, Disk::DiskImageHolder, TargetPlatform::DiskII) // PO + Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81 // PRG if(extension == "prg") { @@ -129,16 +136,16 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: Format( "rom", result.cartridges, Cartridge::BinaryDump, - TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX) // ROM - Format("sg", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SG - Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SMS - 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) - Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX - Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX - Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape) - Format("woz", result.disks, Disk::DiskImageHolder, TargetPlatform::DiskII) // WOZ + TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX) // ROM + Format("sg", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SG + Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SMS + 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) + Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX + Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX + Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape) + Format("woz", result.disks, Disk::DiskImageHolder, TargetPlatform::DiskII) // WOZ #undef Format #undef Insert @@ -173,9 +180,10 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) { if(potential_platforms & TargetPlatform::ColecoVision) Append(Coleco); if(potential_platforms & TargetPlatform::Commodore) Append(Commodore); if(potential_platforms & TargetPlatform::DiskII) Append(DiskII); - if(potential_platforms & TargetPlatform::Sega) Append(Sega); + if(potential_platforms & TargetPlatform::Macintosh) Append(Macintosh); if(potential_platforms & TargetPlatform::MSX) Append(MSX); if(potential_platforms & TargetPlatform::Oric) Append(Oric); + if(potential_platforms & TargetPlatform::Sega) Append(Sega); if(potential_platforms & TargetPlatform::ZX8081) Append(ZX8081); #undef Append diff --git a/ClockReceiver/ClockReceiver.hpp b/ClockReceiver/ClockReceiver.hpp index c6cd23f23..ecfccec27 100644 --- a/ClockReceiver/ClockReceiver.hpp +++ b/ClockReceiver/ClockReceiver.hpp @@ -9,6 +9,8 @@ #ifndef ClockReceiver_hpp #define ClockReceiver_hpp +#include "ForceInline.hpp" + /* Informal pattern for all classes that run from a clock cycle: @@ -52,79 +54,92 @@ */ template class WrappedInt { public: - constexpr WrappedInt(int l) : length_(l) {} - constexpr WrappedInt() : length_(0) {} + forceinline constexpr WrappedInt(int l) : length_(l) {} + forceinline constexpr WrappedInt() : length_(0) {} - T &operator =(const T &rhs) { + forceinline T &operator =(const T &rhs) { length_ = rhs.length_; return *this; } - T &operator +=(const T &rhs) { + forceinline T &operator +=(const T &rhs) { length_ += rhs.length_; return *static_cast(this); } - T &operator -=(const T &rhs) { + forceinline T &operator -=(const T &rhs) { length_ -= rhs.length_; return *static_cast(this); } - T &operator ++() { + forceinline T &operator ++() { ++ length_; return *static_cast(this); } - T &operator ++(int) { + forceinline T &operator ++(int) { length_ ++; return *static_cast(this); } - T &operator --() { + forceinline T &operator --() { -- length_; return *static_cast(this); } - T &operator --(int) { + forceinline T &operator --(int) { length_ --; return *static_cast(this); } - T &operator %=(const T &rhs) { + forceinline T &operator *=(const T &rhs) { + length_ *= rhs.length_; + return *static_cast(this); + } + + forceinline T &operator /=(const T &rhs) { + length_ /= rhs.length_; + return *static_cast(this); + } + + forceinline T &operator %=(const T &rhs) { length_ %= rhs.length_; return *static_cast(this); } - T &operator &=(const T &rhs) { + forceinline T &operator &=(const T &rhs) { length_ &= rhs.length_; return *static_cast(this); } - constexpr T operator +(const T &rhs) const { return T(length_ + rhs.length_); } - constexpr T operator -(const T &rhs) const { return T(length_ - rhs.length_); } + forceinline constexpr T operator +(const T &rhs) const { return T(length_ + rhs.length_); } + forceinline constexpr T operator -(const T &rhs) const { return T(length_ - rhs.length_); } - constexpr T operator %(const T &rhs) const { return T(length_ % rhs.length_); } - constexpr T operator &(const T &rhs) const { return T(length_ & rhs.length_); } + forceinline constexpr T operator *(const T &rhs) const { return T(length_ * rhs.length_); } + forceinline constexpr T operator /(const T &rhs) const { return T(length_ / rhs.length_); } - constexpr T operator -() const { return T(- length_); } + forceinline constexpr T operator %(const T &rhs) const { return T(length_ % rhs.length_); } + forceinline constexpr T operator &(const T &rhs) const { return T(length_ & rhs.length_); } - constexpr bool operator <(const T &rhs) const { return length_ < rhs.length_; } - constexpr bool operator >(const T &rhs) const { return length_ > rhs.length_; } - constexpr bool operator <=(const T &rhs) const { return length_ <= rhs.length_; } - constexpr bool operator >=(const T &rhs) const { return length_ >= rhs.length_; } - constexpr bool operator ==(const T &rhs) const { return length_ == rhs.length_; } - constexpr bool operator !=(const T &rhs) const { return length_ != rhs.length_; } + forceinline constexpr T operator -() const { return T(- length_); } - constexpr bool operator !() const { return !length_; } + forceinline constexpr bool operator <(const T &rhs) const { return length_ < rhs.length_; } + forceinline constexpr bool operator >(const T &rhs) const { return length_ > rhs.length_; } + forceinline constexpr bool operator <=(const T &rhs) const { return length_ <= rhs.length_; } + forceinline constexpr bool operator >=(const T &rhs) const { return length_ >= rhs.length_; } + forceinline constexpr bool operator ==(const T &rhs) const { return length_ == rhs.length_; } + forceinline constexpr bool operator !=(const T &rhs) const { return length_ != rhs.length_; } + + forceinline constexpr bool operator !() const { return !length_; } // bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse - constexpr int as_int() const { return length_; } + forceinline constexpr int as_int() const { return length_; } /*! 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. */ - T divide(const T &divisor) { + forceinline T divide(const T &divisor) { T result(length_ / divisor.length_); length_ %= divisor.length_; return result; @@ -134,7 +149,7 @@ template class WrappedInt { Flushes the value in @c this. The current value is returned, and the internal value is reset to zero. */ - T flush() { + forceinline T flush() { T result(length_); length_ = 0; return result; @@ -150,34 +165,34 @@ template class WrappedInt { /// Describes an integer number of whole cycles: pairs of clock signal transitions. class Cycles: public WrappedInt { public: - constexpr Cycles(int l) : WrappedInt(l) {} - constexpr Cycles() : WrappedInt() {} - constexpr Cycles(const Cycles &cycles) : WrappedInt(cycles.length_) {} + forceinline constexpr Cycles(int l) : WrappedInt(l) {} + forceinline constexpr Cycles() : WrappedInt() {} + forceinline constexpr Cycles(const Cycles &cycles) : WrappedInt(cycles.length_) {} }; /// Describes an integer number of half cycles: single clock signal transitions. class HalfCycles: public WrappedInt { public: - constexpr HalfCycles(int l) : WrappedInt(l) {} - constexpr HalfCycles() : WrappedInt() {} + forceinline constexpr HalfCycles(int l) : WrappedInt(l) {} + forceinline constexpr HalfCycles() : WrappedInt() {} - constexpr HalfCycles(const Cycles cycles) : WrappedInt(cycles.as_int() * 2) {} - constexpr HalfCycles(const HalfCycles &half_cycles) : WrappedInt(half_cycles.length_) {} + forceinline constexpr HalfCycles(const Cycles cycles) : WrappedInt(cycles.as_int() * 2) {} + forceinline constexpr HalfCycles(const HalfCycles &half_cycles) : WrappedInt(half_cycles.length_) {} /// @returns The number of whole cycles completely covered by this span of half cycles. - constexpr Cycles cycles() const { + forceinline constexpr Cycles cycles() const { return Cycles(length_ >> 1); } /// Flushes the whole cycles in @c this, subtracting that many from the total stored here. - Cycles flush_cycles() { + forceinline Cycles flush_cycles() { Cycles result(length_ >> 1); length_ &= 1; return result; } /// Flushes the half cycles in @c this, returning the number stored and setting this total to zero. - HalfCycles flush() { + forceinline HalfCycles flush() { HalfCycles result(length_); length_ = 0; return result; @@ -187,9 +202,9 @@ class HalfCycles: public 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. */ - Cycles divide_cycles(const Cycles &divisor) { - HalfCycles half_divisor = HalfCycles(divisor); - Cycles result(length_ / half_divisor.length_); + forceinline Cycles divide_cycles(const Cycles &divisor) { + const HalfCycles half_divisor = HalfCycles(divisor); + const Cycles result(length_ / half_divisor.length_); length_ %= half_divisor.length_; return result; } @@ -203,7 +218,7 @@ template class HalfClockReceiver: public T { public: using T::T; - inline void run_for(const HalfCycles half_cycles) { + forceinline void run_for(const HalfCycles half_cycles) { half_cycles_ += half_cycles; T::run_for(half_cycles_.flush_cycles()); } diff --git a/Components/6522/6522.hpp b/Components/6522/6522.hpp index 2369c500c..80986d705 100644 --- a/Components/6522/6522.hpp +++ b/Components/6522/6522.hpp @@ -47,6 +47,12 @@ class PortHandler { /// Sets the current logical value of the interrupt line. void set_interrupt_status(bool status) {} + + /// Provides a measure of time elapsed between other calls. + void run_for(HalfCycles duration) {} + + /// Receives passed-on flush() calls from the 6522. + void flush() {} }; /*! @@ -71,26 +77,6 @@ class IRQDelegatePortHandler: public PortHandler { Delegate *delegate_ = nullptr; }; -class MOS6522Base: public MOS6522Storage { - public: - /// Sets the input value of line @c line on port @c port. - void set_control_line_input(Port port, Line line, bool value); - - /// Runs for a specified number of half cycles. - void run_for(const HalfCycles half_cycles); - - /// Runs for a specified number of cycles. - void run_for(const Cycles cycles); - - /// @returns @c true if the IRQ line is currently active; @c false otherwise. - bool get_interrupt_line(); - - private: - inline void do_phase1(); - inline void do_phase2(); - virtual void reevaluate_interrupts() = 0; -}; - /*! Implements a template for emulation of the MOS 6522 Versatile Interface Adaptor ('VIA'). @@ -102,7 +88,7 @@ class MOS6522Base: public MOS6522Storage { Consumers should derive their own curiously-recurring-template-pattern subclass, implementing bus communications as required. */ -template class MOS6522: public MOS6522Base { +template class MOS6522: public MOS6522Storage { public: MOS6522(T &bus_handler) noexcept : bus_handler_(bus_handler) {} MOS6522(const MOS6522 &) = delete; @@ -116,11 +102,39 @@ template class MOS6522: public MOS6522Base { /*! @returns the bus handler. */ T &bus_handler(); + /// Sets the input value of line @c line on port @c port. + void set_control_line_input(Port port, Line line, bool value); + + /// Runs for a specified number of half cycles. + void run_for(const HalfCycles half_cycles); + + /// Runs for a specified number of cycles. + void run_for(const Cycles cycles); + + /// @returns @c true if the IRQ line is currently active; @c false otherwise. + bool get_interrupt_line(); + + /// Updates the port handler to the current time and then requests that it flush. + void flush(); + private: + void do_phase1(); + void do_phase2(); + void shift_in(); + void shift_out(); + T &bus_handler_; + HalfCycles time_since_bus_handler_call_; + + void access(int address); uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output); inline void reevaluate_interrupts(); + + /// Sets the current intended output value for the port and line; + /// if this affects the visible output, it will be passed to the handler. + void set_control_line_output(Port port, Line line, LineState value); + void evaluate_cb2_output(); }; } diff --git a/Components/6522/Implementation/6522Base.cpp b/Components/6522/Implementation/6522Base.cpp deleted file mode 100644 index 5fcb2c19f..000000000 --- a/Components/6522/Implementation/6522Base.cpp +++ /dev/null @@ -1,116 +0,0 @@ -// -// 6522Base.cpp -// Clock Signal -// -// Created by Thomas Harte on 04/09/2017. -// Copyright 2017 Thomas Harte. All rights reserved. -// - -#include "../6522.hpp" - -using namespace MOS::MOS6522; - -void MOS6522Base::set_control_line_input(Port port, Line line, bool value) { - switch(line) { - case Line::One: - if( value != control_inputs_[port].line_one && - value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01)) - ) { - registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge; - reevaluate_interrupts(); - } - control_inputs_[port].line_one = value; - break; - - case Line::Two: - // TODO: output modes, but probably elsewhere? - if( value != control_inputs_[port].line_two && // i.e. value has changed ... - !(registers_.peripheral_control & (port ? 0x80 : 0x08)) && // ... and line is input ... - value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04)) // ... and it's either high or low, as required - ) { - registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge; - reevaluate_interrupts(); - } - control_inputs_[port].line_two = value; - break; - } -} - -void MOS6522Base::do_phase2() { - registers_.last_timer[0] = registers_.timer[0]; - registers_.last_timer[1] = registers_.timer[1]; - - if(registers_.timer_needs_reload) { - registers_.timer_needs_reload = false; - registers_.timer[0] = registers_.timer_latch[0]; - } else { - registers_.timer[0] --; - } - - registers_.timer[1] --; - if(registers_.next_timer[0] >= 0) { - registers_.timer[0] = static_cast(registers_.next_timer[0]); - registers_.next_timer[0] = -1; - } - if(registers_.next_timer[1] >= 0) { - registers_.timer[1] = static_cast(registers_.next_timer[1]); - registers_.next_timer[1] = -1; - } -} - -void MOS6522Base::do_phase1() { - // IRQ is raised on the half cycle after overflow - if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1]) { - timer_is_running_[1] = false; - registers_.interrupt_flags |= InterruptFlag::Timer2; - reevaluate_interrupts(); - } - - if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0]) { - registers_.interrupt_flags |= InterruptFlag::Timer1; - reevaluate_interrupts(); - - if(registers_.auxiliary_control&0x40) - registers_.timer_needs_reload = true; - else - timer_is_running_[0] = false; - } -} - -/*! Runs for a specified number of half cycles. */ -void MOS6522Base::run_for(const HalfCycles half_cycles) { - int number_of_half_cycles = half_cycles.as_int(); - - if(is_phase2_) { - do_phase2(); - number_of_half_cycles--; - } - - while(number_of_half_cycles >= 2) { - do_phase1(); - do_phase2(); - number_of_half_cycles -= 2; - } - - if(number_of_half_cycles) { - do_phase1(); - is_phase2_ = true; - } else { - is_phase2_ = false; - } -} - -/*! Runs for a specified number of cycles. */ -void MOS6522Base::run_for(const Cycles cycles) { - int number_of_cycles = cycles.as_int(); - while(number_of_cycles--) { - do_phase1(); - do_phase2(); - } -} - -/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */ -bool MOS6522Base::get_interrupt_line() { - uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f; - return !!interrupt_status; -} diff --git a/Components/6522/Implementation/6522Implementation.hpp b/Components/6522/Implementation/6522Implementation.hpp index 4e3074032..9b6f5118e 100644 --- a/Components/6522/Implementation/6522Implementation.hpp +++ b/Components/6522/Implementation/6522Implementation.hpp @@ -11,29 +11,58 @@ namespace MOS { namespace MOS6522 { -template void MOS6522::set_register(int address, uint8_t value) { - address &= 0xf; +template void MOS6522::access(int address) { switch(address) { case 0x0: + // In both handshake and pulse modes, CB2 goes low on any read or write of Port B. + if(handshake_modes_[1] != HandshakeMode::None) { + set_control_line_output(Port::B, Line::Two, LineState::Off); + } + break; + + case 0xf: + case 0x1: + // In both handshake and pulse modes, CA2 goes low on any read or write of Port A. + if(handshake_modes_[0] != HandshakeMode::None) { + set_control_line_output(Port::A, Line::Two, LineState::Off); + } + break; + } +} + +template void MOS6522::set_register(int address, uint8_t value) { + address &= 0xf; + access(address); + switch(address) { + case 0x0: // Write Port B. + // Store locally and communicate outwards. registers_.output[1] = value; - bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]); // TODO: handshake + + bus_handler_.run_for(time_since_bus_handler_call_.flush()); + bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]); registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge)); reevaluate_interrupts(); break; case 0xf: - case 0x1: + case 0x1: // Write Port A. registers_.output[0] = value; - bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]); // TODO: handshake + + bus_handler_.run_for(time_since_bus_handler_call_.flush()); + bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]); + + if(handshake_modes_[1] != HandshakeMode::None) { + set_control_line_output(Port::A, Line::Two, LineState::Off); + } registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge)); reevaluate_interrupts(); break; - case 0x2: + case 0x2: // Port B direction. registers_.data_direction[1] = value; break; - case 0x3: + case 0x3: // Port A direction. registers_.data_direction[0] = value; break; @@ -59,31 +88,57 @@ template void MOS6522::set_register(int address, uint8_t value) break; // Shift - case 0xa: registers_.shift = value; break; + case 0xa: + registers_.shift = value; + shift_bits_remaining_ = 8; + registers_.interrupt_flags &= ~InterruptFlag::ShiftRegister; + reevaluate_interrupts(); + break; // Control case 0xb: registers_.auxiliary_control = value; + evaluate_cb2_output(); break; - case 0xc: + case 0xc: { +// const auto old_peripheral_control = registers_.peripheral_control; registers_.peripheral_control = value; - // TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode - if(value & 0x08) { - switch(value & 0x0e) { - default: LOG("Unimplemented control line mode " << int((value >> 1)&7)); break; - case 0x0c: bus_handler_.set_control_line_output(Port::A, Line::Two, false); break; - case 0x0e: bus_handler_.set_control_line_output(Port::A, Line::Two, true); break; + int shift = 0; + for(int port = 0; port < 2; ++port) { + handshake_modes_[port] = HandshakeMode::None; + switch((value >> shift) & 0x0e) { + default: break; + + case 0x00: // Negative interrupt input; set Cx2 interrupt on negative Cx2 transition, clear on access to Port x register. + case 0x02: // Independent negative interrupt input; set Cx2 interrupt on negative transition, don't clear automatically. + case 0x04: // Positive interrupt input; set Cx2 interrupt on positive Cx2 transition, clear on access to Port x register. + case 0x06: // Independent positive interrupt input; set Cx2 interrupt on positive transition, don't clear automatically. + set_control_line_output(Port(port), Line::Two, LineState::Input); + break; + + case 0x08: // Handshake: set Cx2 to low on any read or write of Port x; set to high on an active transition of Cx1. + handshake_modes_[port] = HandshakeMode::Handshake; + set_control_line_output(Port(port), Line::Two, LineState::Off); // At a guess. + break; + + case 0x0a: // Pulse output: Cx2 is low for one cycle following a read or write of Port x. + handshake_modes_[port] = HandshakeMode::Pulse; + set_control_line_output(Port(port), Line::Two, LineState::On); + break; + + case 0x0c: // Manual output: Cx2 low. + set_control_line_output(Port(port), Line::Two, LineState::Off); + break; + + case 0x0e: // Manual output: Cx2 high. + set_control_line_output(Port(port), Line::Two, LineState::On); + break; } + + shift += 4; } - if(value & 0x80) { - switch(value & 0xe0) { - default: LOG("Unimplemented control line mode " << int((value >> 5)&7)); break; - case 0xc0: bus_handler_.set_control_line_output(Port::B, Line::Two, false); break; - case 0xe0: bus_handler_.set_control_line_output(Port::B, Line::Two, true); break; - } - } - break; + } break; // Interrupt control case 0xd: @@ -102,12 +157,13 @@ template void MOS6522::set_register(int address, uint8_t value) template uint8_t MOS6522::get_register(int address) { address &= 0xf; + access(address); switch(address) { case 0x0: registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge); reevaluate_interrupts(); return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]); - case 0xf: // TODO: handshake, latching + case 0xf: case 0x1: registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge); reevaluate_interrupts(); @@ -132,7 +188,11 @@ template uint8_t MOS6522::get_register(int address) { return registers_.timer[1] & 0x00ff; case 0x9: return registers_.timer[1] >> 8; - case 0xa: return registers_.shift; + case 0xa: + shift_bits_remaining_ = 8; + registers_.interrupt_flags &= ~InterruptFlag::ShiftRegister; + reevaluate_interrupts(); + return registers_.shift; case 0xb: return registers_.auxiliary_control; case 0xc: return registers_.peripheral_control; @@ -145,7 +205,8 @@ template uint8_t MOS6522::get_register(int address) { } template uint8_t MOS6522::get_port_input(Port port, uint8_t output_mask, uint8_t output) { - uint8_t input = bus_handler_.get_port_input(port); + bus_handler_.run_for(time_since_bus_handler_call_.flush()); + const uint8_t input = bus_handler_.get_port_input(port); return (input & ~output_mask) | (output & output_mask); } @@ -158,9 +219,238 @@ template void MOS6522::reevaluate_interrupts() { bool new_interrupt_status = get_interrupt_line(); if(new_interrupt_status != last_posted_interrupt_status_) { last_posted_interrupt_status_ = new_interrupt_status; + + bus_handler_.run_for(time_since_bus_handler_call_.flush()); bus_handler_.set_interrupt_status(new_interrupt_status); } } +template void MOS6522::set_control_line_input(Port port, Line line, bool value) { + switch(line) { + case Line::One: + if(value != control_inputs_[port].lines[line]) { + // In handshake mode, any transition on C[A/B]1 sets output high on C[A/B]2. + if(handshake_modes_[port] == HandshakeMode::Handshake) { + set_control_line_output(port, Line::Two, LineState::On); + } + + // Set the proper transition interrupt bit if enabled. + if(value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))) { + registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge; + reevaluate_interrupts(); + } + + // If this is a transition on CB1, consider updating the shift register. + // TODO: and at least one full clock since the shift register was written? + if(port == Port::B) { + switch(shift_mode()) { + default: break; + case ShiftMode::InUnderCB1: if(value) shift_in(); break; // Shifts in are captured on a low-to-high transition. + case ShiftMode::OutUnderCB1: if(!value) shift_out(); break; // Shifts out are updated on a high-to-low transition. + } + } + } + control_inputs_[port].lines[line] = value; + break; + + case Line::Two: + if( value != control_inputs_[port].lines[line] && // i.e. value has changed ... + !(registers_.peripheral_control & (port ? 0x80 : 0x08)) && // ... and line is input ... + value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04)) // ... and it's either high or low, as required + ) { + registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge; + reevaluate_interrupts(); + } + control_inputs_[port].lines[line] = value; + break; + } +} + +template void MOS6522::do_phase2() { + ++ time_since_bus_handler_call_; + + registers_.last_timer[0] = registers_.timer[0]; + registers_.last_timer[1] = registers_.timer[1]; + + if(registers_.timer_needs_reload) { + registers_.timer_needs_reload = false; + registers_.timer[0] = registers_.timer_latch[0]; + } else { + registers_.timer[0] --; + } + + registers_.timer[1] --; + if(registers_.next_timer[0] >= 0) { + registers_.timer[0] = static_cast(registers_.next_timer[0]); + registers_.next_timer[0] = -1; + } + if(registers_.next_timer[1] >= 0) { + registers_.timer[1] = static_cast(registers_.next_timer[1]); + registers_.next_timer[1] = -1; + } + + // In pulse modes, CA2 and CB2 go high again on the next clock edge. + if(handshake_modes_[1] == HandshakeMode::Pulse) { + set_control_line_output(Port::B, Line::Two, LineState::On); + } + if(handshake_modes_[0] == HandshakeMode::Pulse) { + set_control_line_output(Port::A, Line::Two, LineState::On); + } + + // If the shift register is shifting according to the input clock, do a shift. + switch(shift_mode()) { + default: break; + case ShiftMode::InUnderPhase2: shift_in(); break; + case ShiftMode::OutUnderPhase2: shift_out(); break; + } +} + +template void MOS6522::do_phase1() { + ++ time_since_bus_handler_call_; + + // IRQ is raised on the half cycle after overflow + if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1]) { + timer_is_running_[1] = false; + + // If the shift register is shifting according to this timer, do a shift. + // TODO: "shift register is driven by only the low order 8 bits of timer 2"? + switch(shift_mode()) { + default: break; + case ShiftMode::InUnderT2: shift_in(); break; + case ShiftMode::OutUnderT2FreeRunning: shift_out(); break; + case ShiftMode::OutUnderT2: shift_out(); break; // TODO: present a clock on CB1. + } + + registers_.interrupt_flags |= InterruptFlag::Timer2; + reevaluate_interrupts(); + } + + if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0]) { + registers_.interrupt_flags |= InterruptFlag::Timer1; + reevaluate_interrupts(); + + // Determine whether to reload. + if(registers_.auxiliary_control&0x40) + registers_.timer_needs_reload = true; + else + timer_is_running_[0] = false; + + // Determine whether to toggle PB7. + if(registers_.auxiliary_control&0x80) { + registers_.output[1] ^= 0x80; + bus_handler_.run_for(time_since_bus_handler_call_.flush()); + bus_handler_.set_port_output(Port::B, registers_.output[1], registers_.data_direction[1]); + } + } +} + +/*! Runs for a specified number of half cycles. */ +template void MOS6522::run_for(const HalfCycles half_cycles) { + int number_of_half_cycles = half_cycles.as_int(); + if(!number_of_half_cycles) return; + + if(is_phase2_) { + do_phase2(); + number_of_half_cycles--; + } + + while(number_of_half_cycles >= 2) { + do_phase1(); + do_phase2(); + number_of_half_cycles -= 2; + } + + if(number_of_half_cycles) { + do_phase1(); + is_phase2_ = true; + } else { + is_phase2_ = false; + } +} + +template void MOS6522::flush() { + bus_handler_.run_for(time_since_bus_handler_call_.flush()); + bus_handler_.flush(); +} + +/*! Runs for a specified number of cycles. */ +template void MOS6522::run_for(const Cycles cycles) { + int number_of_cycles = cycles.as_int(); + while(number_of_cycles--) { + do_phase1(); + do_phase2(); + } +} + +/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */ +template bool MOS6522::get_interrupt_line() { + uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f; + return !!interrupt_status; +} + +template void MOS6522::evaluate_cb2_output() { + // CB2 is a special case, being both the line the shift register can output to, + // and one that can be used as an input or handshaking output according to the + // peripheral control register. + + // My guess: other CB2 functions work only if the shift register is disabled (?). + if(shift_mode() != ShiftMode::Disabled) { + // Shift register is enabled, one way or the other; but announce only output. + if(is_shifting_out()) { + // Output mode; set the level according to the current top of the shift register. + bus_handler_.set_control_line_output(Port::B, Line::Two, !!(registers_.shift & 0x80)); + } else { + // Input mode. + bus_handler_.set_control_line_output(Port::B, Line::Two, true); + } + } else { + // Shift register is disabled. + bus_handler_.set_control_line_output(Port::B, Line::Two, control_outputs_[1].lines[1] != LineState::Off); + } +} + +template void MOS6522::set_control_line_output(Port port, Line line, LineState value) { + if(port == Port::B && line == Line::Two) { + control_outputs_[port].lines[line] = value; + evaluate_cb2_output(); + } else { + // Do nothing if unchanged. + if(value == control_outputs_[port].lines[line]) { + return; + } + + control_outputs_[port].lines[line] = value; + + if(value != LineState::Input) { + bus_handler_.run_for(time_since_bus_handler_call_.flush()); + bus_handler_.set_control_line_output(port, line, value != LineState::Off); + } + } +} + +template void MOS6522::shift_in() { + registers_.shift = uint8_t((registers_.shift << 1) | (control_inputs_[1].lines[1] ? 1 : 0)); + --shift_bits_remaining_; + if(!shift_bits_remaining_) { + registers_.interrupt_flags |= InterruptFlag::ShiftRegister; + reevaluate_interrupts(); + } +} + +template void MOS6522::shift_out() { + // When shifting out, the shift register rotates rather than strictly shifts. + // TODO: is that true for all modes? + if(shift_mode() == ShiftMode::OutUnderT2FreeRunning || shift_bits_remaining_) { + registers_.shift = uint8_t((registers_.shift << 1) | (registers_.shift >> 7)); + evaluate_cb2_output(); + + --shift_bits_remaining_; + if(!shift_bits_remaining_) { + registers_.interrupt_flags |= InterruptFlag::ShiftRegister; + reevaluate_interrupts(); + } + } +} + } } diff --git a/Components/6522/Implementation/6522Storage.hpp b/Components/6522/Implementation/6522Storage.hpp index 0f37abe22..da9e30981 100644 --- a/Components/6522/Implementation/6522Storage.hpp +++ b/Components/6522/Implementation/6522Storage.hpp @@ -21,7 +21,7 @@ class MOS6522Storage { // The registers struct Registers { - // "A low reset (RES) input clears all R6522 internal registers to logic 0" + // "A low reset (RES) input clears all R6522 internal registers to logic 0" uint8_t output[2] = {0, 0}; uint8_t input[2] = {0, 0}; uint8_t data_direction[2] = {0, 0}; @@ -37,14 +37,27 @@ class MOS6522Storage { bool timer_needs_reload = false; } registers_; - // control state + // Control state. struct { - bool line_one = false; - bool line_two = false; + bool lines[2] = {false, false}; } control_inputs_[2]; + enum class LineState { + On, Off, Input + }; + struct { + LineState lines[2] = {LineState::Input, LineState::Input}; + } control_outputs_[2]; + + enum class HandshakeMode { + None, + Handshake, + Pulse + } handshake_modes_[2] = { HandshakeMode::None, HandshakeMode::None }; + bool timer_is_running_[2] = {false, false}; bool last_posted_interrupt_status_ = false; + int shift_bits_remaining_ = 8; enum InterruptFlag: uint8_t { CA2ActiveEdge = 1 << 0, @@ -55,6 +68,23 @@ class MOS6522Storage { Timer2 = 1 << 5, Timer1 = 1 << 6, }; + + enum class ShiftMode { + Disabled = 0, + InUnderT2 = 1, + InUnderPhase2 = 2, + InUnderCB1 = 3, + OutUnderT2FreeRunning = 4, + OutUnderT2 = 5, + OutUnderPhase2 = 6, + OutUnderCB1 = 7 + }; + ShiftMode shift_mode() const { + return ShiftMode((registers_.auxiliary_control >> 2) & 7); + } + bool is_shifting_out() const { + return registers_.auxiliary_control & 0x10; + } }; } diff --git a/Components/8530/z8530.cpp b/Components/8530/z8530.cpp new file mode 100644 index 000000000..77ccf5e50 --- /dev/null +++ b/Components/8530/z8530.cpp @@ -0,0 +1,257 @@ +// +// 8530.cpp +// Clock Signal +// +// Created by Thomas Harte on 07/06/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#include "z8530.hpp" + +#include "../../Outputs/Log.hpp" + +using namespace Zilog::SCC; + +void z8530::reset() { + // TODO. +} + +bool z8530::get_interrupt_line() { + return + (master_interrupt_control_ & 0x8) && + ( + channels_[0].get_interrupt_line() || + channels_[1].get_interrupt_line() + ); +} + +std::uint8_t z8530::read(int address) { + if(address & 2) { + // Read data register for channel + return 0x00; + } else { + // Read control register for channel. + uint8_t result = 0; + + switch(pointer_) { + default: + result = channels_[address & 1].read(address & 2, pointer_); + break; + + case 2: // Handled non-symmetrically between channels. + if(address & 1) { + LOG("[SCC] Unimplemented: register 2 status bits"); + } else { + result = interrupt_vector_; + + // Modify the vector if permitted. +// if(master_interrupt_control_ & 1) { + for(int port = 0; port < 2; ++port) { + // TODO: the logic below assumes that DCD is the only implemented interrupt. Fix. + if(channels_[port].get_interrupt_line()) { + const uint8_t shift = 1 + 3*((master_interrupt_control_ & 0x10) >> 4); + const uint8_t mask = uint8_t(~(7 << shift)); + result = uint8_t( + (result & mask) | + ((1 | ((port == 1) ? 4 : 0)) << shift) + ); + break; + } + } +// } + } + break; + } + + pointer_ = 0; + return result; + } + + return 0x00; +} + +void z8530::write(int address, std::uint8_t value) { + if(address & 2) { + // Write data register for channel. + } else { + // Write control register for channel. + + // Most registers are per channel, but a couple are shared; sever + // them here. + switch(pointer_) { + default: + channels_[address & 1].write(address & 2, pointer_, value); + break; + + case 2: // Interrupt vector register; shared between both channels. + interrupt_vector_ = value; + LOG("[SCC] Interrupt vector set to " << PADHEX(2) << int(value)); + break; + + case 9: // Master interrupt and reset register; also shared between both channels. + LOG("[SCC] Master interrupt and reset register: " << PADHEX(2) << int(value)); + master_interrupt_control_ = value; + break; + } + + // The pointer number resets to 0 after every access, but if it is zero + // then crib at least the next set of pointer bits (which, similarly, are shared + // between the two channels). + if(pointer_) { + pointer_ = 0; + } else { + // The lowest three bits are the lowest three bits of the pointer. + pointer_ = value & 7; + + // If the command part of the byte is a 'point high', also set the + // top bit of the pointer. + if(((value >> 3)&7) == 1) { + pointer_ |= 8; + } + } + } +} + +void z8530::set_dcd(int port, bool level) { + channels_[port].set_dcd(level); +} + +// MARK: - Channel implementations + +uint8_t z8530::Channel::read(bool data, uint8_t pointer) { + // If this is a data read, just return it. + if(data) { + return data_; + } else { + // Otherwise, this is a control read... + switch(pointer) { + default: + LOG("[SCC] Unrecognised control read from register " << int(pointer)); + return 0x00; + + case 0: + return dcd_ ? 0x8 : 0x0; + + case 0xf: + return external_interrupt_status_; + } + } + + return 0x00; +} + +void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) { + if(data) { + data_ = value; + return; + } else { + switch(pointer) { + default: + LOG("[SCC] Unrecognised control write: " << PADHEX(2) << int(value) << " to register " << int(pointer)); + break; + + case 0x0: // Write register 0 — CRC reset and other functions. + // Decode CRC reset instructions. + switch(value >> 6) { + default: /* Do nothing. */ break; + case 1: + LOG("[SCC] TODO: reset Rx CRC checker."); + break; + case 2: + LOG("[SCC] TODO: reset Tx CRC checker."); + break; + case 3: + LOG("[SCC] TODO: reset Tx underrun/EOM latch."); + break; + } + + // Decode command code. + switch((value >> 3)&7) { + default: /* Do nothing. */ break; + case 2: +// LOG("[SCC] reset ext/status interrupts."); + external_status_interrupt_ = false; + external_interrupt_status_ = 0; + break; + case 3: + LOG("[SCC] TODO: send abort (SDLC)."); + break; + case 4: + LOG("[SCC] TODO: enable interrupt on next Rx character."); + break; + case 5: + LOG("[SCC] TODO: reset Tx interrupt pending."); + break; + case 6: + LOG("[SCC] TODO: reset error."); + break; + case 7: + LOG("[SCC] TODO: reset highest IUS."); + break; + } + break; + + case 0x1: // Write register 1 — Transmit/Receive Interrupt and Data Transfer Mode Definition. + interrupt_mask_ = value; + break; + + case 0x4: // Write register 4 — Transmit/Receive Miscellaneous Parameters and Modes. + // Bits 0 and 1 select parity mode. + if(!(value&1)) { + parity_ = Parity::Off; + } else { + parity_ = (value&2) ? Parity::Even : Parity::Odd; + } + + // Bits 2 and 3 select stop bits. + switch((value >> 2)&3) { + default: stop_bits_ = StopBits::Synchronous; break; + case 1: stop_bits_ = StopBits::OneBit; break; + case 2: stop_bits_ = StopBits::OneAndAHalfBits; break; + case 3: stop_bits_ = StopBits::TwoBits; break; + } + + // Bits 4 and 5 pick a sync mode. + switch((value >> 4)&3) { + default: sync_mode_ = Sync::Monosync; break; + case 1: sync_mode_ = Sync::Bisync; break; + case 2: sync_mode_ = Sync::SDLC; break; + case 3: sync_mode_ = Sync::External; break; + } + + // Bits 6 and 7 select a clock rate multiplier, unless synchronous + // mode is enabled (and this is ignored if sync mode is external). + if(stop_bits_ == StopBits::Synchronous) { + clock_rate_multiplier_ = 1; + } else { + switch((value >> 6)&3) { + default: clock_rate_multiplier_ = 1; break; + case 1: clock_rate_multiplier_ = 16; break; + case 2: clock_rate_multiplier_ = 32; break; + case 3: clock_rate_multiplier_ = 64; break; + } + } + break; + + case 0xf: // Write register 15 — External/Status Interrupt Control. + external_interrupt_mask_ = value; + break; + } + } +} + +void z8530::Channel::set_dcd(bool level) { + if(dcd_ == level) return; + dcd_ = level; + + if(external_interrupt_mask_ & 0x8) { + external_status_interrupt_ = true; + external_interrupt_status_ |= 0x8; + } +} + +bool z8530::Channel::get_interrupt_line() { + return + (interrupt_mask_ & 1) && external_status_interrupt_; + // TODO: other potential causes of an interrupt. +} diff --git a/Components/8530/z8530.hpp b/Components/8530/z8530.hpp new file mode 100644 index 000000000..01307dce5 --- /dev/null +++ b/Components/8530/z8530.hpp @@ -0,0 +1,88 @@ +// +// z8530.hpp +// Clock Signal +// +// Created by Thomas Harte on 07/06/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef z8530_hpp +#define z8530_hpp + +#include + +namespace Zilog { +namespace SCC { + +/*! + Models the Zilog 8530 SCC, a serial adaptor. +*/ +class z8530 { + public: + /* + **Interface for emulated machine.** + + Notes on addressing below: + + There's no inherent ordering of the two 'address' lines, + A/B and C/D, but the methods below assume: + + A/B = A0 + C/D = A1 + */ + std::uint8_t read(int address); + void write(int address, std::uint8_t value); + void reset(); + bool get_interrupt_line(); + + /* + **Interface for serial port input.** + */ + void set_dcd(int port, bool level); + + private: + class Channel { + public: + uint8_t read(bool data, uint8_t pointer); + void write(bool data, uint8_t pointer, uint8_t value); + void set_dcd(bool level); + bool get_interrupt_line(); + + private: + uint8_t data_ = 0xff; + + enum class Parity { + Even, Odd, Off + } parity_ = Parity::Off; + + enum class StopBits { + Synchronous, OneBit, OneAndAHalfBits, TwoBits + } stop_bits_ = StopBits::Synchronous; + + enum class Sync { + Monosync, Bisync, SDLC, External + } sync_mode_ = Sync::Monosync; + + int clock_rate_multiplier_ = 1; + + uint8_t interrupt_mask_ = 0; // i.e. Write Register 0x1. + + uint8_t external_interrupt_mask_ = 0; // i.e. Write Register 0xf. + bool external_status_interrupt_ = false; + uint8_t external_interrupt_status_ = 0; + + bool dcd_ = false; + } channels_[2]; + + uint8_t pointer_ = 0; + + uint8_t interrupt_vector_ = 0; + + uint8_t master_interrupt_control_ = 0; +}; + +} +} + + +#endif /* z8530_hpp */ diff --git a/Components/DiskII/DiskII.cpp b/Components/DiskII/DiskII.cpp index e915b61a7..21f3daf7c 100644 --- a/Components/DiskII/DiskII.cpp +++ b/Components/DiskII/DiskII.cpp @@ -22,7 +22,7 @@ namespace { DiskII::DiskII(int clock_rate) : clock_rate_(clock_rate), inputs_(input_command), - drives_{{static_cast(clock_rate), 300, 1}, {static_cast(clock_rate), 300, 1}} + drives_{{clock_rate, 300, 1}, {clock_rate, 300, 1}} { drives_[0].set_clocking_hint_observer(this); drives_[1].set_clocking_hint_observer(this); @@ -211,7 +211,7 @@ void DiskII::set_disk(const std::shared_ptr &disk, int driv drives_[drive].set_disk(disk); } -void DiskII::process_event(const Storage::Disk::Track::Event &event) { +void DiskII::process_event(const Storage::Disk::Drive::Event &event) { if(event.type == Storage::Disk::Track::Event::FluxTransition) { inputs_ &= ~input_flux; flux_duration_ = 2; // Upon detection of a flux transition, the flux flag should stay set for 1us. Emulate that as two cycles. diff --git a/Components/DiskII/DiskII.hpp b/Components/DiskII/DiskII.hpp index 6df8edf10..8f5cd92f8 100644 --- a/Components/DiskII/DiskII.hpp +++ b/Components/DiskII/DiskII.hpp @@ -98,7 +98,7 @@ class DiskII: void select_drive(int drive); uint8_t trigger_address(int address, uint8_t value); - void process_event(const Storage::Disk::Track::Event &event) override; + void process_event(const Storage::Disk::Drive::Event &event) override; void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override; const int clock_rate_ = 0; diff --git a/Components/DiskII/IWM.cpp b/Components/DiskII/IWM.cpp new file mode 100644 index 000000000..25acf2ced --- /dev/null +++ b/Components/DiskII/IWM.cpp @@ -0,0 +1,375 @@ +// +// IWM.cpp +// Clock Signal +// +// Created by Thomas Harte on 05/05/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#include "IWM.hpp" + +#include "../../Outputs/Log.hpp" + +using namespace Apple; + +namespace { + const int CA0 = 1 << 0; + const int CA1 = 1 << 1; + const int CA2 = 1 << 2; + const int LSTRB = 1 << 3; + const int ENABLE = 1 << 4; + const int DRIVESEL = 1 << 5; /* This means drive select, like on the original Disk II. */ + const int Q6 = 1 << 6; + const int Q7 = 1 << 7; + const int SEL = 1 << 8; /* This is an additional input, not available on a Disk II, with a confusingly-similar name to SELECT but a distinct purpose. */ +} + +IWM::IWM(int clock_rate) : + clock_rate_(clock_rate) {} + +// MARK: - Bus accessors + +uint8_t IWM::read(int address) { + access(address); + + // Per Inside Macintosh: + // + // "Before you can read from any of the disk registers you must set up the state of the IWM so that it + // can pass the data through to the MC68000's address space where you'll be able to read it. To do that, + // you must first turn off Q7 by reading or writing dBase+q7L. Then turn on Q6 by accessing dBase+q6H. + // After that, the IWM will be able to pass data from the disk's RD/SENSE line through to you." + // + // My understanding: + // + // Q6 = 1, Q7 = 0 reads the status register. The meaning of the top 'SENSE' bit is then determined by + // the CA0,1,2 and SEL switches as described in Inside Macintosh, summarised above as RD/SENSE. + + if(address&1) { + return 0xff; + } + + switch(state_ & (Q6 | Q7 | ENABLE)) { + default: + LOG("[IWM] Invalid read\n"); + return 0xff; + + // "Read all 1s". +// printf("Reading all 1s\n"); +// return 0xff; + + case 0: + case ENABLE: { /* Read data register. Zeroing afterwards is a guess. */ + const auto result = data_register_; + + if(data_register_ & 0x80) { +// printf("\n\nIWM:%02x\n\n", data_register_); + data_register_ = 0; + } +// LOG("Reading data register: " << PADHEX(2) << int(result)); + + return result; + } + + case Q6: case Q6|ENABLE: { + /* + [If A = 0], Read status register: + + bits 0-4: same as mode register. + bit 5: 1 = either /ENBL1 or /ENBL2 is currently low. + bit 6: 1 = MZ (reserved for future compatibility; should always be read as 0). + bit 7: 1 = SENSE input high; 0 = SENSE input low. + + (/ENBL1 is low when the first drive's motor is on; /ENBL2 is low when the second drive's motor is on. + If the 1-second timer is enabled, motors remain on for one second after being programmatically disabled.) + */ + + return uint8_t( + (mode_&0x1f) | + ((state_ & ENABLE) ? 0x20 : 0x00) | + (sense() & 0x80) + ); + } break; + + case Q7: case Q7|ENABLE: + /* + Read write-handshake register: + + bits 0-5: reserved for future use (currently read as 1). + bit 6: 1 = write state (0 = underrun has occurred; 1 = no underrun so far). + bit 7: 1 = write data buffer ready for data (1 = ready; 0 = busy). + */ +// LOG("Reading write handshake: " << PADHEX(2) << (0x3f | write_handshake_)); + return 0x3f | write_handshake_; + } + + return 0xff; +} + +void IWM::write(int address, uint8_t input) { + access(address); + + switch(state_ & (Q6 | Q7 | ENABLE)) { + default: break; + + case Q7|Q6: + /* + Write mode register: + + bit 0: 1 = latch mode (should be set in asynchronous mode). + bit 1: 0 = synchronous handshake protocol; 1 = asynchronous. + bit 2: 0 = 1-second on-board timer enable; 1 = timer disable. + bit 3: 0 = slow mode; 1 = fast mode. + bit 4: 0 = 7Mhz; 1 = 8Mhz (7 or 8 mHz clock descriptor). + bit 5: 1 = test mode; 0 = normal operation. + bit 6: 1 = MZ-reset. + bit 7: reserved for future expansion. + */ + + mode_ = input; + + switch(mode_ & 0x18) { + case 0x00: bit_length_ = Cycles(24); break; // slow mode, 7Mhz + case 0x08: bit_length_ = Cycles(12); break; // fast mode, 7Mhz + case 0x10: bit_length_ = Cycles(32); break; // slow mode, 8Mhz + case 0x18: bit_length_ = Cycles(16); break; // fast mode, 8Mhz + } + LOG("IWM mode is now " << PADHEX(2) << int(mode_)); + break; + + case Q7|Q6|ENABLE: // Write data register. + next_output_ = input; + write_handshake_ &= ~0x80; + break; + } +} + +// MARK: - Switch access + +void IWM::access(int address) { + // Keep a record of switch state; bits in state_ + // should correlate with the anonymous namespace constants + // defined at the top of this file — CA0, CA1, etc. + address &= 0xf; + const auto mask = 1 << (address >> 1); + const auto old_state = state_; + + if(address & 1) { + state_ |= mask; + } else { + state_ &= ~mask; + } + + // React appropriately to ENABLE and DRIVESEL changes, and changes into/out of write mode. + if(old_state != state_) { + push_drive_state(); + + switch(mask) { + default: break; + + case ENABLE: + if(address & 1) { + if(drives_[active_drive_]) drives_[active_drive_]->set_enabled(true); + } else { + // If the 1-second delay is enabled, set up a timer for that. + if(!(mode_ & 4)) { + cycles_until_disable_ = Cycles(clock_rate_); + } else { + if(drives_[active_drive_]) drives_[active_drive_]->set_enabled(false); + } + } + break; + + case DRIVESEL: { + const int new_drive = address & 1; + if(new_drive != active_drive_) { + if(drives_[active_drive_]) drives_[active_drive_]->set_enabled(false); + active_drive_ = new_drive; + if(drives_[active_drive_]) { + drives_[active_drive_]->set_enabled(state_ & ENABLE || (cycles_until_disable_ > Cycles(0))); + push_drive_state(); + } + } + } break; + + case Q6: + case Q7: + select_shift_mode(); + break; + } + } +} + +void IWM::set_select(bool enabled) { + // Store SEL as an extra state bit. + if(enabled) state_ |= SEL; + else state_ &= ~SEL; + push_drive_state(); +} + +void IWM::push_drive_state() { + if(drives_[active_drive_]) { + const uint8_t drive_control_lines = + ((state_ & CA0) ? IWMDrive::CA0 : 0) | + ((state_ & CA1) ? IWMDrive::CA1 : 0) | + ((state_ & CA2) ? IWMDrive::CA2 : 0) | + ((state_ & SEL) ? IWMDrive::SEL : 0) | + ((state_ & LSTRB) ? IWMDrive::LSTRB : 0); + drives_[active_drive_]->set_control_lines(drive_control_lines); + } +} + +// MARK: - Active logic + +void IWM::run_for(const Cycles cycles) { + // Check for a timeout of the motor-off timer. + if(cycles_until_disable_ > Cycles(0)) { + cycles_until_disable_ -= cycles; + if(cycles_until_disable_ <= Cycles(0)) { + cycles_until_disable_ = Cycles(0); + if(drives_[active_drive_]) + drives_[active_drive_]->set_enabled(false); + } + } + + // Activity otherwise depends on mode and motor state. + int integer_cycles = cycles.as_int(); + switch(shift_mode_) { + case ShiftMode::Reading: + if(drive_is_rotating_[active_drive_]) { + while(integer_cycles--) { + drives_[active_drive_]->run_for(Cycles(1)); + ++cycles_since_shift_; + if(cycles_since_shift_ == bit_length_ + Cycles(2)) { + propose_shift(0); + } + } + } else { + while(cycles_since_shift_ + integer_cycles >= bit_length_ + Cycles(2)) { + propose_shift(0); + integer_cycles -= bit_length_.as_int() + 2 - cycles_since_shift_.as_int(); + } + cycles_since_shift_ += Cycles(integer_cycles); + } + break; + + case ShiftMode::Writing: + if(drives_[active_drive_]->is_writing()) { + while(cycles_since_shift_ + integer_cycles >= bit_length_) { + const auto cycles_until_write = bit_length_ - cycles_since_shift_; + drives_[active_drive_]->run_for(cycles_until_write); + + // Output a flux transition if the top bit is set. + drives_[active_drive_]->write_bit(shift_register_ & 0x80); + shift_register_ <<= 1; + + integer_cycles -= cycles_until_write.as_int(); + cycles_since_shift_ = Cycles(0); + + --output_bits_remaining_; + if(!output_bits_remaining_) { + if(!(write_handshake_ & 0x80)) { + write_handshake_ |= 0x80; + shift_register_ = next_output_; + output_bits_remaining_ = 8; +// LOG("Next byte: " << PADHEX(2) << int(shift_register_)); + } else { + write_handshake_ &= ~0x40; + drives_[active_drive_]->end_writing(); + printf("\n"); + LOG("Overrun; done."); + select_shift_mode(); + } + } + } + + cycles_since_shift_ = integer_cycles; + if(integer_cycles) { + drives_[active_drive_]->run_for(cycles_since_shift_); + } + } else { + drives_[active_drive_]->run_for(cycles); + } + break; + + case ShiftMode::CheckingWriteProtect: + if(integer_cycles < 8) { + shift_register_ = (shift_register_ >> integer_cycles) | (sense() & (0xff << (8 - integer_cycles))); + } else { + shift_register_ = sense(); + } + + /* Deliberate fallthrough. */ + default: + if(drive_is_rotating_[active_drive_]) drives_[active_drive_]->run_for(cycles); + break; + } +} + +void IWM::select_shift_mode() { + // Don't allow an ongoing write to be interrupted. + if(shift_mode_ == ShiftMode::Writing && drives_[active_drive_] && drives_[active_drive_]->is_writing()) return; + + const auto old_shift_mode = shift_mode_; + + switch(state_ & (Q6|Q7)) { + default: shift_mode_ = ShiftMode::CheckingWriteProtect; break; + case 0: shift_mode_ = ShiftMode::Reading; break; + case Q7: + // "The IWM is put into the write state by a transition from the write protect sense state to the + // write load state". + if(shift_mode_ == ShiftMode::CheckingWriteProtect) shift_mode_ = ShiftMode::Writing; + break; + } + + // If writing mode just began, set the drive into write mode and cue up the first output byte. + if(drives_[active_drive_] && old_shift_mode != ShiftMode::Writing && shift_mode_ == ShiftMode::Writing) { + drives_[active_drive_]->begin_writing(Storage::Time(1, clock_rate_ / bit_length_.as_int()), false); + shift_register_ = next_output_; + write_handshake_ |= 0x80 | 0x40; + output_bits_remaining_ = 8; + LOG("Seeding output with " << PADHEX(2) << shift_register_); + } +} + +uint8_t IWM::sense() { + return drives_[active_drive_] ? (drives_[active_drive_]->read() ? 0xff : 0x00) : 0xff; +} + +void IWM::process_event(const Storage::Disk::Drive::Event &event) { + if(shift_mode_ != ShiftMode::Reading) return; + + switch(event.type) { + case Storage::Disk::Track::Event::IndexHole: return; + case Storage::Disk::Track::Event::FluxTransition: + propose_shift(1); + break; + } +} + +void IWM::propose_shift(uint8_t bit) { + // TODO: synchronous mode. + +// LOG("Shifting input"); + shift_register_ = uint8_t((shift_register_ << 1) | bit); + if(shift_register_ & 0x80) { + data_register_ = shift_register_; + shift_register_ = 0; + } + cycles_since_shift_ = Cycles(0); +} + +void IWM::set_drive(int slot, IWMDrive *drive) { + drives_[slot] = drive; + drive->set_event_delegate(this); + drive->set_clocking_hint_observer(this); +} + +void IWM::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) { + const bool is_rotating = clocking != ClockingHint::Preference::None; + + if(component == static_cast(drives_[0])) { + drive_is_rotating_[0] = is_rotating; + } else { + drive_is_rotating_[1] = is_rotating; + } +} diff --git a/Components/DiskII/IWM.hpp b/Components/DiskII/IWM.hpp new file mode 100644 index 000000000..69d184b73 --- /dev/null +++ b/Components/DiskII/IWM.hpp @@ -0,0 +1,117 @@ +// +// IWM.hpp +// Clock Signal +// +// Created by Thomas Harte on 05/05/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef IWM_hpp +#define IWM_hpp + +#include "../../ClockReceiver/ClockReceiver.hpp" +#include "../../ClockReceiver/ClockingHintSource.hpp" +#include "../../Storage/Disk/Drive.hpp" + +#include + +namespace Apple { + +/*! + Defines the drive interface used by the IWM, derived from the external pinout as + per e.g. https://old.pinouts.ru/HD/MacExtDrive_pinout.shtml + + These are subclassed of Storage::Disk::Drive, so accept any disk the emulator supports, + and provide the usual read/write interface for on-disk data. +*/ +struct IWMDrive: public Storage::Disk::Drive { + IWMDrive(int input_clock_rate, int number_of_heads) : Storage::Disk::Drive(input_clock_rate, number_of_heads) {} + + enum Line: int { + CA0 = 1 << 0, + CA1 = 1 << 1, + CA2 = 1 << 2, + LSTRB = 1 << 3, + SEL = 1 << 4, + }; + + virtual void set_enabled(bool) = 0; + virtual void set_control_lines(int) = 0; + virtual bool read() = 0; +}; + +class IWM: + public Storage::Disk::Drive::EventDelegate, + public ClockingHint::Observer { + public: + IWM(int clock_rate); + + /// Sets the current external value of the data bus. + void write(int address, uint8_t value); + + /*! + Submits an access to address @c address. + + @returns The 8-bit value loaded to the data bus by the IWM. + */ + uint8_t read(int address); + + /*! + Sets the current input of the IWM's SEL line. + */ + void set_select(bool enabled); + + /// Advances the controller by @c cycles. + void run_for(const Cycles cycles); + + /// Connects a drive to the IWM. + void set_drive(int slot, IWMDrive *drive); + + private: + // Storage::Disk::Drive::EventDelegate. + void process_event(const Storage::Disk::Drive::Event &event) override; + + const int clock_rate_; + + uint8_t data_register_ = 0; + uint8_t mode_ = 0; + bool read_write_ready_ = true; + bool write_overran_ = false; + + int state_ = 0; + + int active_drive_ = 0; + IWMDrive *drives_[2] = {nullptr, nullptr}; + bool drive_is_rotating_[2] = {false, false}; + + void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override; + + Cycles cycles_until_disable_; + uint8_t write_handshake_ = 0x80; + + void access(int address); + + uint8_t shift_register_ = 0; + uint8_t next_output_ = 0; + int output_bits_remaining_ = 0; + + void propose_shift(uint8_t bit); + Cycles cycles_since_shift_; + Cycles bit_length_; + + void push_drive_state(); + + enum class ShiftMode { + Reading, + Writing, + CheckingWriteProtect + } shift_mode_; + + uint8_t sense(); + void select_shift_mode(); +}; + + +} + +#endif /* IWM_hpp */ diff --git a/Components/DiskII/MacintoshDoubleDensityDrive.cpp b/Components/DiskII/MacintoshDoubleDensityDrive.cpp new file mode 100644 index 000000000..523217195 --- /dev/null +++ b/Components/DiskII/MacintoshDoubleDensityDrive.cpp @@ -0,0 +1,170 @@ +// +// MacintoshDoubleDensityDrive.cpp +// Clock Signal +// +// Created by Thomas Harte on 10/07/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#include "MacintoshDoubleDensityDrive.hpp" + +/* + Sources used pervasively: + + http://members.iinet.net.au/~kalandi/apple/AUG/1991/11%20NOV.DEC/DISK.STUFF.html + Apple Guide to the Macintosh Family Hardware + Inside Macintosh III +*/ + +using namespace Apple::Macintosh; + +DoubleDensityDrive::DoubleDensityDrive(int input_clock_rate, bool is_800k) : + IWMDrive(input_clock_rate, is_800k ? 2 : 1), // Only 800kb drives are double sided. + is_800k_(is_800k) { + // Start with a valid rotation speed. + if(is_800k) { + set_rotation_speed(393.3807f); + } +} + +// MARK: - Speed Selection + +void DoubleDensityDrive::did_step(Storage::Disk::HeadPosition to_position) { + // The 800kb drive automatically selects rotation speed as a function of + // head position; the 400kb drive doesn't do so. + if(is_800k_) { + /* + Numbers below cribbed from the Kryoflux forums; specifically: + https://forum.kryoflux.com/viewtopic.php?t=1090 + + They can almost be worked out algorithmically, since the point is to + produce an almost-constant value for speed*(number of sectors), and: + + 393.3807 * 12 = 4720.5684 + 429.1723 * 11 = 4720.895421 + 472.1435 * 10 = 4721.435 + 524.5672 * 9 = 4721.1048 + 590.1098 * 8 = 4720.8784 + + So 4721 / (number of sectors per track in zone) would give essentially + the same results. + */ + const int zone = to_position.as_int() >> 4; + switch(zone) { + case 0: set_rotation_speed(393.3807f); break; + case 1: set_rotation_speed(429.1723f); break; + case 2: set_rotation_speed(472.1435f); break; + case 3: set_rotation_speed(524.5672f); break; + default: set_rotation_speed(590.1098f); break; + } + } +} + +// MARK: - Control input/output. + +void DoubleDensityDrive::set_enabled(bool) { +} + +void DoubleDensityDrive::set_control_lines(int lines) { + const auto old_state = control_state_; + control_state_ = lines; + + // Catch low-to-high LSTRB transitions. + if((old_state ^ control_state_) & control_state_ & Line::LSTRB) { + switch(control_state_ & (Line::CA2 | Line::CA1 | Line::CA0 | Line::SEL)) { + default: + break; + + case 0: // Set step direction — CA2 set => step outward. + case Line::CA2: + step_direction_ = (control_state_ & Line::CA2) ? -1 : 1; + break; + + case Line::CA1: // Set drive motor — CA2 set => motor off. + case Line::CA1|Line::CA2: + set_motor_on(!(control_state_ & Line::CA2)); + break; + + case Line::CA0: // Initiate a step. + step(Storage::Disk::HeadPosition(step_direction_)); + break; + + case Line::SEL|Line::CA2: // Reset new disk flag. + has_new_disk_ = false; + break; + + case Line::CA2 | Line::CA1 | Line::CA0: // Eject the disk. + set_disk(nullptr); + break; + } + } +} + +bool DoubleDensityDrive::read() { + switch(control_state_ & (CA2 | CA1 | CA0 | SEL)) { + default: + return false; + + case 0: // Head step direction. + // (0 = inward) + return step_direction_ <= 0; + + case SEL: // Disk in place. + // (0 = disk present) + return !has_disk(); + + case CA0: // Disk head step completed. + // (0 = still stepping) + return true; // TODO: stepping delay. But at the main Drive level. + + case CA0|SEL: // Disk locked. + // (0 = write protected) + return !get_is_read_only(); + + case CA1: // Disk motor running. + // (0 = motor on) + return !get_motor_on(); + + case CA1|SEL: // Head at track 0. + // (0 = at track 0) + // "This bit becomes valid beginning 12 msec after the step that places the head at track 0." + return !get_is_track_zero(); + + case CA1|CA0: // Disk has been ejected. + // (0 = user has ejected disk) + return !has_new_disk_; + + case CA1|CA0|SEL: // Tachometer. + // (arbitrary) + return get_tachometer(); + + case CA2: // Read data, lower head. + set_head(0); + return false; + + case CA2|SEL: // Read data, upper head. + set_head(1); + return false; + + case CA2|CA1: // Single- or double-sided drive. + // (0 = single sided) + return get_head_count() != 1; + + case CA2|CA1|CA0: // "Present/HD" (per the Mac Plus ROM) + // (0 = ??HD??) + // + // Alternative explanation: "Disk ready for reading?" + // (0 = ready) + return false; + + case CA2|CA1|CA0|SEL: // Drive installed. + // (0 = present, 1 = missing) + // + // TODO: why do I need to return this the wrong way around for the Mac Plus? + return true; + } +} + +void DoubleDensityDrive::did_set_disk() { + has_new_disk_ = true; +} diff --git a/Components/DiskII/MacintoshDoubleDensityDrive.hpp b/Components/DiskII/MacintoshDoubleDensityDrive.hpp new file mode 100644 index 000000000..da409b6de --- /dev/null +++ b/Components/DiskII/MacintoshDoubleDensityDrive.hpp @@ -0,0 +1,39 @@ +// +// MacintoshDoubleDensityDrive.hpp +// Clock Signal +// +// Created by Thomas Harte on 10/07/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef MacintoshDoubleDensityDrive_hpp +#define MacintoshDoubleDensityDrive_hpp + +#include "IWM.hpp" + +namespace Apple { +namespace Macintosh { + +class DoubleDensityDrive: public IWMDrive { + public: + DoubleDensityDrive(int input_clock_rate, bool is_800k); + + void set_enabled(bool) override; + void set_control_lines(int) override; + bool read() override; + + private: + // To receive the proper notifications from Storage::Disk::Drive. + void did_step(Storage::Disk::HeadPosition to_position) override; + void did_set_disk() override; + + const bool is_800k_; + bool has_new_disk_ = false; + int control_state_ = 0; + int step_direction_ = 1; +}; + +} +} + +#endif /* MacintoshDoubleDensityDrive_hpp */ diff --git a/Inputs/Keyboard.hpp b/Inputs/Keyboard.hpp index e6f0f07b6..7b2481dab 100644 --- a/Inputs/Keyboard.hpp +++ b/Inputs/Keyboard.hpp @@ -6,8 +6,8 @@ // Copyright 2017 Thomas Harte. All rights reserved. // -#ifndef Keyboard_hpp -#define Keyboard_hpp +#ifndef Inputs_Keyboard_hpp +#define Inputs_Keyboard_hpp #include #include @@ -75,4 +75,4 @@ class Keyboard { } -#endif /* Keyboard_hpp */ +#endif /* Inputs_Keyboard_hpp */ diff --git a/Inputs/Mouse.hpp b/Inputs/Mouse.hpp new file mode 100644 index 000000000..2d6fe7a82 --- /dev/null +++ b/Inputs/Mouse.hpp @@ -0,0 +1,47 @@ +// +// Mouse.hpp +// Clock Signal +// +// Created by Thomas Harte on 11/06/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef Mouse_h +#define Mouse_h + +namespace Inputs { + +/*! + Models a classic-era mouse: something that provides 2d relative motion plus + some quantity of buttons. +*/ +class Mouse { + public: + /*! + Indicates a movement of the mouse. + */ + virtual void move(int x, int y) {} + + /*! + @returns the number of buttons on this mouse. + */ + virtual int get_number_of_buttons() { + return 1; + } + + /*! + Indicates that button @c index is now either pressed or unpressed. + The intention is that @c index be semantic, not positional: + 0 for the primary button, 1 for the secondary, 2 for the tertiary, etc. + */ + virtual void set_button_pressed(int index, bool is_pressed) {} + + /*! + Releases all depressed buttons. + */ + virtual void reset_all_buttons() {} +}; + +} + +#endif /* Mouse_h */ diff --git a/Inputs/QuadratureMouse/QuadratureMouse.hpp b/Inputs/QuadratureMouse/QuadratureMouse.hpp new file mode 100644 index 000000000..d9e048d11 --- /dev/null +++ b/Inputs/QuadratureMouse/QuadratureMouse.hpp @@ -0,0 +1,123 @@ +// +// QuadratureMouse.hpp +// Clock Signal +// +// Created by Thomas Harte on 11/06/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef QuadratureMouse_hpp +#define QuadratureMouse_hpp + +#include "../Mouse.hpp" +#include + +namespace Inputs { + +/*! + Provides a simple implementation of a Mouse, designed for simple + thread-safe feeding to a machine that accepts quadrature-encoded input. + + TEMPORARY SIMPLIFICATION: it is assumed that the caller will be interested + in observing a signal that dictates velocity, sampling the other to + obtain direction only on transitions in the velocity signal. + + Or, more concretely, of the two channels per axis, one is accurate only when + the other transitions. Hence the discussion of 'primary' and 'secondary' + channels below. This is intended to be fixed. +*/ +class QuadratureMouse: public Mouse { + public: + QuadratureMouse(int number_of_buttons) : + number_of_buttons_(number_of_buttons) {} + + /* + Inputs, to satisfy the Mouse interface. + */ + void move(int x, int y) override { + // Accumulate all provided motion. + axes_[0] += x; + axes_[1] += y; + } + + int get_number_of_buttons() override { + return number_of_buttons_; + } + + void set_button_pressed(int index, bool is_pressed) override { + if(is_pressed) + button_flags_ |= (1 << index); + else + button_flags_ &= ~(1 << index); + } + + void reset_all_buttons() override { + button_flags_ = 0; + } + + /* + Outputs. + */ + + /*! + Applies a single step from the current accumulated mouse movement, which + might involve the mouse moving right, or left, or not at all. + */ + void prepare_step() { + for(int axis = 0; axis < 2; ++axis) { + // Do nothing if there's no motion to communicate. + const int axis_value = axes_[axis]; + if(!axis_value) continue; + + // Toggle the primary channel and set the secondary for + // negative motion. At present the y axis signals the + // secondary channel the opposite way around from the + // primary. + primaries_[axis] ^= 1; + secondaries_[axis] = primaries_[axis] ^ axis; + if(axis_value > 0) { + -- axes_[axis]; + secondaries_[axis] ^= 1; // Switch to positive motion. + } else { + ++ axes_[axis]; + } + } + } + + /*! + @returns the two quadrature channels — bit 0 is the 'primary' channel + (i.e. the one that can be monitored to observe velocity) and + bit 1 is the 'secondary' (i.e. that which can be queried to + observe direction). + */ + int get_channel(int axis) { + return primaries_[axis] | (secondaries_[axis] << 1); + } + + /*! + @returns a bit mask of the currently pressed buttons. + */ + int get_button_mask() { + return button_flags_; + } + + /*! + @returns @c true if any mouse motion is waiting to be communicated; + @c false otherwise. + */ + bool has_steps() { + return axes_[0] || axes_[1]; + } + + private: + int number_of_buttons_ = 0; + std::atomic button_flags_; + std::atomic axes_[2]; + + int primaries_[2] = {0, 0}; + int secondaries_[2] = {0, 0}; +}; + +} + +#endif /* QuadratureMouse_hpp */ diff --git a/Machines/AppleII/AppleII.cpp b/Machines/Apple/AppleII/AppleII.cpp similarity index 93% rename from Machines/AppleII/AppleII.cpp rename to Machines/Apple/AppleII/AppleII.cpp index a8121e534..f36e93103 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/Apple/AppleII/AppleII.cpp @@ -8,33 +8,34 @@ #include "AppleII.hpp" -#include "../../Activity/Source.hpp" -#include "../MediaTarget.hpp" -#include "../CRTMachine.hpp" -#include "../JoystickMachine.hpp" -#include "../KeyboardMachine.hpp" -#include "../Utility/MemoryFuzzer.hpp" -#include "../Utility/StringSerialiser.hpp" +#include "../../../Activity/Source.hpp" +#include "../../MediaTarget.hpp" +#include "../../CRTMachine.hpp" +#include "../../JoystickMachine.hpp" +#include "../../KeyboardMachine.hpp" +#include "../../Utility/MemoryFuzzer.hpp" +#include "../../Utility/StringSerialiser.hpp" -#include "../../Processors/6502/6502.hpp" -#include "../../Components/AudioToggle/AudioToggle.hpp" +#include "../../../Processors/6502/6502.hpp" +#include "../../../Components/AudioToggle/AudioToggle.hpp" -#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" -#include "../../Outputs/Log.hpp" +#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" +#include "../../../Outputs/Log.hpp" #include "Card.hpp" #include "DiskIICard.hpp" #include "Video.hpp" -#include "../../Analyser/Static/AppleII/Target.hpp" -#include "../../ClockReceiver/ForceInline.hpp" -#include "../../Configurable/StandardOptions.hpp" +#include "../../../Analyser/Static/AppleII/Target.hpp" +#include "../../../ClockReceiver/ForceInline.hpp" +#include "../../../Configurable/StandardOptions.hpp" #include #include #include -namespace AppleII { +namespace Apple { +namespace II { std::vector> get_options() { return Configurable::standard_options( @@ -51,12 +52,12 @@ template class ConcreteMachine: public CPU::MOS6502::BusHandler, public Inputs::Keyboard, public Configurable::Device, - public AppleII::Machine, + public Apple::II::Machine, public Activity::Source, public JoystickMachine::Machine, - public AppleII::Card::Delegate { + public Apple::II::Card::Delegate { private: - struct VideoBusHandler : public AppleII::Video::BusHandler { + struct VideoBusHandler : public Apple::II::Video::BusHandler { public: VideoBusHandler(uint8_t *ram, uint8_t *aux_ram) : ram_(ram), aux_ram_(aux_ram) {} @@ -71,7 +72,7 @@ template class ConcreteMachine: CPU::MOS6502::Processor<(model == Analyser::Static::AppleII::Target::Model::EnhancedIIe) ? CPU::MOS6502::Personality::PSynertek65C02 : CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_; VideoBusHandler video_bus_handler_; - AppleII::Video::Video video_; + Apple::II::Video::Video video_; int cycles_into_current_line_ = 0; Cycles cycles_since_video_update_; @@ -109,28 +110,28 @@ template class ConcreteMachine: Cycles cycles_since_audio_update_; // MARK: - Cards - std::array, 7> cards_; + std::array, 7> cards_; Cycles cycles_since_card_update_; - std::vector every_cycle_cards_; - std::vector just_in_time_cards_; + std::vector every_cycle_cards_; + std::vector just_in_time_cards_; int stretched_cycles_since_card_update_ = 0; - void install_card(std::size_t slot, AppleII::Card *card) { + void install_card(std::size_t slot, Apple::II::Card *card) { assert(slot >= 1 && slot < 8); cards_[slot - 1].reset(card); card->set_delegate(this); pick_card_messaging_group(card); } - bool is_every_cycle_card(AppleII::Card *card) { + bool is_every_cycle_card(Apple::II::Card *card) { return !card->get_select_constraints(); } - void pick_card_messaging_group(AppleII::Card *card) { + void pick_card_messaging_group(Apple::II::Card *card) { const bool is_every_cycle = is_every_cycle_card(card); - std::vector &intended = is_every_cycle ? every_cycle_cards_ : just_in_time_cards_; - std::vector &undesired = is_every_cycle ? just_in_time_cards_ : every_cycle_cards_; + std::vector &intended = is_every_cycle ? every_cycle_cards_ : just_in_time_cards_; + std::vector &undesired = is_every_cycle ? just_in_time_cards_ : every_cycle_cards_; if(std::find(intended.begin(), intended.end(), card) != intended.end()) return; auto old_membership = std::find(undesired.begin(), undesired.end(), card); @@ -138,12 +139,12 @@ template class ConcreteMachine: intended.push_back(card); } - void card_did_change_select_constraints(AppleII::Card *card) override { + void card_did_change_select_constraints(Apple::II::Card *card) override { pick_card_messaging_group(card); } - AppleII::DiskIICard *diskii_card() { - return dynamic_cast(cards_[5].get()); + Apple::II::DiskIICard *diskii_card() { + return dynamic_cast(cards_[5].get()); } // MARK: - Memory Map. @@ -383,7 +384,7 @@ template class ConcreteMachine: if(target.disk_controller != Target::DiskController::None) { // Apple recommended slot 6 for the (first) Disk II. - install_card(6, new AppleII::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector)); + install_card(6, new Apple::II::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector)); } // Set up the default memory blocks. On a II or II+ these values will never change. @@ -700,7 +701,7 @@ template class ConcreteMachine: // If this is a card access, figure out which card is at play before determining // the totality of who needs messaging. size_t card_number = 0; - AppleII::Card::Select select = AppleII::Card::None; + Apple::II::Card::Select select = Apple::II::Card::None; if(address >= 0xc100) { /* @@ -708,20 +709,20 @@ template class ConcreteMachine: 0xCn00 to 0xCnff: card n. */ card_number = (address - 0xc100) >> 8; - select = AppleII::Card::Device; + select = Apple::II::Card::Device; } else { /* Decode the area conventionally used by cards for registers: C0n0 to C0nF: card n - 8. */ card_number = (address - 0xc090) >> 4; - select = AppleII::Card::IO; + select = Apple::II::Card::IO; } // If the selected card is a just-in-time card, update the just-in-time cards, // and then message it specifically. const bool is_read = isReadOperation(operation); - AppleII::Card *const target = cards_[static_cast(card_number)].get(); + Apple::II::Card *const target = cards_[static_cast(card_number)].get(); if(target && !is_every_cycle_card(target)) { update_just_in_time_cards(); target->perform_bus_operation(select, is_read, address, value); @@ -732,7 +733,7 @@ template class ConcreteMachine: for(const auto &card: every_cycle_cards_) { card->run_for(Cycles(1), is_stretched_cycle); card->perform_bus_operation( - (card == target) ? select : AppleII::Card::None, + (card == target) ? select : Apple::II::Card::None, is_read, address, value); } has_updated_cards = true; @@ -744,7 +745,7 @@ template class ConcreteMachine: const bool is_read = isReadOperation(operation); for(const auto &card: every_cycle_cards_) { card->run_for(Cycles(1), is_stretched_cycle); - card->perform_bus_operation(AppleII::Card::None, is_read, address, value); + card->perform_bus_operation(Apple::II::Card::None, is_read, address, value); } } @@ -818,7 +819,7 @@ template class ConcreteMachine: // MARK:: Configuration options. std::vector> get_options() override { - return AppleII::get_options(); + return Apple::II::get_options(); } void set_selections(const Configurable::SelectionSet &selections_by_option) override { @@ -860,9 +861,10 @@ template class ConcreteMachine: } }; +} } -using namespace AppleII; +using namespace Apple::II; Machine *Machine::AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { using Target = Analyser::Static::AppleII::Target; diff --git a/Machines/AppleII/AppleII.hpp b/Machines/Apple/AppleII/AppleII.hpp similarity index 76% rename from Machines/AppleII/AppleII.hpp rename to Machines/Apple/AppleII/AppleII.hpp index c680b3163..ad4fededd 100644 --- a/Machines/AppleII/AppleII.hpp +++ b/Machines/Apple/AppleII/AppleII.hpp @@ -9,14 +9,15 @@ #ifndef AppleII_hpp #define AppleII_hpp -#include "../../Configurable/Configurable.hpp" -#include "../../Analyser/Static/StaticAnalyser.hpp" -#include "../ROMMachine.hpp" +#include "../../../Configurable/Configurable.hpp" +#include "../../../Analyser/Static/StaticAnalyser.hpp" +#include "../../ROMMachine.hpp" #include #include -namespace AppleII { +namespace Apple { +namespace II { /// @returns The options available for an Apple II. std::vector> get_options(); @@ -29,6 +30,7 @@ class Machine { static Machine *AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); }; -}; +} +} #endif /* AppleII_hpp */ diff --git a/Machines/AppleII/Card.hpp b/Machines/Apple/AppleII/Card.hpp similarity index 95% rename from Machines/AppleII/Card.hpp rename to Machines/Apple/AppleII/Card.hpp index 2b1ae664f..7e68daf81 100644 --- a/Machines/AppleII/Card.hpp +++ b/Machines/Apple/AppleII/Card.hpp @@ -9,11 +9,12 @@ #ifndef Card_h #define Card_h -#include "../../Processors/6502/6502.hpp" -#include "../../ClockReceiver/ClockReceiver.hpp" -#include "../../Activity/Observer.hpp" +#include "../../../Processors/6502/6502.hpp" +#include "../../../ClockReceiver/ClockReceiver.hpp" +#include "../../../Activity/Observer.hpp" -namespace AppleII { +namespace Apple { +namespace II { /*! This provides a small subset of the interface offered to cards installed in @@ -109,6 +110,7 @@ class Card { } }; +} } #endif /* Card_h */ diff --git a/Machines/AppleII/DiskIICard.cpp b/Machines/Apple/AppleII/DiskIICard.cpp similarity index 98% rename from Machines/AppleII/DiskIICard.cpp rename to Machines/Apple/AppleII/DiskIICard.cpp index a73d16844..2235cff6a 100644 --- a/Machines/AppleII/DiskIICard.cpp +++ b/Machines/Apple/AppleII/DiskIICard.cpp @@ -8,7 +8,7 @@ #include "DiskIICard.hpp" -using namespace AppleII; +using namespace Apple::II; DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) : diskii_(2045454) { const auto roms = rom_fetcher( diff --git a/Machines/AppleII/DiskIICard.hpp b/Machines/Apple/AppleII/DiskIICard.hpp similarity index 83% rename from Machines/AppleII/DiskIICard.hpp rename to Machines/Apple/AppleII/DiskIICard.hpp index 703788f05..3e8d3b962 100644 --- a/Machines/AppleII/DiskIICard.hpp +++ b/Machines/Apple/AppleII/DiskIICard.hpp @@ -10,17 +10,18 @@ #define DiskIICard_hpp #include "Card.hpp" -#include "../ROMMachine.hpp" +#include "../../ROMMachine.hpp" -#include "../../Components/DiskII/DiskII.hpp" -#include "../../Storage/Disk/Disk.hpp" -#include "../../ClockReceiver/ClockingHintSource.hpp" +#include "../../../Components/DiskII/DiskII.hpp" +#include "../../../Storage/Disk/Disk.hpp" +#include "../../../ClockReceiver/ClockingHintSource.hpp" #include #include #include -namespace AppleII { +namespace Apple { +namespace II { class DiskIICard: public Card, public ClockingHint::Observer { public: @@ -41,6 +42,7 @@ class DiskIICard: public Card, public ClockingHint::Observer { ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime; }; +} } #endif /* DiskIICard_hpp */ diff --git a/Machines/AppleII/Video.cpp b/Machines/Apple/AppleII/Video.cpp similarity index 99% rename from Machines/AppleII/Video.cpp rename to Machines/Apple/AppleII/Video.cpp index 22474c8ae..5370aaab2 100644 --- a/Machines/AppleII/Video.cpp +++ b/Machines/Apple/AppleII/Video.cpp @@ -8,7 +8,7 @@ #include "Video.hpp" -using namespace AppleII::Video; +using namespace Apple::II::Video; VideoBase::VideoBase(bool is_iie, std::function &&target) : crt_(910, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance1), diff --git a/Machines/AppleII/Video.hpp b/Machines/Apple/AppleII/Video.hpp similarity index 99% rename from Machines/AppleII/Video.hpp rename to Machines/Apple/AppleII/Video.hpp index 3eccde46e..7ae6b179b 100644 --- a/Machines/AppleII/Video.hpp +++ b/Machines/Apple/AppleII/Video.hpp @@ -9,14 +9,15 @@ #ifndef Video_hpp #define Video_hpp -#include "../../Outputs/CRT/CRT.hpp" -#include "../../ClockReceiver/ClockReceiver.hpp" -#include "../../ClockReceiver/ClockDeferrer.hpp" +#include "../../../Outputs/CRT/CRT.hpp" +#include "../../../ClockReceiver/ClockReceiver.hpp" +#include "../../../ClockReceiver/ClockDeferrer.hpp" #include #include -namespace AppleII { +namespace Apple { +namespace II { namespace Video { class BusHandler { @@ -600,6 +601,7 @@ template class Video: public VideoBase { BusHandler &bus_handler_; }; +} } } diff --git a/Machines/Apple/Macintosh/Audio.cpp b/Machines/Apple/Macintosh/Audio.cpp new file mode 100644 index 000000000..3946553fc --- /dev/null +++ b/Machines/Apple/Macintosh/Audio.cpp @@ -0,0 +1,97 @@ +// +// Audio.cpp +// Clock Signal +// +// Created by Thomas Harte on 31/05/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#include "Audio.hpp" + +using namespace Apple::Macintosh; + +namespace { + +// The sample_length is coupled with the clock rate selected within the Macintosh proper; +// as per the header-declaration a divide-by-two clock is expected to arrive here. +const std::size_t sample_length = 352 / 2; + +} + +Audio::Audio(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {} + +// MARK: - Inputs + +void Audio::post_sample(uint8_t sample) { + // Store sample directly indexed by current write pointer; this ensures that collected samples + // directly map to volume and enabled/disabled states. + sample_queue_.buffer[sample_queue_.write_pointer] = sample; + sample_queue_.write_pointer = (sample_queue_.write_pointer + 1) % sample_queue_.buffer.size(); +} + +void Audio::set_volume(int volume) { + // Do nothing if the volume hasn't changed. + if(posted_volume_ == volume) return; + posted_volume_ = volume; + + // Post the volume change as a deferred event. + task_queue_.defer([=] () { + volume_ = volume; + set_volume_multiplier(); + }); +} + +void Audio::set_enabled(bool on) { + // Do nothing if the mask hasn't changed. + if(posted_enable_mask_ == int(on)) return; + posted_enable_mask_ = int(on); + + // Post the enabled mask change as a deferred event. + task_queue_.defer([=] () { + enabled_mask_ = int(on); + set_volume_multiplier(); + }); +} + +// MARK: - Output generation + +bool Audio::is_zero_level() { + return !volume_ || !enabled_mask_; +} + +void Audio::set_sample_volume_range(std::int16_t range) { + // Some underflow here doesn't really matter. + output_volume_ = range / (7 * 255); + set_volume_multiplier(); +} + +void Audio::set_volume_multiplier() { + volume_multiplier_ = int16_t(output_volume_ * volume_ * enabled_mask_); +} + +void Audio::get_samples(std::size_t number_of_samples, int16_t *target) { + // TODO: the implementation below acts as if the hardware uses pulse-amplitude modulation; + // in fact it uses pulse-width modulation. But the scale for pulses isn't specified, so + // that's something to return to. + + while(number_of_samples) { + // Determine how many output samples will be at the same level. + const auto cycles_left_in_sample = std::min(number_of_samples, sample_length - subcycle_offset_); + + // Determine the output level, and output that many samples. + // (Hoping that the copiler substitutes an effective memset16-type operation here). + const int16_t output_level = volume_multiplier_ * (int16_t(sample_queue_.buffer[sample_queue_.read_pointer]) - 128); + for(size_t c = 0; c < cycles_left_in_sample; ++c) { + target[c] = output_level; + } + target += cycles_left_in_sample; + + // Advance the sample pointer. + subcycle_offset_ += cycles_left_in_sample; + sample_queue_.read_pointer = (sample_queue_.read_pointer + (subcycle_offset_ / sample_length)) % sample_queue_.buffer.size(); + subcycle_offset_ %= sample_length; + + // Decreate the number of samples left to write. + number_of_samples -= cycles_left_in_sample; + } +} diff --git a/Machines/Apple/Macintosh/Audio.hpp b/Machines/Apple/Macintosh/Audio.hpp new file mode 100644 index 000000000..cfec007d6 --- /dev/null +++ b/Machines/Apple/Macintosh/Audio.hpp @@ -0,0 +1,88 @@ +// +// Audio.hpp +// Clock Signal +// +// Created by Thomas Harte on 31/05/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef Audio_hpp +#define Audio_hpp + +#include "../../../Concurrency/AsyncTaskQueue.hpp" +#include "../../../ClockReceiver/ClockReceiver.hpp" +#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp" + +#include +#include + +namespace Apple { +namespace Macintosh { + +/*! + Implements the Macintosh's audio output hardware. + + Designed to be clocked at half the rate of the real hardware — i.e. + a shade less than 4Mhz. +*/ +class Audio: public ::Outputs::Speaker::SampleSource { + public: + Audio(Concurrency::DeferringAsyncTaskQueue &task_queue); + + /*! + Macintosh audio is (partly) sourced by the same scanning + hardware as the video; each line it collects an additional + word of memory, half of which is used for audio output. + + Use this method to add a newly-collected sample to the queue. + */ + void post_sample(uint8_t sample); + + /*! + Macintosh audio also separately receives an output volume + level, in the range 0 to 7. + + Use this method to set the current output volume. + */ + void set_volume(int volume); + + /*! + A further factor in audio output is the on-off toggle. + */ + void set_enabled(bool on); + + // to satisfy ::Outputs::Speaker (included via ::Outputs::Filter. + void get_samples(std::size_t number_of_samples, int16_t *target); + bool is_zero_level(); + void set_sample_volume_range(std::int16_t range); + + private: + Concurrency::DeferringAsyncTaskQueue &task_queue_; + + // A queue of fetched samples; read from by one thread, + // written to by another. + struct { + std::array buffer; + size_t read_pointer = 0, write_pointer = 0; + } sample_queue_; + + // Emulator-thread stateful variables, to avoid work posting + // deferral updates if possible. + int posted_volume_ = 0; + int posted_enable_mask_ = 0; + + // Stateful variables, modified from the audio generation + // thread only. + int volume_ = 0; + int enabled_mask_ = 0; + std::int16_t output_volume_ = 0; + + std::int16_t volume_multiplier_ = 0; + std::size_t subcycle_offset_ = 0; + void set_volume_multiplier(); +}; + +} +} + +#endif /* Audio_hpp */ diff --git a/Machines/Apple/Macintosh/DeferredAudio.hpp b/Machines/Apple/Macintosh/DeferredAudio.hpp new file mode 100644 index 000000000..72ebc6b10 --- /dev/null +++ b/Machines/Apple/Macintosh/DeferredAudio.hpp @@ -0,0 +1,34 @@ +// +// DeferredAudio.hpp +// Clock Signal +// +// Created by Thomas Harte on 01/06/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef DeferredAudio_h +#define DeferredAudio_h + +#include "Audio.hpp" +#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" + +namespace Apple { +namespace Macintosh { + +struct DeferredAudio { + Concurrency::DeferringAsyncTaskQueue queue; + Audio audio; + Outputs::Speaker::LowpassSpeaker - CFBundleTypeOSTypes - - ???? - CFBundleTypeIconFile floppy35.png CFBundleTypeName Electron/BBC Disk Image + CFBundleTypeOSTypes + + ???? + CFBundleTypeRole Editor + LSHandlerRank + Owner LSTypeIsPackage NSDocumentClass $(PRODUCT_MODULE_NAME).MachineDocument - LSHandlerRank - Owner CFBundleTypeExtensions dsk - CFBundleTypeOSTypes - - ???? - CFBundleTypeIconFile floppy35.png CFBundleTypeName Disk Image + CFBundleTypeOSTypes + + ???? + CFBundleTypeRole Editor + LSHandlerRank + Owner LSTypeIsPackage NSDocumentClass $(PRODUCT_MODULE_NAME).MachineDocument - LSHandlerRank - Owner CFBundleTypeExtensions @@ -216,22 +216,22 @@ o 80 - CFBundleTypeOSTypes - - ???? - CFBundleTypeIconFile cassette.png CFBundleTypeName ZX80 Tape Image + CFBundleTypeOSTypes + + ???? + CFBundleTypeRole Viewer + LSHandlerRank + Owner LSTypeIsPackage NSDocumentClass $(PRODUCT_MODULE_NAME).MachineDocument - LSHandlerRank - Owner CFBundleTypeExtensions @@ -240,240 +240,240 @@ 81 p81 - CFBundleTypeOSTypes - - ???? - CFBundleTypeIconFile cassette.png CFBundleTypeName ZX81 Tape Image + CFBundleTypeOSTypes + + ???? + CFBundleTypeRole Viewer + LSHandlerRank + Owner LSTypeIsPackage NSDocumentClass $(PRODUCT_MODULE_NAME).MachineDocument - LSHandlerRank - Owner CFBundleTypeExtensions csw - CFBundleTypeOSTypes - - ???? - CFBundleTypeIconFile cassette.png CFBundleTypeName Tape Image + CFBundleTypeOSTypes + + ???? + CFBundleTypeRole Viewer + LSHandlerRank + Owner LSTypeIsPackage NSDocumentClass $(PRODUCT_MODULE_NAME).MachineDocument - LSHandlerRank - Owner CFBundleTypeExtensions tzx - CFBundleTypeOSTypes - - ???? - CFBundleTypeIconFile cassette.png CFBundleTypeName Tape Image + CFBundleTypeOSTypes + + ???? + CFBundleTypeRole Viewer + LSHandlerRank + Owner LSTypeIsPackage NSDocumentClass $(PRODUCT_MODULE_NAME).MachineDocument - LSHandlerRank - Owner CFBundleTypeExtensions cdt - CFBundleTypeOSTypes - - ???? - CFBundleTypeIconFile cassette.png CFBundleTypeName Amstrad CPC Tape Image + CFBundleTypeOSTypes + + ???? + CFBundleTypeRole Viewer + LSHandlerRank + Owner LSTypeIsPackage NSDocumentClass $(PRODUCT_MODULE_NAME).MachineDocument - LSHandlerRank - Owner CFBundleTypeExtensions hfe - CFBundleTypeOSTypes - - ???? - CFBundleTypeIconFile floppy35.png CFBundleTypeName HxC Disk Image + CFBundleTypeOSTypes + + ???? + CFBundleTypeRole Viewer + LSHandlerRank + Owner LSTypeIsPackage NSDocumentClass $(PRODUCT_MODULE_NAME).MachineDocument - LSHandlerRank - Owner CFBundleTypeExtensions cas - CFBundleTypeOSTypes - - ???? - CFBundleTypeIconFile cassette.png CFBundleTypeName MSX Tape Image + CFBundleTypeOSTypes + + ???? + CFBundleTypeRole Viewer + LSHandlerRank + Owner LSTypeIsPackage NSDocumentClass $(PRODUCT_MODULE_NAME).MachineDocument - LSHandlerRank - Owner CFBundleTypeExtensions dmk - CFBundleTypeOSTypes - - ???? - CFBundleTypeIconFile floppy35.png CFBundleTypeName Disk Image + CFBundleTypeOSTypes + + ???? + CFBundleTypeRole Viewer - LSTypeIsPackage - LSHandlerRank Owner + LSTypeIsPackage + CFBundleTypeExtensions tsx - CFBundleTypeOSTypes - - ???? - CFBundleTypeIconFile cassette.png CFBundleTypeName MSX Tape Image + CFBundleTypeOSTypes + + ???? + CFBundleTypeRole Viewer + LSHandlerRank + Owner LSTypeIsPackage NSDocumentClass $(PRODUCT_MODULE_NAME).MachineDocument - LSHandlerRank - Owner CFBundleTypeExtensions col - CFBundleTypeOSTypes - - ???? - CFBundleTypeIconFile cartridge.png CFBundleTypeName ColecoVision Cartridge + CFBundleTypeOSTypes + + ???? + CFBundleTypeRole Viewer + LSHandlerRank + Owner LSTypeIsPackage NSDocumentClass $(PRODUCT_MODULE_NAME).MachineDocument - LSHandlerRank - Owner CFBundleTypeExtensions sms - CFBundleTypeOSTypes - - ???? - CFBundleTypeIconFile cartridge.png CFBundleTypeName Master System Cartridge + CFBundleTypeOSTypes + + ???? + CFBundleTypeRole Viewer + LSHandlerRank + Owner LSTypeIsPackage NSDocumentClass $(PRODUCT_MODULE_NAME).MachineDocument - LSHandlerRank - Owner CFBundleTypeExtensions sg - CFBundleTypeOSTypes - - ???? - CFBundleTypeIconFile cartridge.png CFBundleTypeName SG1000 Cartridge + CFBundleTypeOSTypes + + ???? + CFBundleTypeRole Viewer + LSHandlerRank + Owner LSTypeIsPackage NSDocumentClass $(PRODUCT_MODULE_NAME).MachineDocument - LSHandlerRank - Owner CFBundleTypeExtensions @@ -483,22 +483,44 @@ do po - CFBundleTypeOSTypes - - ???? - CFBundleTypeIconFile floppy525.png CFBundleTypeName Apple II Disk Image + CFBundleTypeOSTypes + + ???? + CFBundleTypeRole Viewer + LSHandlerRank + Owner LSTypeIsPackage NSDocumentClass $(PRODUCT_MODULE_NAME).MachineDocument - LSHandlerRank - Owner + + + CFBundleTypeExtensions + + img + hfv + image + + CFBundleTypeIconFile + floppy35 + CFBundleTypeName + DiskCopy 4.2 Disk Image + CFBundleTypeOSTypes + + ???? + + CFBundleTypeRole + Viewer + LSTypeIsPackage + 0 + NSDocumentClass + $(PRODUCT_MODULE_NAME).MachineDocument CFBundleExecutable diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h index ca824bbc3..5f1bd5da0 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h @@ -61,6 +61,9 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) { - (void)setKey:(uint16_t)key characters:(nullable NSString *)characters isPressed:(BOOL)isPressed; - (void)clearAllKeys; +- (void)setMouseButton:(int)button isPressed:(BOOL)isPressed; +- (void)addMouseMotionX:(CGFloat)deltaX y:(CGFloat)deltaY; + @property (nonatomic, strong, nullable) CSAudioQueue *audioQueue; @property (nonatomic, readonly, nonnull) CSOpenGLView *view; @property (nonatomic, weak, nullable) id delegate; @@ -81,6 +84,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) { // Input control. @property (nonatomic, readonly) BOOL hasExclusiveKeyboard; @property (nonatomic, readonly) BOOL hasJoystick; +@property (nonatomic, readonly) BOOL hasMouse; @property (nonatomic, assign) CSMachineKeyboardInputMode inputMode; @property (nonatomic, nullable) CSJoystickManager *joystickManager; diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm index c43535b04..c2077baef 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm @@ -410,14 +410,14 @@ struct ActivityObserver: public Activity::Observer { } - (void)clearAllKeys { - auto keyboard_machine = _machine->keyboard_machine(); + const auto keyboard_machine = _machine->keyboard_machine(); if(keyboard_machine) { @synchronized(self) { keyboard_machine->get_keyboard().reset_all_keys(); } } - auto joystick_machine = _machine->joystick_machine(); + const auto joystick_machine = _machine->joystick_machine(); if(joystick_machine) { @synchronized(self) { for(auto &joystick : joystick_machine->get_joysticks()) { @@ -425,6 +425,31 @@ struct ActivityObserver: public Activity::Observer { } } } + + const auto mouse_machine = _machine->mouse_machine(); + if(mouse_machine) { + @synchronized(self) { + mouse_machine->get_mouse().reset_all_buttons(); + } + } +} + +- (void)setMouseButton:(int)button isPressed:(BOOL)isPressed { + auto mouse_machine = _machine->mouse_machine(); + if(mouse_machine) { + @synchronized(self) { + mouse_machine->get_mouse().set_button_pressed(button % mouse_machine->get_mouse().get_number_of_buttons(), isPressed); + } + } +} + +- (void)addMouseMotionX:(CGFloat)deltaX y:(CGFloat)deltaY { + auto mouse_machine = _machine->mouse_machine(); + if(mouse_machine) { + @synchronized(self) { + mouse_machine->get_mouse().move(int(deltaX), int(deltaY)); + } + } } #pragma mark - Options @@ -540,6 +565,10 @@ struct ActivityObserver: public Activity::Observer { return !!_machine->joystick_machine(); } +- (BOOL)hasMouse { + return !!_machine->mouse_machine(); +} + - (BOOL)hasExclusiveKeyboard { return !!_machine->keyboard_machine() && _machine->keyboard_machine()->get_keyboard().is_exclusive(); } diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h index 0316568c3..56867af17 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h @@ -29,6 +29,13 @@ typedef NS_ENUM(NSInteger, CSMachineCPCModel) { CSMachineCPCModel6128 }; +typedef NS_ENUM(NSInteger, CSMachineMacintoshModel) { + CSMachineMacintoshModel128k, + CSMachineMacintoshModel512k, + CSMachineMacintoshModel512ke, + CSMachineMacintoshModelPlus, +}; + typedef NS_ENUM(NSInteger, CSMachineOricModel) { CSMachineOricModelOric1, CSMachineOricModelOricAtmos, @@ -69,6 +76,7 @@ typedef int Kilobytes; - (instancetype)initWithZX80MemorySize:(Kilobytes)memorySize useZX81ROM:(BOOL)useZX81ROM; - (instancetype)initWithZX81MemorySize:(Kilobytes)memorySize; - (instancetype)initWithAppleIIModel:(CSMachineAppleIIModel)model diskController:(CSMachineAppleIIDiskController)diskController; +- (instancetype)initWithMacintoshModel:(CSMachineMacintoshModel)model; @property(nonatomic, readonly) NSString *optionsPanelNibName; @property(nonatomic, readonly) NSString *displayName; diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm index 8dcdff8c0..ee187bfbf 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm @@ -17,6 +17,7 @@ #include "../../../../../Analyser/Static/AmstradCPC/Target.hpp" #include "../../../../../Analyser/Static/AppleII/Target.hpp" #include "../../../../../Analyser/Static/Commodore/Target.hpp" +#include "../../../../../Analyser/Static/Macintosh/Target.hpp" #include "../../../../../Analyser/Static/MSX/Target.hpp" #include "../../../../../Analyser/Static/Oric/Target.hpp" #include "../../../../../Analyser/Static/ZX8081/Target.hpp" @@ -188,7 +189,27 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K _targets.push_back(std::move(target)); } return self; +} +- (instancetype)initWithMacintoshModel:(CSMachineMacintoshModel)model { + self = [super init]; + if(self) { + using Target = Analyser::Static::Macintosh::Target; + std::unique_ptr target(new Target); + target->machine = Analyser::Machine::Macintosh; + + using Model = Target::Model; + switch(model) { + default: + case CSMachineMacintoshModel128k: target->model = Model::Mac128k; break; + case CSMachineMacintoshModel512k: target->model = Model::Mac512k; break; + case CSMachineMacintoshModel512ke: target->model = Model::Mac512ke; break; + case CSMachineMacintoshModelPlus: target->model = Model::MacPlus; break; + } + + _targets.push_back(std::move(target)); + } + return self; } - (NSString *)optionsPanelNibName { diff --git a/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib b/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib index 458589a8d..b51807442 100644 --- a/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib +++ b/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib @@ -1,8 +1,8 @@ - + - + @@ -17,14 +17,14 @@ - + - +