From db8d8d8404fdcfcad8dc03fddef349213afeb74e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 27 May 2018 23:17:06 -0400 Subject: [PATCH 1/9] Commutes `Sleeper` to `ClockingHint::Source`, making state more granular. --- ClockReceiver/ClockingHintSource.hpp | 88 +++++++++++++++++++ ClockReceiver/Sleeper.hpp | 60 ------------- Components/1770/1770.hpp | 1 - Components/8272/i8272.cpp | 14 +-- Components/8272/i8272.hpp | 4 +- Components/DiskII/DiskII.cpp | 20 ++--- Components/DiskII/DiskII.hpp | 10 +-- Machines/AmstradCPC/AmstradCPC.cpp | 15 ++-- Machines/AppleII/DiskIICard.cpp | 10 +-- Machines/AppleII/DiskIICard.hpp | 8 +- Machines/Commodore/Vic-20/Vic20.cpp | 8 +- Machines/MSX/MSX.cpp | 8 +- Machines/Oric/Microdisc.hpp | 4 +- Machines/Oric/Oric.cpp | 8 +- .../Clock Signal.xcodeproj/project.pbxproj | 4 +- Storage/Disk/Controller/DiskController.cpp | 18 ++-- Storage/Disk/Controller/DiskController.hpp | 23 +++-- Storage/Disk/Drive.cpp | 8 +- Storage/Disk/Drive.hpp | 10 +-- Storage/Tape/Tape.cpp | 15 ++-- Storage/Tape/Tape.hpp | 12 +-- 21 files changed, 190 insertions(+), 158 deletions(-) create mode 100644 ClockReceiver/ClockingHintSource.hpp delete mode 100644 ClockReceiver/Sleeper.hpp diff --git a/ClockReceiver/ClockingHintSource.hpp b/ClockReceiver/ClockingHintSource.hpp new file mode 100644 index 000000000..c0d7b24a2 --- /dev/null +++ b/ClockReceiver/ClockingHintSource.hpp @@ -0,0 +1,88 @@ +// +// ClockingHintSource.h +// Clock Signal +// +// Created by Thomas Harte on 20/08/2017. +// Copyright 2017 Thomas Harte. All rights reserved. +// + +#ifndef ClockingHintSource_hpp +#define ClockingHintSource_hpp + +namespace ClockingHint { + +enum class Preference { + /// The component doesn't currently require a clock signal. + None, + /// The component can be clocked only immediate prior to (explicit) accesses. + JustInTime, + /// The component require real-time clocking. + RealTime +}; + +class Source; + +struct Observer { + /// Called to inform an observer that the component @c component has changed its clocking requirements. + virtual void set_component_prefers_clocking(Source *component, Preference clocking) = 0; +}; + +/*! + An clocking hint source is any component that can provide hints as to the type of + clocking required for accurate emulation. A disk controller is an archetypal example. + + Types of clocking are: + + - none: + a component that acts and reacts to direct contact but does not have a state that autonomously evolves. + E.g. a ROM, RAM, or some kinds of disk controller when not in the process of performing a command. + + - just-in-time: + a component that has an evolving state but can receive clock updates only immediately before a + direct contact. This is possibly the most common kind of component. + + - real-time: + a component that needs to be clocked in 'real time' (i.e. in terms of the emulated machine). For example + so that it can announce an interrupt at the proper moment, because it is monitoring some aspect of + the machine rather than waiting to be called upon, or because there's some other non-obvious relationship + at play. + + A clocking hint source can signal changes in preferred clocking to an observer. + + This is intended to allow for performance improvements to machines with components that can be messaged selectively. + The observer callout is virtual so the intended use case is that a machine holds a component that might go through + periods of different clocking requirements. + + Transitions should be sufficiently infrequent that a virtual call to announce them costs little enough that + the saved or deferred ::run_fors add up to a substantial amount. + + The hint provided is just that: a hint. Owners may perform ::run_for at a greater frequency. +*/ +class Source { + public: + /// Registers @c observer as the new clocking observer. + void set_clocking_hint_observer(Observer *observer) { + observer_ = observer; + update_clocking_observer(); + } + + /// @returns the current preferred clocking strategy. + virtual Preference preferred_clocking() = 0; + + private: + Observer *observer_ = nullptr; + + protected: + /*! + Provided for subclasses; call this whenever the clocking preference might have changed. + This will notify the observer if there is one. + */ + void update_clocking_observer() { + if(!observer_) return; + observer_->set_component_prefers_clocking(this, preferred_clocking()); + } +}; + +} + +#endif /* ClockingHintSource_h */ diff --git a/ClockReceiver/Sleeper.hpp b/ClockReceiver/Sleeper.hpp deleted file mode 100644 index 7026b97ce..000000000 --- a/ClockReceiver/Sleeper.hpp +++ /dev/null @@ -1,60 +0,0 @@ -// -// Sleeper.h -// Clock Signal -// -// Created by Thomas Harte on 20/08/2017. -// Copyright 2017 Thomas Harte. All rights reserved. -// - -#ifndef Sleeper_hpp -#define Sleeper_hpp - -/*! - A sleeper is any component that sometimes requires a clock but at other times is 'asleep', i.e. is not doing - any clock-derived work, so needn't receive a clock. A disk controller is an archetypal example. - - A sleeper will signal sleeps and wakes to an observer. - - This is intended to allow for performance improvements to machines with components that can sleep. The observer - callout is virtual so the intended use case is that a machine holds a component that might sleep. Its transitions - into and out of sleep are sufficiently infrequent that a virtual call to announce them costs sufficiently little that - the saved ::run_fors add up to a substantial amount. - - By convention, sleeper components must be willing to accept ::run_for even after announcing sleep. It's a hint, - not a command. -*/ -class Sleeper { - public: - Sleeper() : sleep_observer_(nullptr) {} - - class SleepObserver { - public: - /// Called to inform an observer that the component @c component has either gone to sleep or become awake. - virtual void set_component_is_sleeping(Sleeper *component, bool is_sleeping) = 0; - }; - - /// Registers @c observer as the new sleep observer; - void set_sleep_observer(SleepObserver *observer) { - sleep_observer_ = observer; - } - - /// @returns @c true if the component is currently sleeping; @c false otherwise. - virtual bool is_sleeping() = 0; - - protected: - /// Provided for subclasses; send sleep announcements to the sleep_observer_. - SleepObserver *sleep_observer_; - - /*! - Provided for subclasses; call this whenever is_sleeping might have changed, and the observer will be notified, - if one exists. - - @c is_sleeping will be called only if there is an observer. - */ - void update_sleep_observer() { - if(!sleep_observer_) return; - sleep_observer_->set_component_is_sleeping(this, is_sleeping()); - } -}; - -#endif /* Sleeper_h */ diff --git a/Components/1770/1770.hpp b/Components/1770/1770.hpp index ae26830fc..cee4a7f23 100644 --- a/Components/1770/1770.hpp +++ b/Components/1770/1770.hpp @@ -43,7 +43,6 @@ class WD1770: public Storage::Disk::MFMController { /// Runs the controller for @c number_of_cycles cycles. void run_for(const Cycles cycles); - using Storage::Disk::Controller::run_for; enum Flag: uint8_t { NotReady = 0x80, diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 183a0c008..8cbc434c8 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -83,8 +83,10 @@ i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) : posit_event(static_cast(Event8272::CommandByte)); } -bool i8272::is_sleeping() { - return is_sleeping_ && Storage::Disk::MFMController::is_sleeping(); +ClockingHint::Preference i8272::preferred_clocking() { + const auto mfm_controller_preferred_clocking = Storage::Disk::MFMController::preferred_clocking(); + if(mfm_controller_preferred_clocking != ClockingHint::Preference::None) return mfm_controller_preferred_clocking; + return is_sleeping_ ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime; } void i8272::run_for(Cycles cycles) { @@ -159,7 +161,7 @@ void i8272::run_for(Cycles cycles) { } is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_; - if(is_sleeping_) update_sleep_observer(); + if(is_sleeping_) update_clocking_observer(); } void i8272::set_register(int address, uint8_t value) { @@ -198,7 +200,7 @@ uint8_t i8272::get_register(int address) { #define MS_TO_CYCLES(x) x * 8000 #define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = static_cast(mask); return; case __LINE__: -#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = static_cast(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_sleep_observer(); case __LINE__: if(delay_time_) return; +#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = static_cast(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_clocking_observer(); case __LINE__: if(delay_time_) return; #define PASTE(x, y) x##y #define CONCAT(x, y) PASTE(x, y) @@ -257,7 +259,7 @@ uint8_t i8272::get_register(int address) { if(drives_[active_drive_].head_unload_delay[active_head_] == 0) { \ head_timers_running_++; \ is_sleeping_ = false; \ - update_sleep_observer(); \ + update_clocking_observer(); \ } \ drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\ } @@ -720,7 +722,7 @@ void i8272::posit_event(int event_type) { if(drives_[drive].phase != Drive::Seeking) { drives_seeking_++; is_sleeping_ = false; - update_sleep_observer(); + update_clocking_observer(); } // Set currently seeking, with a step to occur right now (yes, it sounds like jamming these diff --git a/Components/8272/i8272.hpp b/Components/8272/i8272.hpp index a07e0ae27..566da8ca5 100644 --- a/Components/8272/i8272.hpp +++ b/Components/8272/i8272.hpp @@ -39,7 +39,7 @@ class i8272: public Storage::Disk::MFMController { void set_dma_acknowledge(bool dack); void set_terminal_count(bool tc); - bool is_sleeping(); + ClockingHint::Preference preferred_clocking() override; protected: virtual void select_drive(int number) = 0; @@ -67,7 +67,7 @@ class i8272: public Storage::Disk::MFMController { ResultEmpty = (1 << 5), NoLongerReady = (1 << 6) }; - void posit_event(int type); + void posit_event(int type) override; int interesting_event_mask_ = static_cast(Event8272::CommandByte); int resume_point_ = 0; bool is_access_command_ = false; diff --git a/Components/DiskII/DiskII.cpp b/Components/DiskII/DiskII.cpp index a19556b21..e8d6539ce 100644 --- a/Components/DiskII/DiskII.cpp +++ b/Components/DiskII/DiskII.cpp @@ -23,8 +23,8 @@ DiskII::DiskII() : inputs_(input_command), drives_{{2045454, 300, 1}, {2045454, 300, 1}} { - drives_[0].set_sleep_observer(this); - drives_[1].set_sleep_observer(this); + drives_[0].set_clocking_hint_observer(this); + drives_[1].set_clocking_hint_observer(this); drives_[active_drive_].set_event_delegate(this); } @@ -73,7 +73,7 @@ void DiskII::select_drive(int drive) { } void DiskII::run_for(const Cycles cycles) { - if(is_sleeping()) return; + if(preferred_clocking() == ClockingHint::Preference::None) return; if(!controller_can_sleep_) { int integer_cycles = cycles.as_int(); @@ -137,7 +137,7 @@ void DiskII::set_controller_can_sleep() { (shift_register_ == (is_write_protected() ? 0xff : 0x00)) ); if(controller_could_sleep != controller_can_sleep_) - update_sleep_observer(); + update_clocking_observer(); } bool DiskII::is_write_protected() { @@ -199,14 +199,14 @@ void DiskII::process_event(const Storage::Disk::Track::Event &event) { } } -void DiskII::set_component_is_sleeping(Sleeper *component, bool is_sleeping) { - drive_is_sleeping_[0] = drives_[0].is_sleeping(); - drive_is_sleeping_[1] = drives_[1].is_sleeping(); - update_sleep_observer(); +void DiskII::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) { + drive_is_sleeping_[0] = drives_[0].preferred_clocking() == ClockingHint::Preference::None; + drive_is_sleeping_[1] = drives_[1].preferred_clocking() == ClockingHint::Preference::None; + update_clocking_observer(); } -bool DiskII::is_sleeping() { - return controller_can_sleep_ && drive_is_sleeping_[0] && drive_is_sleeping_[1]; +ClockingHint::Preference DiskII::preferred_clocking() { + return (controller_can_sleep_ && drive_is_sleeping_[0] && drive_is_sleeping_[1]) ? ClockingHint::Preference::None : ClockingHint::Preference::RealTime; } void DiskII::set_data_input(uint8_t input) { diff --git a/Components/DiskII/DiskII.hpp b/Components/DiskII/DiskII.hpp index ee81e0892..96904142c 100644 --- a/Components/DiskII/DiskII.hpp +++ b/Components/DiskII/DiskII.hpp @@ -10,7 +10,7 @@ #define DiskII_hpp #include "../../ClockReceiver/ClockReceiver.hpp" -#include "../../ClockReceiver/Sleeper.hpp" +#include "../../ClockReceiver/ClockingHintSource.hpp" #include "../../Storage/Disk/Disk.hpp" #include "../../Storage/Disk/Drive.hpp" @@ -28,8 +28,8 @@ namespace Apple { */ class DiskII: public Storage::Disk::Drive::EventDelegate, - public Sleeper::SleepObserver, - public Sleeper { + public ClockingHint::Source, + public ClockingHint::Observer { public: DiskII(); @@ -76,7 +76,7 @@ class DiskII: void set_disk(const std::shared_ptr &disk, int drive); // As per Sleeper. - bool is_sleeping() override; + ClockingHint::Preference preferred_clocking() override; // The Disk II functions as a potential target for @c Activity::Sources. void set_activity_observer(Activity::Observer *observer); @@ -95,7 +95,7 @@ class DiskII: uint8_t trigger_address(int address, uint8_t value); void process_event(const Storage::Disk::Track::Event &event) override; - void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override; + void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override; uint8_t state_ = 0; uint8_t inputs_ = 0; diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 10dca0dac..e5557533a 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -694,7 +694,7 @@ class ConcreteMachine: public KeyboardMachine::Machine, public Utility::TypeRecipient, public CPU::Z80::BusHandler, - public Sleeper::SleepObserver, + public ClockingHint::Observer, public Machine, public Activity::Source { public: @@ -714,11 +714,8 @@ class ConcreteMachine: Memory::Fuzz(ram_, sizeof(ram_)); // register this class as the sleep observer for the FDC and tape - fdc_.set_sleep_observer(this); - fdc_is_sleeping_ = fdc_.is_sleeping(); - - tape_player_.set_sleep_observer(this); - tape_player_is_sleeping_ = tape_player_.is_sleeping(); + fdc_.set_clocking_hint_observer(this); + tape_player_.set_clocking_hint_observer(this); ay_.ay().set_port_handler(&key_state_); } @@ -967,9 +964,9 @@ class ConcreteMachine: return true; } - void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override final { - fdc_is_sleeping_ = fdc_.is_sleeping(); - tape_player_is_sleeping_ = tape_player_.is_sleeping(); + void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override final { + fdc_is_sleeping_ = fdc_.preferred_clocking() == ClockingHint::Preference::None; + tape_player_is_sleeping_ = tape_player_.preferred_clocking() == ClockingHint::Preference::None; } // MARK: - Keyboard diff --git a/Machines/AppleII/DiskIICard.cpp b/Machines/AppleII/DiskIICard.cpp index 159c062ed..3421a1907 100644 --- a/Machines/AppleII/DiskIICard.cpp +++ b/Machines/AppleII/DiskIICard.cpp @@ -20,7 +20,7 @@ DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sec boot_ = std::move(*roms[0]); diskii_.set_state_machine(*roms[1]); set_select_constraints(None); - diskii_.set_sleep_observer(this); + diskii_.set_clocking_hint_observer(this); } void DiskIICard::perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) { @@ -41,7 +41,7 @@ void DiskIICard::perform_bus_operation(Select select, bool is_read, uint16_t add } void DiskIICard::run_for(Cycles cycles, int stretches) { - if(diskii_is_sleeping_) return; + if(diskii_clocking_preference_ == ClockingHint::Preference::None) return; diskii_.run_for(Cycles(cycles.as_int() * 2)); } @@ -53,7 +53,7 @@ void DiskIICard::set_activity_observer(Activity::Observer *observer) { diskii_.set_activity_observer(observer); } -void DiskIICard::set_component_is_sleeping(Sleeper *component, bool is_sleeping) { - diskii_is_sleeping_ = is_sleeping; - set_select_constraints(is_sleeping ? (IO | Device) : 0); +void DiskIICard::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) { + diskii_clocking_preference_ = preference; + set_select_constraints((preference != ClockingHint::Preference::RealTime) ? (IO | Device) : 0); } diff --git a/Machines/AppleII/DiskIICard.hpp b/Machines/AppleII/DiskIICard.hpp index 9d5eaa8ec..2072241a1 100644 --- a/Machines/AppleII/DiskIICard.hpp +++ b/Machines/AppleII/DiskIICard.hpp @@ -14,7 +14,7 @@ #include "../../Components/DiskII/DiskII.hpp" #include "../../Storage/Disk/Disk.hpp" -#include "../../ClockReceiver/Sleeper.hpp" +#include "../../ClockReceiver/ClockingHintSource.hpp" #include #include @@ -22,7 +22,7 @@ namespace AppleII { -class DiskIICard: public Card, public Sleeper::SleepObserver { +class DiskIICard: public Card, public ClockingHint::Observer { public: DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector); @@ -34,10 +34,10 @@ class DiskIICard: public Card, public Sleeper::SleepObserver { void set_disk(const std::shared_ptr &disk, int drive); private: - void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override; + void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override; std::vector boot_; Apple::DiskII diskii_; - bool diskii_is_sleeping_ = false; + ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime; }; } diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 6c00d7d28..cb7248524 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -304,7 +304,7 @@ class ConcreteMachine: public Utility::TypeRecipient, public Storage::Tape::BinaryTapePlayer::Delegate, public Machine, - public Sleeper::SleepObserver, + public ClockingHint::Observer, public Activity::Source { public: ConcreteMachine() : @@ -331,7 +331,7 @@ class ConcreteMachine: user_port_via_port_handler_->set_interrupt_delegate(this); keyboard_via_port_handler_->set_interrupt_delegate(this); tape_->set_delegate(this); - tape_->set_sleep_observer(this); + tape_->set_clocking_hint_observer(this); // install a joystick joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_)); @@ -749,8 +749,8 @@ class ConcreteMachine: return selection_set; } - void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override { - tape_is_sleeping_ = is_sleeping; + void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override { + tape_is_sleeping_ = clocking == ClockingHint::Preference::None; set_use_fast_tape(); } diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 0281f9ba1..6b36bd1b8 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -88,7 +88,7 @@ class ConcreteMachine: public KeyboardMachine::Machine, public Configurable::Device, public MemoryMap, - public Sleeper::SleepObserver, + public ClockingHint::Observer, public Activity::Source { public: ConcreteMachine(): @@ -108,7 +108,7 @@ class ConcreteMachine: ay_.set_port_handler(&ay_port_handler_); speaker_.set_input_rate(3579545.0f / 2.0f); - tape_player_.set_sleep_observer(this); + tape_player_.set_clocking_hint_observer(this); // Set the AY to 50% of available volume, the toggle to 10% and leave 40% for an SCC. mixer_.set_relative_volumes({0.5f, 0.1f, 0.4f}); @@ -555,8 +555,8 @@ class ConcreteMachine: } // MARK: - Sleeper - void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override { - tape_player_is_sleeping_ = tape_player_.is_sleeping(); + void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override { + tape_player_is_sleeping_ = tape_player_.preferred_clocking() == ClockingHint::Preference::None; set_use_fast_tape(); } diff --git a/Machines/Oric/Microdisc.hpp b/Machines/Oric/Microdisc.hpp index 3aef6aefe..fe3d0f0ef 100644 --- a/Machines/Oric/Microdisc.hpp +++ b/Machines/Oric/Microdisc.hpp @@ -28,7 +28,6 @@ class Microdisc: public WD::WD1770 { bool get_interrupt_request_line(); void run_for(const Cycles cycles); - using WD::WD1770::run_for; enum PagingFlags { /// Indicates that the BASIC ROM should be disabled; if this is set then either @@ -52,8 +51,9 @@ class Microdisc: public WD::WD1770 { private: void set_control_register(uint8_t control, uint8_t changes); - void set_head_load_request(bool head_load); + void set_head_load_request(bool head_load) override; bool get_drive_is_ready(); + std::array, 4> drives_; size_t selected_drive_; bool irq_enable_ = false; diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index 30b7c1c39..5dda32e31 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -201,7 +201,7 @@ template class Co public Utility::TypeRecipient, public Storage::Tape::BinaryTapePlayer::Delegate, public Microdisc::Delegate, - public Sleeper::SleepObserver, + public ClockingHint::Observer, public Activity::Source, public Machine { @@ -219,7 +219,7 @@ template class Co Memory::Fuzz(ram_, sizeof(ram_)); if(disk_interface == Analyser::Static::Oric::Target::DiskInterface::Pravetz) { - diskii_.set_sleep_observer(this); + diskii_.set_clocking_hint_observer(this); } } @@ -571,8 +571,8 @@ template class Co } } - void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override final { - diskii_is_sleeping_ = diskii_.is_sleeping(); + void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference is_sleeping) override final { + diskii_is_sleeping_ = diskii_.preferred_clocking() == ClockingHint::Preference::None; } private: diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 8ffd5537e..2355efa97 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1037,7 +1037,7 @@ 4BB06B211F316A3F00600C7A /* ForceInline.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ForceInline.hpp; sourceTree = ""; }; 4BB0A6592044FD3000FB3688 /* SN76489.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SN76489.cpp; sourceTree = ""; }; 4BB0A65A2044FD3000FB3688 /* SN76489.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SN76489.hpp; sourceTree = ""; }; - 4BB146C61F49D7D700253439 /* Sleeper.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sleeper.hpp; sourceTree = ""; }; + 4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ClockingHintSource.hpp; sourceTree = ""; }; 4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.expected.json; path = FUSE/tests.expected.json; sourceTree = ""; }; 4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.in.json; path = FUSE/tests.in.json; sourceTree = ""; }; 4BB297E51B587D8300A49093 /* start */ = {isa = PBXFileReference; lastKnownFileType = file; path = " start"; sourceTree = ""; }; @@ -3124,7 +3124,7 @@ children = ( 4BF6606A1F281573002CB053 /* ClockReceiver.hpp */, 4BB06B211F316A3F00600C7A /* ForceInline.hpp */, - 4BB146C61F49D7D700253439 /* Sleeper.hpp */, + 4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */, 4B449C942063389900A095C8 /* TimeTypes.hpp */, ); name = ClockReceiver; diff --git a/Storage/Disk/Controller/DiskController.cpp b/Storage/Disk/Controller/DiskController.cpp index f4c403664..60996efde 100644 --- a/Storage/Disk/Controller/DiskController.cpp +++ b/Storage/Disk/Controller/DiskController.cpp @@ -22,12 +22,12 @@ Controller::Controller(Cycles clock_rate) : set_drive(empty_drive_); } -void Controller::set_component_is_sleeping(Sleeper *component, bool is_sleeping) { - update_sleep_observer(); +void Controller::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) { + update_clocking_observer(); } -bool Controller::is_sleeping() { - return !drive_ || drive_->is_sleeping(); +ClockingHint::Preference Controller::preferred_clocking() { + return (!drive_ || (drive_->preferred_clocking() == ClockingHint::Preference::None)) ? ClockingHint::Preference::None : ClockingHint::Preference::RealTime; } void Controller::run_for(const Cycles cycles) { @@ -77,23 +77,23 @@ void Controller::digital_phase_locked_loop_output_bit(int value) { void Controller::set_drive(std::shared_ptr drive) { if(drive_ != drive) { - bool was_sleeping = is_sleeping(); + ClockingHint::Preference former_prefernece = preferred_clocking(); // invalidate_track(); if(drive_) { drive_->set_event_delegate(nullptr); - drive_->set_sleep_observer(nullptr); + drive_->set_clocking_hint_observer(nullptr); } drive_ = drive; if(drive_) { drive_->set_event_delegate(this); - drive_->set_sleep_observer(this); + drive_->set_clocking_hint_observer(this); } else { drive_ = empty_drive_; } - if(is_sleeping() != was_sleeping) { - update_sleep_observer(); + if(preferred_clocking() != former_prefernece) { + update_clocking_observer(); } } } diff --git a/Storage/Disk/Controller/DiskController.hpp b/Storage/Disk/Controller/DiskController.hpp index 3382db073..b900bc80f 100644 --- a/Storage/Disk/Controller/DiskController.hpp +++ b/Storage/Disk/Controller/DiskController.hpp @@ -15,7 +15,7 @@ #include "../Track/PCMPatchedTrack.hpp" #include "../../../ClockReceiver/ClockReceiver.hpp" -#include "../../../ClockReceiver/Sleeper.hpp" +#include "../../../ClockReceiver/ClockingHintSource.hpp" namespace Storage { namespace Disk { @@ -29,7 +29,11 @@ namespace Disk { TODO: communication of head size and permissible stepping extents, appropriate simulation of gain. */ -class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDelegate, public Sleeper, public Sleeper::SleepObserver { +class Controller: + public DigitalPhaseLockedLoop::Delegate, + public Drive::EventDelegate, + public ClockingHint::Source, + public ClockingHint::Observer { protected: /*! Constructs a @c Controller that will be run at @c clock_rate. @@ -65,7 +69,7 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDe Should be implemented by subclasses if they implement writing; communicates that all bits supplied to write_bit have now been written. */ - virtual void process_write_completed(); + virtual void process_write_completed() override; /*! Puts the controller and the drive returned by get_drive() into write mode, supplying to @@ -97,9 +101,9 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDe Drive &get_drive(); /*! - As per Sleeper. + As per ClockingHint::Source. */ - bool is_sleeping(); + ClockingHint::Preference preferred_clocking() override; private: Time bit_length_; @@ -113,14 +117,15 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDe std::shared_ptr empty_drive_; - void set_component_is_sleeping(Sleeper *component, bool is_sleeping); + // ClockingHint::Observer. + void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override; // for Drive::EventDelegate - void process_event(const Track::Event &event); - void advance(const Cycles cycles); + void process_event(const Track::Event &event) override; + void advance(const Cycles cycles) override ; // to satisfy DigitalPhaseLockedLoop::Delegate - void digital_phase_locked_loop_output_bit(int value); + void digital_phase_locked_loop_output_bit(int value) override; }; } diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index d2c61f98b..535f19624 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -46,15 +46,15 @@ void Drive::set_disk(const std::shared_ptr &disk) { has_disk_ = !!disk_; invalidate_track(); - update_sleep_observer(); + update_clocking_observer(); } bool Drive::has_disk() { return has_disk_; } -bool Drive::is_sleeping() { - return !motor_is_on_ || !has_disk_; +ClockingHint::Preference Drive::preferred_clocking() { + return (!motor_is_on_ || !has_disk_) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime; } bool Drive::get_is_track_zero() { @@ -119,7 +119,7 @@ void Drive::set_motor_on(bool motor_is_on) { ready_index_count_ = 0; if(disk_) disk_->flush_tracks(); } - update_sleep_observer(); + update_clocking_observer(); } bool Drive::get_motor_on() { diff --git a/Storage/Disk/Drive.hpp b/Storage/Disk/Drive.hpp index a5601ea9a..8606534f8 100644 --- a/Storage/Disk/Drive.hpp +++ b/Storage/Disk/Drive.hpp @@ -15,14 +15,14 @@ #include "../TimedEventLoop.hpp" #include "../../Activity/Observer.hpp" -#include "../../ClockReceiver/Sleeper.hpp" +#include "../../ClockReceiver/ClockingHintSource.hpp" #include namespace Storage { namespace Disk { -class Drive: public Sleeper, public TimedEventLoop { +class Drive: public ClockingHint::Source, public TimedEventLoop { public: Drive(unsigned int input_clock_rate, int revolutions_per_minute, int number_of_heads); ~Drive(); @@ -121,7 +121,7 @@ class Drive: public Sleeper, public TimedEventLoop { void set_event_delegate(EventDelegate *); // As per Sleeper. - bool is_sleeping(); + ClockingHint::Preference preferred_clocking() override; /// Adds an activity observer; it'll be notified of disk activity. /// The caller can specify whether to add an LED based on disk motor. @@ -171,9 +171,9 @@ class Drive: public Sleeper, public TimedEventLoop { Time cycles_per_bit_; // TimedEventLoop call-ins and state. - void process_next_event(); + void process_next_event() override; void get_next_event(const Time &duration_already_passed); - void advance(const Cycles cycles); + void advance(const Cycles cycles) override; Track::Event current_event_; // Helper for track changes. diff --git a/Storage/Tape/Tape.cpp b/Storage/Tape/Tape.cpp index 1b66af089..5b83c05b1 100644 --- a/Storage/Tape/Tape.cpp +++ b/Storage/Tape/Tape.cpp @@ -65,15 +65,15 @@ void Tape::set_offset(uint64_t offset) { // MARK: - Player -bool TapePlayer::is_sleeping() { - return !tape_ || tape_->is_at_end(); +ClockingHint::Preference TapePlayer::preferred_clocking() { + return (!tape_ || tape_->is_at_end()) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime; } void TapePlayer::set_tape(std::shared_ptr tape) { tape_ = tape; reset_timer(); get_next_pulse(); - update_sleep_observer(); + update_clocking_observer(); } std::shared_ptr TapePlayer::get_tape() { @@ -88,7 +88,7 @@ void TapePlayer::get_next_pulse() { // get the new pulse if(tape_) { current_pulse_ = tape_->get_next_pulse(); - if(tape_->is_at_end()) update_sleep_observer(); + if(tape_->is_at_end()) update_clocking_observer(); } else { current_pulse_.length.length = 1; current_pulse_.length.clock_rate = 1; @@ -119,14 +119,15 @@ BinaryTapePlayer::BinaryTapePlayer(unsigned int input_clock_rate) : TapePlayer(input_clock_rate) {} -bool BinaryTapePlayer::is_sleeping() { - return !motor_is_running_ || TapePlayer::is_sleeping(); +ClockingHint::Preference BinaryTapePlayer::preferred_clocking() { + if(!motor_is_running_) return ClockingHint::Preference::None; + return TapePlayer::preferred_clocking(); } void BinaryTapePlayer::set_motor_control(bool enabled) { if(motor_is_running_ != enabled) { motor_is_running_ = enabled; - update_sleep_observer(); + update_clocking_observer(); } } diff --git a/Storage/Tape/Tape.hpp b/Storage/Tape/Tape.hpp index b28e815c4..329da3e3b 100644 --- a/Storage/Tape/Tape.hpp +++ b/Storage/Tape/Tape.hpp @@ -12,7 +12,7 @@ #include #include "../../ClockReceiver/ClockReceiver.hpp" -#include "../../ClockReceiver/Sleeper.hpp" +#include "../../ClockReceiver/ClockingHintSource.hpp" #include "../TimedEventLoop.hpp" @@ -95,7 +95,7 @@ class Tape { Will call @c process_input_pulse instantaneously upon reaching *the end* of a pulse. Therefore a subclass can decode pulses into data within process_input_pulse, using the supplied pulse's @c length and @c type. */ -class TapePlayer: public TimedEventLoop, public Sleeper { +class TapePlayer: public TimedEventLoop, public ClockingHint::Source { public: TapePlayer(unsigned int input_clock_rate); @@ -107,10 +107,10 @@ class TapePlayer: public TimedEventLoop, public Sleeper { void run_for_input_pulse(); - bool is_sleeping(); + ClockingHint::Preference preferred_clocking() override; protected: - virtual void process_next_event(); + virtual void process_next_event() override; virtual void process_input_pulse(const Tape::Pulse &pulse) = 0; private: @@ -145,11 +145,11 @@ class BinaryTapePlayer: public TapePlayer { }; void set_delegate(Delegate *delegate); - bool is_sleeping(); + ClockingHint::Preference preferred_clocking() override; protected: Delegate *delegate_ = nullptr; - virtual void process_input_pulse(const Storage::Tape::Tape::Pulse &pulse); + void process_input_pulse(const Storage::Tape::Tape::Pulse &pulse) override; bool input_level_ = false; bool motor_is_running_ = false; }; From f3fe711542d0cbf50dc236af21280313796ee2ea Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 27 May 2018 23:55:04 -0400 Subject: [PATCH 2/9] Attempts to reduce FDC costs. --- Machines/AmstradCPC/AmstradCPC.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index e5557533a..c484b7add 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -746,7 +746,7 @@ class ConcreteMachine: ay_.run_for(cycle.length); // Clock the FDC, if connected, using a lazy scale by two - if(has_fdc_ && !fdc_is_sleeping_) fdc_.run_for(Cycles(cycle.length.as_int())); + time_since_fdc_update_ += cycle.length; // Update typing activity if(typer_) typer_->run_for(cycle.length); @@ -793,11 +793,13 @@ class ConcreteMachine: // Check for an FDC access if(has_fdc_ && (address & 0x580) == 0x100) { + flush_fdc(); fdc_.set_register(address & 1, *cycle.value); } // Check for a disk motor access if(has_fdc_ && !(address & 0x580)) { + flush_fdc(); fdc_.set_motor_on(!!(*cycle.value)); } break; @@ -812,6 +814,7 @@ class ConcreteMachine: // Check for an FDC access if(has_fdc_ && (address & 0x580) == 0x100) { + flush_fdc(); *cycle.value &= fdc_.get_register(address & 1); } @@ -855,6 +858,7 @@ class ConcreteMachine: // Just flush the AY. ay_.update(); ay_.flush(); + flush_fdc(); } /// A CRTMachine function; indicates that outputs should be created now. @@ -967,6 +971,7 @@ class ConcreteMachine: void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override final { fdc_is_sleeping_ = fdc_.preferred_clocking() == ClockingHint::Preference::None; tape_player_is_sleeping_ = tape_player_.preferred_clocking() == ClockingHint::Preference::None; + printf("FDC: %s, tape %s\n", fdc_is_sleeping_ ? "sleeping" : "regular", tape_player_is_sleeping_ ? "sleeping" : "regular"); } // MARK: - Keyboard @@ -1060,6 +1065,14 @@ class ConcreteMachine: Intel::i8255::i8255 i8255_; FDC fdc_; + HalfCycles time_since_fdc_update_; + void flush_fdc() { + // Clock the FDC, if connected, using a lazy scale by two + if(has_fdc_ && !fdc_is_sleeping_) { + fdc_.run_for(Cycles(time_since_fdc_update_.as_int())); + } + time_since_fdc_update_ = HalfCycles(0); + } InterruptTimer interrupt_timer_; Storage::Tape::BinaryTapePlayer tape_player_; From 928aab13dc8d86f9ffaef7137982de66c1c87f18 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 28 May 2018 17:19:29 -0400 Subject: [PATCH 3/9] Introduces more granular clocking announcements to the Disk II. As well as making it accept the clock rate it'll actually receive, to supply to the drives, so that they spin at the proper speed. --- Components/DiskII/DiskII.cpp | 127 +++++++++++++++++--------------- Components/DiskII/DiskII.hpp | 8 +- Machines/AppleII/DiskIICard.cpp | 2 +- Machines/Oric/Oric.cpp | 3 +- 4 files changed, 74 insertions(+), 66 deletions(-) diff --git a/Components/DiskII/DiskII.cpp b/Components/DiskII/DiskII.cpp index e8d6539ce..e2f0c8f23 100644 --- a/Components/DiskII/DiskII.cpp +++ b/Components/DiskII/DiskII.cpp @@ -19,9 +19,10 @@ namespace { const uint8_t input_flux = 0x1; } -DiskII::DiskII() : +DiskII::DiskII(int clock_rate) : + clock_rate_(clock_rate), inputs_(input_command), - drives_{{2045454, 300, 1}, {2045454, 300, 1}} + drives_{{static_cast(clock_rate), 300, 1}, {static_cast(clock_rate), 300, 1}} { drives_[0].set_clocking_hint_observer(this); drives_[1].set_clocking_hint_observer(this); @@ -75,68 +76,72 @@ void DiskII::select_drive(int drive) { void DiskII::run_for(const Cycles cycles) { if(preferred_clocking() == ClockingHint::Preference::None) return; - if(!controller_can_sleep_) { - int integer_cycles = cycles.as_int(); - while(integer_cycles--) { - const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6); - inputs_ |= input_flux; - state_ = state_machine_[static_cast(address)]; - switch(state_ & 0xf) { - default: shift_register_ = 0; break; // clear - case 0x8: break; // nop + int integer_cycles = cycles.as_int(); + while(integer_cycles--) { + const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6); + inputs_ |= input_flux; + state_ = state_machine_[static_cast(address)]; + switch(state_ & 0xf) { + default: shift_register_ = 0; break; // clear + case 0x8: break; // nop - case 0x9: shift_register_ = static_cast(shift_register_ << 1); break; // shift left, bringing in a zero - case 0xd: shift_register_ = static_cast((shift_register_ << 1) | 1); break; // shift left, bringing in a one + case 0x9: shift_register_ = static_cast(shift_register_ << 1); break; // shift left, bringing in a zero + case 0xd: shift_register_ = static_cast((shift_register_ << 1) | 1); break; // shift left, bringing in a one - case 0xa: // shift right, bringing in write protected status - shift_register_ = (shift_register_ >> 1) | (is_write_protected() ? 0x80 : 0x00); + case 0xa: // shift right, bringing in write protected status + shift_register_ = (shift_register_ >> 1) | (is_write_protected() ? 0x80 : 0x00); - // If the controller is in the sense write protect loop but the register will never change, - // short circuit further work and return now. - if(shift_register_ == is_write_protected() ? 0xff : 0x00) { - if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(integer_cycles)); - if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(integer_cycles)); - set_controller_can_sleep(); - return; - } - break; - case 0xb: shift_register_ = data_input_; break; // load data register from data bus - } - - // Currently writing? - if(inputs_&input_mode) { - // state_ & 0x80 should be the current level sent to the disk; - // therefore transitions in that bit should become flux transitions - drives_[active_drive_].write_bit(!!((state_ ^ address) & 0x80)); - } - - // TODO: surely there's a less heavyweight solution than this? - if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(1)); - if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(1)); + // If the controller is in the sense write protect loop but the register will never change, + // short circuit further work and return now. + if(shift_register_ == is_write_protected() ? 0xff : 0x00) { + if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(integer_cycles)); + if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(integer_cycles)); + decide_clocking_preference(); + return; + } + break; + case 0xb: shift_register_ = data_input_; break; // load data register from data bus } - } else { - if(!drive_is_sleeping_[0]) drives_[0].run_for(cycles); - if(!drive_is_sleeping_[1]) drives_[1].run_for(cycles); + + // Currently writing? + if(inputs_&input_mode) { + // state_ & 0x80 should be the current level sent to the disk; + // therefore transitions in that bit should become flux transitions + drives_[active_drive_].write_bit(!!((state_ ^ address) & 0x80)); + } + + // TODO: surely there's a less heavyweight solution than inline updates? + if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(1)); + if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(1)); } - set_controller_can_sleep(); + decide_clocking_preference(); } -void DiskII::set_controller_can_sleep() { - // Permit the controller to sleep if it's in sense write protect mode, and the shift register - // has already filled with the result of shifting eight times. - bool controller_could_sleep = controller_can_sleep_; - controller_can_sleep_ = - ( - (inputs_ == input_flux) && - !motor_is_enabled_ && - !shift_register_ - ) || - ( - (inputs_ == (input_command | input_flux)) && - (shift_register_ == (is_write_protected() ? 0xff : 0x00)) - ); - if(controller_could_sleep != controller_can_sleep_) +void DiskII::decide_clocking_preference() { + ClockingHint::Preference prior_preference = clocking_preference_; + + // If in read mode, clocking is either: + // + // just-in-time, if drives are running or the shift register has any 1s in it or a flux event hasn't yet passed; or + // none, given that drives are not running, the shift register has already emptied and there's no flux about to fire. + if(!(inputs_ & ~input_flux)) { + clocking_preference_ = (!motor_is_enabled_ && !shift_register_ && !(inputs_&input_flux)) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime; + } + + // If in writing mode, clocking is real time. + if(inputs_ & input_mode) { + clocking_preference_ = ClockingHint::Preference::RealTime; + } + + // If in sense-write-protect mode, clocking is just-in-time if the shift register hasn't yet filled with the value that + // corresponds to the current write protect status. Otherwise it is none. + if((inputs_ & ~input_flux) == input_command) { + clocking_preference_ = (shift_register_ == (is_write_protected() ? 0xff : 0x00)) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime; + } + + // Announce a change if there was one. + if(prior_preference != clocking_preference_) update_clocking_observer(); } @@ -195,18 +200,18 @@ void DiskII::set_disk(const std::shared_ptr &disk, int driv void DiskII::process_event(const Storage::Disk::Track::Event &event) { if(event.type == Storage::Disk::Track::Event::FluxTransition) { inputs_ &= ~input_flux; - set_controller_can_sleep(); + decide_clocking_preference(); } } void DiskII::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) { drive_is_sleeping_[0] = drives_[0].preferred_clocking() == ClockingHint::Preference::None; drive_is_sleeping_[1] = drives_[1].preferred_clocking() == ClockingHint::Preference::None; - update_clocking_observer(); + decide_clocking_preference(); } ClockingHint::Preference DiskII::preferred_clocking() { - return (controller_can_sleep_ && drive_is_sleeping_[0] && drive_is_sleeping_[1]) ? ClockingHint::Preference::None : ClockingHint::Preference::RealTime; + return clocking_preference_; } void DiskII::set_data_input(uint8_t input) { @@ -243,11 +248,11 @@ int DiskII::read_address(int address) { break; case 0xf: if(!(inputs_ & input_mode)) - drives_[active_drive_].begin_writing(Storage::Time(1, 2045454), false); + drives_[active_drive_].begin_writing(Storage::Time(1, clock_rate_), false); inputs_ |= input_mode; break; } - set_controller_can_sleep(); + decide_clocking_preference(); return (address & 1) ? 0xff : shift_register_; } diff --git a/Components/DiskII/DiskII.hpp b/Components/DiskII/DiskII.hpp index 96904142c..31773b59e 100644 --- a/Components/DiskII/DiskII.hpp +++ b/Components/DiskII/DiskII.hpp @@ -31,7 +31,7 @@ class DiskII: public ClockingHint::Source, public ClockingHint::Observer { public: - DiskII(); + DiskII(int clock_rate); /// Sets the current external value of the data bus. void set_data_input(uint8_t input); @@ -97,6 +97,8 @@ class DiskII: void process_event(const Storage::Disk::Track::Event &event) override; void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override; + const int clock_rate_ = 0; + uint8_t state_ = 0; uint8_t inputs_ = 0; uint8_t shift_register_ = 0; @@ -108,11 +110,11 @@ class DiskII: std::array state_machine_; Storage::Disk::Drive drives_[2]; bool drive_is_sleeping_[2]; - bool controller_can_sleep_ = false; int active_drive_ = 0; bool motor_is_enabled_ = false; - void set_controller_can_sleep(); + void decide_clocking_preference(); + ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::RealTime; uint8_t data_input_ = 0; }; diff --git a/Machines/AppleII/DiskIICard.cpp b/Machines/AppleII/DiskIICard.cpp index 3421a1907..a914025db 100644 --- a/Machines/AppleII/DiskIICard.cpp +++ b/Machines/AppleII/DiskIICard.cpp @@ -10,7 +10,7 @@ using namespace AppleII; -DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) { +DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) : diskii_(2045454) { auto roms = rom_fetcher( "DiskII", { diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index 5dda32e31..2478b8054 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -212,7 +212,8 @@ template class Co ay8910_(audio_queue_), speaker_(ay8910_), via_port_handler_(audio_queue_, ay8910_, speaker_, tape_player_, keyboard_), - via_(via_port_handler_) { + via_(via_port_handler_), + diskii_(2000000) { set_clock_rate(1000000); via_port_handler_.set_interrupt_delegate(this); tape_player_.set_delegate(this); From 8a566cc1dd2f997a707cd013a6c8b9538f980af4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 28 May 2018 17:20:11 -0400 Subject: [PATCH 4/9] Experimentally goes to town on `constexpr`. --- ClockReceiver/ClockReceiver.hpp | 75 ++++++++++++++++----------------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/ClockReceiver/ClockReceiver.hpp b/ClockReceiver/ClockReceiver.hpp index 45bc776f9..245d84cb3 100644 --- a/ClockReceiver/ClockReceiver.hpp +++ b/ClockReceiver/ClockReceiver.hpp @@ -52,79 +52,79 @@ */ template class WrappedInt { public: - inline WrappedInt(int l) : length_(l) {} - inline WrappedInt() : length_(0) {} + constexpr WrappedInt(int l) : length_(l) {} + constexpr WrappedInt() : length_(0) {} - inline T &operator =(const T &rhs) { + constexpr T &operator =(const T &rhs) { length_ = rhs.length_; return *this; } - inline T &operator +=(const T &rhs) { + constexpr T &operator +=(const T &rhs) { length_ += rhs.length_; return *static_cast(this); } - inline T &operator -=(const T &rhs) { + constexpr T &operator -=(const T &rhs) { length_ -= rhs.length_; return *static_cast(this); } - inline T &operator ++() { + constexpr T &operator ++() { ++ length_; return *static_cast(this); } - inline T &operator ++(int) { + constexpr T &operator ++(int) { length_ ++; return *static_cast(this); } - inline T &operator --() { + constexpr T &operator --() { -- length_; return *static_cast(this); } - inline T &operator --(int) { + constexpr T &operator --(int) { length_ --; return *static_cast(this); } - inline T &operator %=(const T &rhs) { + constexpr T &operator %=(const T &rhs) { length_ %= rhs.length_; return *static_cast(this); } - inline T &operator &=(const T &rhs) { + constexpr T &operator &=(const T &rhs) { length_ &= rhs.length_; return *static_cast(this); } - inline T operator +(const T &rhs) const { return T(length_ + rhs.length_); } - inline 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_); } - inline T operator %(const T &rhs) const { return T(length_ % rhs.length_); } - inline 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_); } - inline T operator -() const { return T(- length_); } + constexpr T operator -() const { return T(- length_); } - inline bool operator <(const T &rhs) const { return length_ < rhs.length_; } - inline bool operator >(const T &rhs) const { return length_ > rhs.length_; } - inline bool operator <=(const T &rhs) const { return length_ <= rhs.length_; } - inline bool operator >=(const T &rhs) const { return length_ >= rhs.length_; } - inline bool operator ==(const T &rhs) const { return length_ == rhs.length_; } - inline 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_; } + constexpr bool operator !=(const T &rhs) const { return length_ != rhs.length_; } - inline bool operator !() const { return !length_; } + 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 - inline int as_int() const { return length_; } + 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. */ - inline T divide(const T &divisor) { + constexpr T divide(const T &divisor) { T result(length_ / divisor.length_); length_ %= divisor.length_; return result; @@ -134,7 +134,7 @@ template class WrappedInt { Flushes the value in @c this. The current value is returned, and the internal value is reset to zero. */ - inline T flush() { + constexpr T flush() { T result(length_); length_ = 0; return result; @@ -150,34 +150,34 @@ template class WrappedInt { /// Describes an integer number of whole cycles: pairs of clock signal transitions. class Cycles: public WrappedInt { public: - inline Cycles(int l) : WrappedInt(l) {} - inline Cycles() : WrappedInt() {} - inline Cycles(const Cycles &cycles) : WrappedInt(cycles.length_) {} + constexpr Cycles(int l) : WrappedInt(l) {} + constexpr Cycles() : WrappedInt() {} + constexpr Cycles(const Cycles &cycles) : WrappedInt(cycles.length_) {} }; /// Describes an integer number of half cycles: single clock signal transitions. class HalfCycles: public WrappedInt { public: - inline HalfCycles(int l) : WrappedInt(l) {} - inline HalfCycles() : WrappedInt() {} + constexpr HalfCycles(int l) : WrappedInt(l) {} + constexpr HalfCycles() : WrappedInt() {} - inline HalfCycles(const Cycles cycles) : WrappedInt(cycles.as_int() * 2) {} - inline HalfCycles(const HalfCycles &half_cycles) : WrappedInt(half_cycles.length_) {} + constexpr HalfCycles(const Cycles cycles) : WrappedInt(cycles.as_int() * 2) {} + constexpr HalfCycles(const HalfCycles &half_cycles) : WrappedInt(half_cycles.length_) {} /// @returns The number of whole cycles completely covered by this span of half cycles. - inline Cycles cycles() { + constexpr Cycles cycles() { return Cycles(length_ >> 1); } /// Flushes the whole cycles in @c this, subtracting that many from the total stored here. - inline Cycles flush_cycles() { + constexpr 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. - inline HalfCycles flush() { + constexpr HalfCycles flush() { HalfCycles result(length_); length_ = 0; return result; @@ -187,7 +187,7 @@ 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. */ - inline Cycles divide_cycles(const Cycles &divisor) { + constexpr Cycles divide_cycles(const Cycles &divisor) { HalfCycles half_divisor = HalfCycles(divisor); Cycles result(length_ / half_divisor.length_); length_ %= half_divisor.length_; @@ -203,7 +203,6 @@ template class HalfClockReceiver: public T { public: using T::T; - using T::run_for; inline void run_for(const HalfCycles half_cycles) { half_cycles_ += half_cycles; T::run_for(half_cycles_.flush_cycles()); From ad5afe21eedbd5e05e0fffc5752f5408c06aae29 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 28 May 2018 17:28:57 -0400 Subject: [PATCH 5/9] Removes `constexpr` from things which are not const. Duh. --- ClockReceiver/ClockReceiver.hpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ClockReceiver/ClockReceiver.hpp b/ClockReceiver/ClockReceiver.hpp index 245d84cb3..a5a0b3186 100644 --- a/ClockReceiver/ClockReceiver.hpp +++ b/ClockReceiver/ClockReceiver.hpp @@ -70,32 +70,32 @@ template class WrappedInt { return *static_cast(this); } - constexpr T &operator ++() { + T &operator ++() { ++ length_; return *static_cast(this); } - constexpr T &operator ++(int) { + T &operator ++(int) { length_ ++; return *static_cast(this); } - constexpr T &operator --() { + T &operator --() { -- length_; return *static_cast(this); } - constexpr T &operator --(int) { + T &operator --(int) { length_ --; return *static_cast(this); } - constexpr T &operator %=(const T &rhs) { + T &operator %=(const T &rhs) { length_ %= rhs.length_; return *static_cast(this); } - constexpr T &operator &=(const T &rhs) { + T &operator &=(const T &rhs) { length_ &= rhs.length_; return *static_cast(this); } @@ -124,7 +124,7 @@ template class WrappedInt { Severs from @c this the effect of dividing by @c divisor; @c this will end up with the value of @c this modulo @c divisor and @c divided by @c divisor is returned. */ - constexpr T divide(const T &divisor) { + T divide(const T &divisor) { T result(length_ / divisor.length_); length_ %= divisor.length_; return result; @@ -134,7 +134,7 @@ template class WrappedInt { Flushes the value in @c this. The current value is returned, and the internal value is reset to zero. */ - constexpr T flush() { + T flush() { T result(length_); length_ = 0; return result; @@ -170,14 +170,14 @@ class HalfCycles: public WrappedInt { } /// Flushes the whole cycles in @c this, subtracting that many from the total stored here. - constexpr Cycles flush_cycles() { + 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. - constexpr HalfCycles flush() { + HalfCycles flush() { HalfCycles result(length_); length_ = 0; return result; @@ -187,7 +187,7 @@ 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. */ - constexpr Cycles divide_cycles(const Cycles &divisor) { + Cycles divide_cycles(const Cycles &divisor) { HalfCycles half_divisor = HalfCycles(divisor); Cycles result(length_ / half_divisor.length_); length_ %= half_divisor.length_; From 75f9e3caeb4dd9b5c96c3e27f01901cbea306b07 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 28 May 2018 17:48:35 -0400 Subject: [PATCH 6/9] Resolves incorrect bracketing. --- Components/DiskII/DiskII.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Components/DiskII/DiskII.cpp b/Components/DiskII/DiskII.cpp index e2f0c8f23..ef2093fd1 100644 --- a/Components/DiskII/DiskII.cpp +++ b/Components/DiskII/DiskII.cpp @@ -93,7 +93,7 @@ void DiskII::run_for(const Cycles cycles) { // If the controller is in the sense write protect loop but the register will never change, // short circuit further work and return now. - if(shift_register_ == is_write_protected() ? 0xff : 0x00) { + if(shift_register_ == (is_write_protected() ? 0xff : 0x00)) { if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(integer_cycles)); if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(integer_cycles)); decide_clocking_preference(); From 6c16754a6b99fbf6233fc48e50c8821405e89a02 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 28 May 2018 17:48:55 -0400 Subject: [PATCH 7/9] Strips further improper `constexpr`s. --- ClockReceiver/ClockReceiver.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ClockReceiver/ClockReceiver.hpp b/ClockReceiver/ClockReceiver.hpp index a5a0b3186..937eb17c0 100644 --- a/ClockReceiver/ClockReceiver.hpp +++ b/ClockReceiver/ClockReceiver.hpp @@ -55,17 +55,17 @@ template class WrappedInt { constexpr WrappedInt(int l) : length_(l) {} constexpr WrappedInt() : length_(0) {} - constexpr T &operator =(const T &rhs) { + T &operator =(const T &rhs) { length_ = rhs.length_; return *this; } - constexpr T &operator +=(const T &rhs) { + T &operator +=(const T &rhs) { length_ += rhs.length_; return *static_cast(this); } - constexpr T &operator -=(const T &rhs) { + T &operator -=(const T &rhs) { length_ -= rhs.length_; return *static_cast(this); } From 6812a001d8b2cbd6cffae733c0206d41cd14bb80 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 28 May 2018 18:20:43 -0400 Subject: [PATCH 8/9] Teaches the Oric to apply a lighter Disk II touch when possible. --- Machines/Oric/Oric.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index 2478b8054..ecf93e322 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -411,6 +411,7 @@ template class Co } } } else { + flush_diskii(); const int disk_value = diskii_.read_address(address); if(isReadOperation(operation) && disk_value != diskii_.DidNotLoad) *value = static_cast(disk_value); } @@ -445,9 +446,11 @@ template class Co microdisc_.run_for(Cycles(8)); break; case Analyser::Static::Oric::Target::DiskInterface::Pravetz: - if(!diskii_is_sleeping_) { + if(diskii_clocking_preference_ == ClockingHint::Preference::RealTime) { diskii_.set_data_input(*value); diskii_.run_for(Cycles(2)); + } else { + cycles_since_diskii_update_ += Cycles(2); } break; } @@ -458,6 +461,7 @@ template class Co forceinline void flush() { update_video(); via_port_handler_.flush(); + flush_diskii(); } // to satisfy CRTMachine::Machine @@ -572,8 +576,8 @@ template class Co } } - void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference is_sleeping) override final { - diskii_is_sleeping_ = diskii_.preferred_clocking() == ClockingHint::Preference::None; + void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override final { + diskii_clocking_preference_ = diskii_.preferred_clocking(); } private: @@ -618,9 +622,13 @@ template class Co // the Pravetz/Disk II, if in use Apple::DiskII diskii_; + Cycles cycles_since_diskii_update_; + void flush_diskii() { + diskii_.run_for(cycles_since_diskii_update_.flush()); + } std::vector pravetz_rom_; std::size_t pravetz_rom_base_pointer_ = 0; - bool diskii_is_sleeping_ = false; + ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime; // Overlay RAM uint16_t ram_top_ = basic_visible_ram_top_; From b2464598d0d76d234a8e9b4d9bcc5068bc35b968 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 28 May 2018 18:21:01 -0400 Subject: [PATCH 9/9] Forces the Apple II bus handler call inline. --- Machines/AppleII/AppleII.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index 4dab72590..27995bdb4 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -24,6 +24,8 @@ #include "DiskIICard.hpp" #include "Video.hpp" +#include "../../ClockReceiver/ForceInline.hpp" + #include "../../Analyser/Static/AppleII/Target.hpp" #include @@ -203,7 +205,7 @@ class ConcreteMachine: return &speaker_; } - Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { + forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { ++ cycles_since_video_update_; ++ cycles_since_card_update_; cycles_since_audio_update_ += Cycles(7);