diff --git a/ClockReceiver/Sleeper.hpp b/ClockReceiver/Sleeper.hpp index 2a6e00e08..08dadfb92 100644 --- a/ClockReceiver/Sleeper.hpp +++ b/ClockReceiver/Sleeper.hpp @@ -30,7 +30,7 @@ class Sleeper { 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(void *component, bool is_sleeping) = 0; + virtual void set_component_is_sleeping(Sleeper *component, bool is_sleeping) = 0; }; /// Registers @c observer as the new sleep observer; diff --git a/Components/DiskII/DiskII.cpp b/Components/DiskII/DiskII.cpp index 57ff5a4be..b90a117f8 100644 --- a/Components/DiskII/DiskII.cpp +++ b/Components/DiskII/DiskII.cpp @@ -13,14 +13,17 @@ using namespace Apple; namespace { - const uint8_t input_command = 0x1; - const uint8_t input_mode = 0x2; + const uint8_t input_command = 0x1; // i.e. Q6 + const uint8_t input_mode = 0x2; // i.e. Q7 const uint8_t input_flux = 0x4; } DiskII::DiskII() : - drives_{{2000000, 300, 1}, {2045454, 300, 1}} + inputs_(input_command), + drives_{{2045454, 300, 1}, {2045454, 300, 1}} { + drives_[0].set_sleep_observer(this); + drives_[1].set_sleep_observer(this); } void DiskII::set_control(Control control, bool on) { @@ -38,7 +41,7 @@ void DiskII::set_control(Control control, bool on) { break; } -// printf("%0x: Set control %d %s\n", stepper_mask_, control, on ? "on" : "off"); + printf("%0x: Set control %d %s\n", stepper_mask_, control, on ? "on" : "off"); // If the stepper magnet selections have changed, and any is on, see how // that moves the head. @@ -62,6 +65,7 @@ void DiskII::set_control(Control control, bool on) { void DiskII::set_mode(Mode mode) { // printf("Set mode %d\n", mode); inputs_ = (inputs_ & ~input_mode) | ((mode == Mode::Write) ? input_mode : 0); + set_controller_can_sleep(); } void DiskII::select_drive(int drive) { @@ -75,11 +79,13 @@ void DiskII::set_data_register(uint8_t value) { // printf("Set data register (?)\n"); inputs_ |= input_command; data_register_ = value; + set_controller_can_sleep(); } uint8_t DiskII::get_shift_register() { // if(shift_register_ & 0x80) printf("[%02x] ", shift_register_); inputs_ &= ~input_command; + set_controller_can_sleep(); return shift_register_; } @@ -97,41 +103,56 @@ void DiskII::run_for(const Cycles cycles) { The bytes in the P6 ROM has the high four bits reversed compared to the BAPD charts, so you will have to reverse them after fetching the byte. */ - // TODO: optimise the resting state. + if(is_sleeping()) return; int integer_cycles = cycles.as_int(); - while(integer_cycles--) { - const int address = - (inputs_ << 2) | - ((shift_register_&0x80) >> 6) | - ((state_&0x2) >> 1) | - ((state_&0x1) << 7) | - ((state_&0x4) << 4) | - ((state_&0x8) << 2); - inputs_ |= input_flux; - const uint8_t update = state_machine_[static_cast(address)]; - state_ = update >> 4; - state_ = ((state_ & 0x8) ? 0x1 : 0x0) | ((state_ & 0x4) ? 0x2 : 0x0) | ((state_ & 0x2) ? 0x4 : 0x0) | ((state_ & 0x1) ? 0x8 : 0x0); + if(!controller_can_sleep_) { + while(integer_cycles--) { + const int address = + (inputs_ << 2) | + ((shift_register_&0x80) >> 6) | + ((state_&0x2) >> 1) | + ((state_&0x1) << 7) | + ((state_&0x4) << 4) | + ((state_&0x8) << 2); + inputs_ |= input_flux; - uint8_t command = update & 0xf; - switch(command) { - case 0x0: shift_register_ = 0; break; // clear - 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 0xb: shift_register_ = data_register_; break; // load - case 0xa: - shift_register_ = (shift_register_ >> 1) | (is_write_protected() ? 0x80 : 0x00); - break; // shift right, bringing in write protected status - default: break; + const uint8_t update = state_machine_[static_cast(address)]; + state_ = update >> 4; + state_ = ((state_ & 0x8) ? 0x1 : 0x0) | ((state_ & 0x4) ? 0x2 : 0x0) | ((state_ & 0x2) ? 0x4 : 0x0) | ((state_ & 0x1) ? 0x8 : 0x0); + + uint8_t command = update & 0xf; + switch(command) { + case 0x0: shift_register_ = 0; break; // clear + 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 0xb: shift_register_ = data_register_; break; // load + case 0xa: + shift_register_ = (shift_register_ >> 1) | (is_write_protected() ? 0x80 : 0x00); + break; // shift right, bringing in write protected status + default: break; + } + + // 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)); } - -// printf(" -> %02x performing %02x (address was %02x)\n", state_, command, address); - - // TODO: surely there's a less heavyweight solution than this? - drives_[0].run_for(Cycles(1)); - drives_[1].run_for(Cycles(1)); + } else { + if(!drive_is_sleeping_[0]) drives_[0].run_for(cycles); + if(!drive_is_sleeping_[1]) drives_[1].run_for(cycles); } + + set_controller_can_sleep(); +} + +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. + controller_can_sleep_ = + (inputs_ == (input_command | input_flux)) && + (shift_register_ == (is_write_protected() ? 0xff : 0x00)); + if(is_sleeping()) update_sleep_observer(); } bool DiskII::is_write_protected() { @@ -140,7 +161,6 @@ bool DiskII::is_write_protected() { void DiskII::set_state_machine(const std::vector &state_machine) { state_machine_ = state_machine; -// run_for(Cycles(15)); // TODO: shuffle ordering here? } @@ -151,5 +171,16 @@ 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(); } } + +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(); +} + +bool DiskII::is_sleeping() { + return controller_can_sleep_ && drive_is_sleeping_[0] && drive_is_sleeping_[1]; +} diff --git a/Components/DiskII/DiskII.hpp b/Components/DiskII/DiskII.hpp index ccb0130b1..26f9d6adb 100644 --- a/Components/DiskII/DiskII.hpp +++ b/Components/DiskII/DiskII.hpp @@ -10,6 +10,7 @@ #define DiskII_hpp #include "../../ClockReceiver/ClockReceiver.hpp" +#include "../../ClockReceiver/Sleeper.hpp" #include "../../Storage/Disk/Disk.hpp" #include "../../Storage/Disk/Drive.hpp" @@ -22,7 +23,10 @@ namespace Apple { /*! Provides an emulation of the Apple Disk II. */ -class DiskII: public Storage::Disk::Drive::EventDelegate { +class DiskII: + public Storage::Disk::Drive::EventDelegate, + public Sleeper::SleepObserver, + public Sleeper { public: DiskII(); @@ -43,9 +47,11 @@ class DiskII: public Storage::Disk::Drive::EventDelegate { void set_state_machine(const std::vector &); void set_disk(const std::shared_ptr &disk, int drive); + bool is_sleeping() override; private: void process_event(const Storage::Disk::Track::Event &event) override; + void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override; uint8_t state_ = 0; uint8_t inputs_ = 0; @@ -58,7 +64,11 @@ class DiskII: public Storage::Disk::Drive::EventDelegate { bool is_write_protected(); std::vector state_machine_; Storage::Disk::Drive drives_[2]; + bool drive_is_sleeping_[2]; + bool controller_can_sleep_ = false; int active_drive_ = 0; + + void set_controller_can_sleep(); }; } diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 58ab1bce2..db1720cb5 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -961,7 +961,7 @@ class ConcreteMachine: return true; } - void set_component_is_sleeping(void *component, bool is_sleeping) override final { + 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(); } diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 39deb6516..56111292d 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -747,7 +747,7 @@ class ConcreteMachine: return selection_set; } - void set_component_is_sleeping(void *component, bool is_sleeping) override { + void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override { tape_is_sleeping_ = is_sleeping; set_use_fast_tape(); } diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index e1c693094..6b57f714c 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -551,7 +551,7 @@ class ConcreteMachine: } // MARK: - Sleeper - void set_component_is_sleeping(void *component, bool is_sleeping) override { + void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override { tape_player_is_sleeping_ = tape_player_.is_sleeping(); set_use_fast_tape(); } diff --git a/Storage/Disk/Controller/DiskController.cpp b/Storage/Disk/Controller/DiskController.cpp index e6788c873..9a0b992f1 100644 --- a/Storage/Disk/Controller/DiskController.cpp +++ b/Storage/Disk/Controller/DiskController.cpp @@ -22,7 +22,7 @@ Controller::Controller(Cycles clock_rate) : set_drive(empty_drive_); } -void Controller::set_component_is_sleeping(void *component, bool is_sleeping) { +void Controller::set_component_is_sleeping(Sleeper *component, bool is_sleeping) { update_sleep_observer(); } diff --git a/Storage/Disk/Controller/DiskController.hpp b/Storage/Disk/Controller/DiskController.hpp index 7eb0ce085..ac079e6c3 100644 --- a/Storage/Disk/Controller/DiskController.hpp +++ b/Storage/Disk/Controller/DiskController.hpp @@ -113,7 +113,7 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDe std::shared_ptr empty_drive_; - void set_component_is_sleeping(void *component, bool is_sleeping); + void set_component_is_sleeping(Sleeper *component, bool is_sleeping); // for Drive::EventDelegate void process_event(const Track::Event &event);