From 90c7056d1295ab40356493ae4e8944975de69aec Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Sep 2017 14:43:20 -0400 Subject: [PATCH 01/22] Started devolving timed event loop logic down to the drives, moving them closer to modelling real life. --- Components/8272/i8272.hpp | 2 +- .../Commodore/1540/Implementation/C1540.cpp | 2 +- Machines/Electron/Plus3.cpp | 2 +- Machines/Oric/Microdisc.cpp | 2 +- StaticAnalyser/Commodore/Disk.cpp | 2 +- Storage/Disk/DiskController.hpp | 1 - Storage/Disk/Drive.cpp | 36 ++++++++++++++-- Storage/Disk/Drive.hpp | 43 ++++++++++++++++--- Storage/Disk/Encodings/MFM.cpp | 2 +- 9 files changed, 76 insertions(+), 16 deletions(-) diff --git a/Components/8272/i8272.hpp b/Components/8272/i8272.hpp index 6c31a9517..c8fced878 100644 --- a/Components/8272/i8272.hpp +++ b/Components/8272/i8272.hpp @@ -103,7 +103,7 @@ class i8272: public Storage::Disk::MFMController { Drive() : head_position(0), phase(NotSeeking), - drive(new Storage::Disk::Drive), + drive(new Storage::Disk::Drive(8000000, 300)), // TODO: these constants can't live here. head_is_loaded{false, false}, head_unload_delay{0, 0} {}; } drives_[4]; diff --git a/Machines/Commodore/1540/Implementation/C1540.cpp b/Machines/Commodore/1540/Implementation/C1540.cpp index a6008c51f..6f3ded472 100644 --- a/Machines/Commodore/1540/Implementation/C1540.cpp +++ b/Machines/Commodore/1540/Implementation/C1540.cpp @@ -82,7 +82,7 @@ void Machine::set_rom(const std::vector &rom) { } void Machine::set_disk(std::shared_ptr disk) { - std::shared_ptr drive(new Storage::Disk::Drive); + std::shared_ptr drive(new Storage::Disk::Drive(1000000, 300)); drive->set_disk(disk); set_drive(drive); } diff --git a/Machines/Electron/Plus3.cpp b/Machines/Electron/Plus3.cpp index 99a580e2b..e0e12bb61 100644 --- a/Machines/Electron/Plus3.cpp +++ b/Machines/Electron/Plus3.cpp @@ -16,7 +16,7 @@ Plus3::Plus3() : WD1770(P1770) { void Plus3::set_disk(std::shared_ptr disk, int drive) { if(!drives_[drive]) { - drives_[drive].reset(new Storage::Disk::Drive); + drives_[drive].reset(new Storage::Disk::Drive(8000000, 300)); if(drive == selected_drive_) set_drive(drives_[drive]); } drives_[drive]->set_disk(disk); diff --git a/Machines/Oric/Microdisc.cpp b/Machines/Oric/Microdisc.cpp index dd7cb8610..90c82cc48 100644 --- a/Machines/Oric/Microdisc.cpp +++ b/Machines/Oric/Microdisc.cpp @@ -30,7 +30,7 @@ Microdisc::Microdisc() : void Microdisc::set_disk(std::shared_ptr disk, int drive) { if(!drives_[drive]) { - drives_[drive].reset(new Storage::Disk::Drive); + drives_[drive].reset(new Storage::Disk::Drive(8000000, 300)); if(drive == selected_drive_) set_drive(drives_[drive]); } drives_[drive]->set_disk(disk); diff --git a/StaticAnalyser/Commodore/Disk.cpp b/StaticAnalyser/Commodore/Disk.cpp index 37c89c015..63221d758 100644 --- a/StaticAnalyser/Commodore/Disk.cpp +++ b/StaticAnalyser/Commodore/Disk.cpp @@ -22,7 +22,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller { std::shared_ptr drive; CommodoreGCRParser() : Storage::Disk::Controller(4000000, 1, 300), shift_register_(0), track_(1) { - drive.reset(new Storage::Disk::Drive); + drive.reset(new Storage::Disk::Drive(4000000, 300)); set_drive(drive); set_motor_on(true); } diff --git a/Storage/Disk/DiskController.hpp b/Storage/Disk/DiskController.hpp index 4ab4e009c..4d35eaecc 100644 --- a/Storage/Disk/DiskController.hpp +++ b/Storage/Disk/DiskController.hpp @@ -47,7 +47,6 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop Advances the drive by @c number_of_cycles cycles. */ void run_for(const Cycles cycles); - using TimedEventLoop::run_for; /*! Sets the current drive. diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index 817bdd994..f920a149a 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -11,8 +11,10 @@ using namespace Storage::Disk; -Drive::Drive() - : head_position_(0), head_(0), has_disk_(false) {} +Drive::Drive(unsigned int input_clock_rate, int revolutions_per_minute): + Storage::TimedEventLoop(input_clock_rate), + rotational_multiplier_(60, revolutions_per_minute) { +} void Drive::set_disk(const std::shared_ptr &disk) { disk_ = disk; @@ -33,7 +35,7 @@ bool Drive::has_disk() { } bool Drive::is_sleeping() { - return !has_disk_; + return !motor_is_on_ || !has_disk_; } bool Drive::get_is_track_zero() { @@ -41,8 +43,22 @@ bool Drive::get_is_track_zero() { } void Drive::step(int direction) { + int old_head_position = head_position_; head_position_ = std::max(head_position_ + direction, 0); - printf("Head -> %d\n", head_position_); + + // If the head moved and this drive has a real disk in it, flush the old track. + if(head_position_ != old_head_position && disk_ != nullptr) { + track_ = nullptr; + } +} + +Storage::Time Drive::get_time_into_track() { + // `result` will initially be amount of time since the index hole was seen as a + // proportion of a second; convert it into proportion of a rotation, simplify and return. + Time result(cycles_since_index_hole_, 8000000); + result /= rotational_multiplier_; + result.simplify(); + return result; } void Drive::set_head(unsigned int head) { @@ -68,3 +84,15 @@ std::shared_ptr Drive::get_track() { void Drive::set_track(const std::shared_ptr &track) { if(disk_) disk_->set_track_at_position(head_, (unsigned int)head_position_, track); } + +void Drive::set_motor_on(bool motor_is_on) { + motor_is_on_ = motor_is_on; + update_sleep_observer(); +} + +void Drive::process_next_event() { + if(event_delegate_) event_delegate_->process_event(current_event_); +} + +void Drive::run_for(const Cycles cycles) { +} diff --git a/Storage/Disk/Drive.hpp b/Storage/Disk/Drive.hpp index 2ed27ea71..0b45a43e8 100644 --- a/Storage/Disk/Drive.hpp +++ b/Storage/Disk/Drive.hpp @@ -13,13 +13,14 @@ #include "Disk.hpp" #include "../../ClockReceiver/Sleeper.hpp" +#include "../TimedEventLoop.hpp" namespace Storage { namespace Disk { -class Drive: public Sleeper { +class Drive: public Sleeper, public TimedEventLoop { public: - Drive(); + Drive(unsigned int input_clock_rate, int revolutions_per_minute); /*! Replaces whatever is in the drive with @c disk. @@ -72,15 +73,47 @@ class Drive: public Sleeper { */ bool get_is_ready(); + /*! + Sets whether the disk motor is on. + */ + void set_motor_on(bool); + + /*! + Advances the drive by @c number_of_cycles cycles. + */ + void run_for(const Cycles cycles); + + /*! + Provides a mechanism to receive track events as they occur. + */ + struct EventDelegate { + virtual void process_event(const Track::Event &event) = 0; + }; + + /// Sets the current event delegate. + void set_event_delegate(EventDelegate *); + // As per Sleeper. bool is_sleeping(); private: std::shared_ptr track_; std::shared_ptr disk_; - bool has_disk_; - int head_position_; - unsigned int head_; + int cycles_since_index_hole_ = 0; + Time rotational_multiplier_; + + bool has_disk_ = false; + + int head_position_ = 0; + unsigned int head_ = 0; + + void process_next_event(); + void get_next_event(const Time &duration_already_passed); + Track::Event current_event_; + bool motor_is_on_ = false; + + Time get_time_into_track(); + EventDelegate *event_delegate_ = nullptr; }; diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index 6b69af9c1..063bdfb44 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -243,7 +243,7 @@ Parser::Parser(bool is_mfm) : bit_length.clock_rate = is_mfm ? 500000 : 250000; // i.e. 250 kbps (including clocks) set_expected_bit_length(bit_length); - drive_.reset(new Storage::Disk::Drive); + drive_.reset(new Storage::Disk::Drive(4000000, 300)); set_drive(drive_); set_motor_on(true); } From 90d2347c90bce8d294e620edc3647883fe58eadb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Sep 2017 14:44:38 -0400 Subject: [PATCH 02/22] Extended to permit subclasses that are interested to get sub-run_for information about event times. --- Storage/TimedEventLoop.cpp | 15 ++++++++++++--- Storage/TimedEventLoop.hpp | 9 +++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Storage/TimedEventLoop.cpp b/Storage/TimedEventLoop.cpp index 377af80fd..523f006a0 100644 --- a/Storage/TimedEventLoop.cpp +++ b/Storage/TimedEventLoop.cpp @@ -16,10 +16,19 @@ TimedEventLoop::TimedEventLoop(unsigned int input_clock_rate) : input_clock_rate_(input_clock_rate) {} void TimedEventLoop::run_for(const Cycles cycles) { - cycles_until_event_ -= cycles.as_int(); - while(cycles_until_event_ <= 0) { + int remaining_cycles = cycles.as_int(); + + while(cycles_until_event_ <= remaining_cycles) { + advance(cycles_until_event_); + remaining_cycles -= cycles_until_event_; + cycles_until_event_ = 0; process_next_event(); } + + if(remaining_cycles) { + cycles_until_event_ -= remaining_cycles; + advance(remaining_cycles); + } } unsigned int TimedEventLoop::get_cycles_until_next_event() { @@ -52,7 +61,7 @@ void TimedEventLoop::set_next_event_time_interval(Time interval) { // So this event will fire in the integral number of cycles from now, putting us at the remainder // number of subcycles - cycles_until_event_ = (int)(numerator / denominator); + cycles_until_event_ += (int)(numerator / denominator); subcycles_until_event_.length = (unsigned int)(numerator % denominator); subcycles_until_event_.clock_rate = (unsigned int)denominator; } diff --git a/Storage/TimedEventLoop.hpp b/Storage/TimedEventLoop.hpp index 660767beb..bfe97eaad 100644 --- a/Storage/TimedEventLoop.hpp +++ b/Storage/TimedEventLoop.hpp @@ -66,6 +66,15 @@ namespace Storage { */ virtual void process_next_event() = 0; + /*! + Optionally allows a subclass to track time within run_for periods; if a subclass implements + advnace then it will receive advance increments that add up to the number of cycles supplied + to run_for, but calls to process_next_event will be precisely interspersed. No time will carry + forward between calls into run_for; a subclass can receive arbitrarily many instructions to + advance before receiving a process_next_event. + */ + virtual void advance(const Cycles cycles) {}; + /*! Resets timing, throwing away any current internal state. So clears any fractional ticks that the event loop is currently tracking. From ff6e65cca9aa3a697fd07c7764e0571a6ed1bac3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Sep 2017 16:23:31 -0400 Subject: [PATCH 03/22] Introduces necessary storage and interface for writing. --- Storage/Disk/Drive.hpp | 86 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 77 insertions(+), 9 deletions(-) diff --git a/Storage/Disk/Drive.hpp b/Storage/Disk/Drive.hpp index 0b45a43e8..234aa87f6 100644 --- a/Storage/Disk/Drive.hpp +++ b/Storage/Disk/Drive.hpp @@ -9,11 +9,14 @@ #ifndef Drive_hpp #define Drive_hpp -#include - #include "Disk.hpp" -#include "../../ClockReceiver/Sleeper.hpp" +#include "PCMSegment.hpp" +#include "PCMPatchedTrack.hpp" + #include "../TimedEventLoop.hpp" +#include "../../ClockReceiver/Sleeper.hpp" + +#include namespace Storage { namespace Disk { @@ -78,16 +81,50 @@ class Drive: public Sleeper, public TimedEventLoop { */ void set_motor_on(bool); + /*! + @returns @c true if the motor is on; @c false otherwise. + */ + bool get_motor_on(); + + /*! + Begins write mode, initiating a PCM sampled region of data. Bits should be written via + @c write_bit. They will be written with the length set via @c set_expected_bit_length. + It is acceptable to supply a backlog of bits. Flux transition events will not be reported + while writing. + + @param clamp_to_index_hole If @c true then writing will automatically be truncated by + the index hole. Writing will continue over the index hole otherwise. + */ + void begin_writing(bool clamp_to_index_hole); + + /*! + Writes the bit @c value as the next in the PCM stream initiated by @c begin_writing. + */ + void write_bit(bool value); + + /*! + Ends write mode, switching back to read mode. The drive will stop overwriting events. + */ + void end_writing(); + /*! Advances the drive by @c number_of_cycles cycles. */ void run_for(const Cycles cycles); /*! - Provides a mechanism to receive track events as they occur. + Provides a mechanism to receive track events as they occur, including the synthetic + event of "you told me to output the following data, and I've done that now". */ struct EventDelegate { + /// Informs the delegate that @c event has been reached. virtual void process_event(const Track::Event &event) = 0; + + /*! + If the drive is in write mode, announces that all queued bits have now been written. + If the controller provides further bits now then there will be no gap in written data. + */ + virtual void process_write_completed() {} }; /// Sets the current event delegate. @@ -97,22 +134,53 @@ class Drive: public Sleeper, public TimedEventLoop { bool is_sleeping(); private: - std::shared_ptr track_; + // Drives [usually] contain an entire disk; from that a certain track + // will be currently under the head. std::shared_ptr disk_; - int cycles_since_index_hole_ = 0; - Time rotational_multiplier_; - + std::shared_ptr track_; bool has_disk_ = false; + // Contains the multiplier that converts between track-relative lengths + // to real-time lengths — so it's the reciprocal of rotation speed. + Time rotational_multiplier_; + + // A count of time since the index hole was last seen. Which is used to + // determine how far the drive is into a full rotation when switching to + // a new track. + int cycles_since_index_hole_ = 0; + + // A record of head position and active head. int head_position_ = 0; unsigned int head_ = 0; + // Motor control state. + bool motor_is_on_ = false; + + // If the drive is not currently reading then it is writing. While writing + // it can optionally be told to clamp to the index hole. + bool is_reading_; + bool clamp_writing_to_index_hole_; + + // If writing is occurring then the drive will be accumulating a write segment, + // for addition to a patched track. + std::shared_ptr patched_track_; + PCMSegment write_segment_; + Time write_start_time_; + + // Maintains appropriate counting to know when to indicate that writing + // is complete. + Time cycles_until_bits_written_; + Time cycles_per_bit_; + + // TimedEventLoop call-ins and state. void process_next_event(); void get_next_event(const Time &duration_already_passed); Track::Event current_event_; - bool motor_is_on_ = false; + // Helper for track changes. Time get_time_into_track(); + + // The target (if any) for track events. EventDelegate *event_delegate_ = nullptr; }; From 60750644001f7d2299e6baeb313ccddd927b3b2f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Sep 2017 17:31:43 -0400 Subject: [PATCH 04/22] Adds the ability to query a TimedEventLoop for its input clock rate. --- Storage/TimedEventLoop.cpp | 4 ++++ Storage/TimedEventLoop.hpp | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/Storage/TimedEventLoop.cpp b/Storage/TimedEventLoop.cpp index 523f006a0..6b1b1a597 100644 --- a/Storage/TimedEventLoop.cpp +++ b/Storage/TimedEventLoop.cpp @@ -35,6 +35,10 @@ unsigned int TimedEventLoop::get_cycles_until_next_event() { return (unsigned int)std::max(cycles_until_event_, 0); } +unsigned int TimedEventLoop::get_input_clock_rate() { + return input_clock_rate_; +} + void TimedEventLoop::reset_timer() { subcycles_until_event_.set_zero(); cycles_until_event_ = 0; diff --git a/Storage/TimedEventLoop.hpp b/Storage/TimedEventLoop.hpp index bfe97eaad..e383e9683 100644 --- a/Storage/TimedEventLoop.hpp +++ b/Storage/TimedEventLoop.hpp @@ -54,6 +54,11 @@ namespace Storage { */ unsigned int get_cycles_until_next_event(); + /*! + @returns the input clock rate. + */ + unsigned int get_input_clock_rate(); + protected: /*! Sets the time interval, as a proportion of a second, until the next event should be triggered. From a4e275e1fc257da6ba1ef6f83a88370202959f27 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Sep 2017 17:33:01 -0400 Subject: [PATCH 05/22] Provides an implementation of Drive's new interface. Mostly lifted from DiskController. `set_disk_with_track` has been withdrawn in favour of providing a suitable wrapper `Disk` subclass, as being an unnecessary complexity and intermingling of concerns. --- Storage/Disk/Drive.cpp | 178 ++++++++++++++++++++++++++++++++++------- Storage/Disk/Drive.hpp | 42 +++++----- 2 files changed, 172 insertions(+), 48 deletions(-) diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index f920a149a..47f5d1750 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -7,7 +7,11 @@ // #include "Drive.hpp" + +#include "UnformattedTrack.hpp" + #include +#include using namespace Storage::Disk; @@ -18,16 +22,13 @@ Drive::Drive(unsigned int input_clock_rate, int revolutions_per_minute): void Drive::set_disk(const std::shared_ptr &disk) { disk_ = disk; - track_ = nullptr; has_disk_ = !!disk_; - update_sleep_observer(); -} -void Drive::set_disk_with_track(const std::shared_ptr &track) { - disk_ = nullptr; - track_ = track; - has_disk_ = !!track_; + invalidate_track(); update_sleep_observer(); + + // TODO: implement ready properly. + is_ready_ = true; } bool Drive::has_disk() { @@ -46,8 +47,15 @@ void Drive::step(int direction) { int old_head_position = head_position_; head_position_ = std::max(head_position_ + direction, 0); - // If the head moved and this drive has a real disk in it, flush the old track. - if(head_position_ != old_head_position && disk_ != nullptr) { + // If the head moved, flush the old track. + if(head_position_ != old_head_position) { + track_ = nullptr; + } +} + +void Drive::set_head(unsigned int head) { + if(head != head_) { + head_ = head; track_ = nullptr; } } @@ -55,34 +63,19 @@ void Drive::step(int direction) { Storage::Time Drive::get_time_into_track() { // `result` will initially be amount of time since the index hole was seen as a // proportion of a second; convert it into proportion of a rotation, simplify and return. - Time result(cycles_since_index_hole_, 8000000); + Time result(cycles_since_index_hole_, (int)get_input_clock_rate()); result /= rotational_multiplier_; result.simplify(); return result; } -void Drive::set_head(unsigned int head) { - head_ = head; -} - bool Drive::get_is_read_only() { if(disk_) return disk_->get_is_read_only(); return true; } bool Drive::get_is_ready() { - // TODO: a real test for this. - return disk_ != nullptr; -} - -std::shared_ptr Drive::get_track() { - if(disk_) return disk_->get_track_at_position(head_, (unsigned int)head_position_); - if(track_) return track_; - return nullptr; -} - -void Drive::set_track(const std::shared_ptr &track) { - if(disk_) disk_->set_track_at_position(head_, (unsigned int)head_position_, track); + return is_ready_; } void Drive::set_motor_on(bool motor_is_on) { @@ -90,9 +83,136 @@ void Drive::set_motor_on(bool motor_is_on) { update_sleep_observer(); } -void Drive::process_next_event() { - if(event_delegate_) event_delegate_->process_event(current_event_); +void Drive::run_for(const Cycles cycles) { + Time zero(0); + + if(has_disk_ && motor_is_on_) { + // Grab a new track if not already in possession of one. + if(!track_) setup_track(); + + int number_of_cycles = cycles.as_int(); + while(number_of_cycles) { + int cycles_until_next_event = (int)get_cycles_until_next_event(); + int cycles_to_run_for = std::min(cycles_until_next_event, number_of_cycles); + if(!is_reading_ && cycles_until_bits_written_ > zero) { + int write_cycles_target = (int)cycles_until_bits_written_.get_unsigned_int(); + if(cycles_until_bits_written_.length % cycles_until_bits_written_.clock_rate) write_cycles_target++; + cycles_to_run_for = std::min(cycles_to_run_for, write_cycles_target); + } + + cycles_since_index_hole_ += (unsigned int)cycles_to_run_for; + + number_of_cycles -= cycles_to_run_for; + if(is_reading_) { + if(event_delegate_) event_delegate_->advance(Cycles(cycles_to_run_for)); + } else { + if(cycles_until_bits_written_ > zero) { + Storage::Time cycles_to_run_for_time(cycles_to_run_for); + if(cycles_until_bits_written_ <= cycles_to_run_for_time) { + if(event_delegate_) event_delegate_->process_write_completed(); + if(cycles_until_bits_written_ <= cycles_to_run_for_time) + cycles_until_bits_written_.set_zero(); + else + cycles_until_bits_written_ -= cycles_to_run_for_time; + } else { + cycles_until_bits_written_ -= cycles_to_run_for_time; + } + } + } + TimedEventLoop::run_for(Cycles(cycles_to_run_for)); + } + } } -void Drive::run_for(const Cycles cycles) { +#pragma mark - Track timed event loop + +void Drive::get_next_event(const Time &duration_already_passed) { + if(track_) { + current_event_ = track_->get_next_event(); + } else { + current_event_.length.length = 1; + current_event_.length.clock_rate = 1; + current_event_.type = Track::Event::IndexHole; + } + + // divide interval, which is in terms of a single rotation of the disk, by rotation speed to + // convert it into revolutions per second; this is achieved by multiplying by rotational_multiplier_ + assert(current_event_.length <= Time(1) && current_event_.length >= Time(0)); + Time interval = (current_event_.length - duration_already_passed) * rotational_multiplier_; + set_next_event_time_interval(interval); +} + +void Drive::process_next_event() { + // TODO: ready test here. + if(event_delegate_) event_delegate_->process_event(current_event_); + get_next_event(Time(0)); +} + +#pragma mark - Track management + +std::shared_ptr Drive::get_track() { + if(disk_) return disk_->get_track_at_position(head_, (unsigned int)head_position_); + return nullptr; +} + +void Drive::set_track(const std::shared_ptr &track) { + if(disk_) disk_->set_track_at_position(head_, (unsigned int)head_position_, track); +} + +void Drive::setup_track() { + track_ = get_track(); + if(!track_) { + track_.reset(new UnformattedTrack); + } + + Time offset; + Time track_time_now = get_time_into_track(); + assert(track_time_now >= Time(0) && current_event_.length <= Time(1)); + + Time time_found = track_->seek_to(track_time_now); + assert(time_found >= Time(0) && time_found <= track_time_now); + offset = track_time_now - time_found; + + get_next_event(offset); +} + +void Drive::invalidate_track() { + track_ = nullptr; +} + +#pragma mark - Writing + +void Drive::begin_writing(Time bit_length, bool clamp_to_index_hole) { + is_reading_ = false; + clamp_writing_to_index_hole_ = clamp_to_index_hole; + + write_segment_.length_of_a_bit = bit_length / rotational_multiplier_; + write_segment_.data.clear(); + write_segment_.number_of_bits = 0; + + write_start_time_ = get_time_into_track(); +} + +void Drive::write_bit(bool value) { + bool needs_new_byte = !(write_segment_.number_of_bits&7); + if(needs_new_byte) write_segment_.data.push_back(0); + if(value) write_segment_.data[write_segment_.number_of_bits >> 3] |= 0x80 >> (write_segment_.number_of_bits & 7); + write_segment_.number_of_bits++; + + cycles_until_bits_written_ += cycles_per_bit_; +} + +void Drive::end_writing() { + is_reading_ = true; + + if(!patched_track_) { + // Avoid creating a new patched track if this one is already patched + patched_track_ = std::dynamic_pointer_cast(track_); + if(!patched_track_) { + patched_track_.reset(new PCMPatchedTrack(track_)); + } + } + patched_track_->add_segment(write_start_time_, write_segment_, clamp_writing_to_index_hole_); + cycles_since_index_hole_ %= get_input_clock_rate(); + invalidate_track(); } diff --git a/Storage/Disk/Drive.hpp b/Storage/Disk/Drive.hpp index 234aa87f6..97f12ed49 100644 --- a/Storage/Disk/Drive.hpp +++ b/Storage/Disk/Drive.hpp @@ -30,11 +30,6 @@ class Drive: public Sleeper, public TimedEventLoop { */ void set_disk(const std::shared_ptr &disk); - /*! - Replaces whatever is in the drive with a disk that contains endless copies of @c track. - */ - void set_disk_with_track(const std::shared_ptr &track); - /*! @returns @c true if a disk is currently inserted; @c false otherwise. */ @@ -61,16 +56,6 @@ class Drive: public Sleeper, public TimedEventLoop { */ bool get_is_read_only(); - /*! - @returns the track underneath the current head at the location now stepped to. - */ - std::shared_ptr get_track(); - - /*! - Attempts to set @c track as the track underneath the current head at the location now stepped to. - */ - void set_track(const std::shared_ptr &track); - /*! @returns @c true if the drive is ready; @c false otherwise. */ @@ -95,7 +80,7 @@ class Drive: public Sleeper, public TimedEventLoop { @param clamp_to_index_hole If @c true then writing will automatically be truncated by the index hole. Writing will continue over the index hole otherwise. */ - void begin_writing(bool clamp_to_index_hole); + void begin_writing(Time bit_length, bool clamp_to_index_hole); /*! Writes the bit @c value as the next in the PCM stream initiated by @c begin_writing. @@ -125,6 +110,9 @@ class Drive: public Sleeper, public TimedEventLoop { If the controller provides further bits now then there will be no gap in written data. */ virtual void process_write_completed() {} + + /// Informs the delegate of the passing of @c cycles. + virtual void advance(const Cycles cycles) {} }; /// Sets the current event delegate. @@ -134,7 +122,7 @@ class Drive: public Sleeper, public TimedEventLoop { bool is_sleeping(); private: - // Drives [usually] contain an entire disk; from that a certain track + // Drives contain an entire disk; from that a certain track // will be currently under the head. std::shared_ptr disk_; std::shared_ptr track_; @@ -158,8 +146,8 @@ class Drive: public Sleeper, public TimedEventLoop { // If the drive is not currently reading then it is writing. While writing // it can optionally be told to clamp to the index hole. - bool is_reading_; - bool clamp_writing_to_index_hole_; + bool is_reading_ = false; + bool clamp_writing_to_index_hole_ = false; // If writing is occurring then the drive will be accumulating a write segment, // for addition to a patched track. @@ -167,6 +155,9 @@ class Drive: public Sleeper, public TimedEventLoop { PCMSegment write_segment_; Time write_start_time_; + // Indicates drive ready state. + bool is_ready_ = false; + // Maintains appropriate counting to know when to indicate that writing // is complete. Time cycles_until_bits_written_; @@ -182,6 +173,19 @@ class Drive: public Sleeper, public TimedEventLoop { // The target (if any) for track events. EventDelegate *event_delegate_ = nullptr; + + /*! + @returns the track underneath the current head at the location now stepped to. + */ + std::shared_ptr get_track(); + + /*! + Attempts to set @c track as the track underneath the current head at the location now stepped to. + */ + void set_track(const std::shared_ptr &track); + + void setup_track(); + void invalidate_track(); }; From 1a96cce26f11d048046bbd76c505d1e40ed54caa Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Sep 2017 17:34:14 -0400 Subject: [PATCH 06/22] Implements SingleTrackDisk, a Disk that contains only a single, specified, track. --- .../Clock Signal.xcodeproj/project.pbxproj | 6 ++++ Storage/Disk/SingleTrackDisk.cpp | 22 ++++++++++++ Storage/Disk/SingleTrackDisk.hpp | 35 +++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 Storage/Disk/SingleTrackDisk.cpp create mode 100644 Storage/Disk/SingleTrackDisk.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index f34b69870..19fd4ff96 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -434,6 +434,7 @@ 4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; }; 4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD69F921D98760000243FE1 /* AcornADF.cpp */; }; 4BDDBA991EF3451200347E61 /* Z80MachineCycleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */; }; + 4BE4144C1F65E439006A8D7C /* SingleTrackDisk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE4144A1F65E439006A8D7C /* SingleTrackDisk.cpp */; }; 4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE77A2C1D84ADFB00BC3827 /* File.cpp */; }; 4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE7C9161E3D397100A5496D /* TIA.cpp */; }; 4BE9A6B11EDE293000CBCB47 /* zexdoc.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BE9A6B01EDE293000CBCB47 /* zexdoc.com */; }; @@ -1042,6 +1043,8 @@ 4BD69F931D98760000243FE1 /* AcornADF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AcornADF.hpp; sourceTree = ""; }; 4BD9137D1F311BC5009BCF85 /* i8255.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = i8255.hpp; path = 8255/i8255.hpp; sourceTree = ""; }; 4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MachineCycleTests.swift; sourceTree = ""; }; + 4BE4144A1F65E439006A8D7C /* SingleTrackDisk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SingleTrackDisk.cpp; sourceTree = ""; }; + 4BE4144B1F65E439006A8D7C /* SingleTrackDisk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SingleTrackDisk.hpp; sourceTree = ""; }; 4BE77A2C1D84ADFB00BC3827 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Commodore/File.cpp; sourceTree = ""; }; 4BE77A2D1D84ADFB00BC3827 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Commodore/File.hpp; sourceTree = ""; }; 4BE7C9161E3D397100A5496D /* TIA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIA.cpp; sourceTree = ""; }; @@ -1641,6 +1644,7 @@ 4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */, 4B121F961E060CF000BFDA12 /* PCMSegment.cpp */, 4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */, + 4BE4144A1F65E439006A8D7C /* SingleTrackDisk.cpp */, 4BAD9B941F43D7E900724854 /* UnformattedTrack.cpp */, 4B0BE4271D3481E700D5256B /* DigitalPhaseLockedLoop.hpp */, 4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */, @@ -1650,6 +1654,7 @@ 4B3F1B451E0388D200DB26EE /* PCMPatchedTrack.hpp */, 4B121F971E060CF000BFDA12 /* PCMSegment.hpp */, 4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */, + 4BE4144B1F65E439006A8D7C /* SingleTrackDisk.hpp */, 4BAD9B951F43D7E900724854 /* UnformattedTrack.hpp */, 4BB697CF1D4BA44900248BDF /* Encodings */, 4BAB62B21D327F7E00DF5BA0 /* Formats */, @@ -2850,6 +2855,7 @@ 4BF8295D1D8F048B001BAE39 /* MFM.cpp in Sources */, 4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */, 4B5FADBD1DE31D1500AEC565 /* OricMFMDSK.cpp in Sources */, + 4BE4144C1F65E439006A8D7C /* SingleTrackDisk.cpp in Sources */, 4B14978B1EE4AC5E00CE2596 /* StaticAnalyser.cpp in Sources */, 4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */, 4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */, diff --git a/Storage/Disk/SingleTrackDisk.cpp b/Storage/Disk/SingleTrackDisk.cpp new file mode 100644 index 000000000..c40775c91 --- /dev/null +++ b/Storage/Disk/SingleTrackDisk.cpp @@ -0,0 +1,22 @@ +// +// SingleTrackDisk.cpp +// Clock Signal +// +// Created by Thomas Harte on 10/09/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#include "SingleTrackDisk.hpp" + +using namespace Storage::Disk; + +SingleTrackDisk::SingleTrackDisk(const std::shared_ptr &track) : + track_(track) {} + +unsigned int SingleTrackDisk::get_head_position_count() { + return 1; +} + +std::shared_ptr SingleTrackDisk::get_uncached_track_at_position(unsigned int head, unsigned int position) { + return track_; +} diff --git a/Storage/Disk/SingleTrackDisk.hpp b/Storage/Disk/SingleTrackDisk.hpp new file mode 100644 index 000000000..139a8f165 --- /dev/null +++ b/Storage/Disk/SingleTrackDisk.hpp @@ -0,0 +1,35 @@ +// +// SingleTrackDisk.hpp +// Clock Signal +// +// Created by Thomas Harte on 10/09/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#ifndef SingleTrackDisk_hpp +#define SingleTrackDisk_hpp + +#include "Disk.hpp" + +namespace Storage { +namespace Disk { + +/*! + Provides a disk that has houses a single track. +*/ +class SingleTrackDisk: public Disk { + public: + /// Constructs a single-track disk with the track @c track. + SingleTrackDisk(const std::shared_ptr &track); + + private: + std::shared_ptr track_; + + unsigned int get_head_position_count(); + std::shared_ptr get_uncached_track_at_position(unsigned int head, unsigned int position); +}; + +} +} + +#endif /* SingleTrackDisk_hpp */ From 523e1288fa8815a40fd1e6f961f366e527cf0291 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Sep 2017 17:34:52 -0400 Subject: [PATCH 07/22] Updates the MFM parser to use SingleTrackDisk rather than the equivalent withdrawn Drive functionality. --- Storage/Disk/Encodings/MFM.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index 063bdfb44..d9544f3b5 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -9,6 +9,7 @@ #include "MFM.hpp" #include "../PCMTrack.hpp" +#include "../SingleTrackDisk.hpp" #include "../../../NumberTheory/CRC.hpp" #include @@ -255,7 +256,7 @@ Parser::Parser(bool is_mfm, const std::shared_ptr &disk) : Parser::Parser(bool is_mfm, const std::shared_ptr &track) : Parser(is_mfm) { - drive_->set_disk_with_track(track); + drive_->set_disk(std::make_shared(track)); } void Parser::seek_to_track(uint8_t track) { From 0622187ddf4fc01390a65077b3dcd663959b82ea Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Sep 2017 19:23:23 -0400 Subject: [PATCH 08/22] Strips Controller of all capabilities now housed on the Drive. --- Components/1770/1770.cpp | 32 +-- Components/8272/i8272.cpp | 13 +- Machines/AmstradCPC/AmstradCPC.cpp | 3 +- .../Commodore/1540/Implementation/C1540.cpp | 6 +- .../1540/Implementation/C1540Base.hpp | 2 +- Machines/Electron/Plus3.cpp | 1 - Machines/Oric/Microdisc.cpp | 2 +- StaticAnalyser/Commodore/Disk.cpp | 6 +- Storage/Disk/DiskController.cpp | 222 ++++-------------- Storage/Disk/DiskController.hpp | 90 ++----- Storage/Disk/Drive.cpp | 15 ++ Storage/Disk/Drive.hpp | 4 +- Storage/Disk/Encodings/MFM.cpp | 7 +- Storage/Disk/Encodings/MFM.hpp | 2 +- Storage/Disk/MFMDiskController.cpp | 12 +- Storage/Disk/MFMDiskController.hpp | 2 +- 16 files changed, 124 insertions(+), 295 deletions(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index 45545f7ef..5852d0b4e 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -75,7 +75,7 @@ uint8_t WD1770::get_register(int address) { switch(status_.type) { case Status::One: status |= - (get_is_track_zero() ? Flag::TrackZero : 0) | + (get_drive().get_is_track_zero() ? Flag::TrackZero : 0) | (status_.seek_error ? Flag::SeekError : 0); // TODO: index hole break; @@ -91,11 +91,11 @@ uint8_t WD1770::get_register(int address) { } if(!has_motor_on_line()) { - status |= get_drive_is_ready() ? 0 : Flag::NotReady; + status |= get_drive().get_is_ready() ? 0 : Flag::NotReady; if(status_.type == Status::One) status |= (head_is_loaded_ ? Flag::HeadLoaded : 0); } else { - status |= (get_motor_on() ? Flag::MotorOn : 0); + status |= (get_drive().get_motor_on() ? Flag::MotorOn : 0); if(status_.type == Status::One) status |= (status_.spin_up ? Flag::SpinUp : 0); } @@ -145,7 +145,7 @@ void WD1770::run_for(const Cycles cycles) { #define LINE_LABEL INDIRECT_CONCATENATE(label, __LINE__) #define SPIN_UP() \ - set_motor_on(true); \ + get_drive().set_motor_on(true); \ index_hole_count_ = 0; \ index_hole_count_target_ = 6; \ WAIT_FOR_EVENT(Event1770::IndexHoleTarget); \ @@ -178,7 +178,7 @@ void WD1770::posit_event(int new_event_type) { // motor power-down if(index_hole_count_ == 9 && !status_.busy && has_motor_on_line()) { - set_motor_on(false); + get_drive().set_motor_on(false); } // head unload @@ -257,7 +257,7 @@ void WD1770::posit_event(int new_event_type) { goto test_type1_type; begin_type1_spin_up: - if((command_&0x08) || get_motor_on()) goto test_type1_type; + if((command_&0x08) || get_drive().get_motor_on()) goto test_type1_type; SPIN_UP(); test_type1_type: @@ -280,11 +280,11 @@ void WD1770::posit_event(int new_event_type) { if(step_direction_) track_++; else track_--; perform_step: - if(!step_direction_ && get_is_track_zero()) { + if(!step_direction_ && get_drive().get_is_track_zero()) { track_ = 0; goto verify; } - step(step_direction_ ? 1 : -1); + get_drive().step(step_direction_ ? 1 : -1); unsigned int time_to_wait; switch(command_ & 3) { default: @@ -376,7 +376,7 @@ void WD1770::posit_event(int new_event_type) { goto test_type2_delay; begin_type2_spin_up: - if(get_motor_on()) goto test_type2_delay; + if(get_drive().get_motor_on()) goto test_type2_delay; // Perform spin up. SPIN_UP(); @@ -386,7 +386,7 @@ void WD1770::posit_event(int new_event_type) { WAIT_FOR_TIME(30); test_type2_write_protection: - if(command_&0x20 && get_drive_is_read_only()) { + if(command_&0x20 && get_drive().get_is_read_only()) { update_status([] (Status &status) { status.write_protect = true; }); @@ -541,7 +541,7 @@ void WD1770::posit_event(int new_event_type) { }); WAIT_FOR_EVENT(Event::DataWritten); if(status_.data_request) { - end_writing(); + get_drive().end_writing(); update_status([] (Status &status) { status.lost_data = true; }); @@ -554,7 +554,7 @@ void WD1770::posit_event(int new_event_type) { write_crc(); write_byte(0xff); WAIT_FOR_EVENT(Event::DataWritten); - end_writing(); + get_drive().end_writing(); if(command_ & 0x10) { sector_++; @@ -594,7 +594,7 @@ void WD1770::posit_event(int new_event_type) { goto type3_test_delay; begin_type3_spin_up: - if((command_&0x08) || get_motor_on()) goto type3_test_delay; + if((command_&0x08) || get_drive().get_motor_on()) goto type3_test_delay; SPIN_UP(); type3_test_delay: @@ -675,7 +675,7 @@ void WD1770::posit_event(int new_event_type) { }); write_track_test_write_protect: - if(get_drive_is_read_only()) { + if(get_drive().get_is_read_only()) { update_status([] (Status &status) { status.write_protect = true; }); @@ -755,11 +755,11 @@ void WD1770::posit_event(int new_event_type) { update_status([] (Status &status) { status.lost_data = true; }); - end_writing(); + get_drive().end_writing(); goto wait_for_command; } if(index_hole_count_) { - end_writing(); + get_drive().end_writing(); goto wait_for_command; } diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 664bf54b8..437533249 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -236,9 +236,8 @@ void i8272::set_disk(std::shared_ptr disk, int drive) { active_drive_ = command_[1]&3; \ active_head_ = (command_[1] >> 2)&1; \ set_drive(drives_[active_drive_].drive); \ - drives_[active_drive_].drive->set_head((unsigned int)active_head_); \ - set_is_double_density(command_[0] & 0x40); \ - invalidate_track(); + get_drive().set_head((unsigned int)active_head_); \ + set_is_double_density(command_[0] & 0x40); #define WAIT_FOR_BYTES(n) \ distance_into_section_ = 0; \ @@ -527,7 +526,7 @@ void i8272::posit_event(int event_type) { WAIT_FOR_EVENT(Event::DataWritten); if(!has_input_) { SetOverrun(); - end_writing(); + get_drive().end_writing(); goto abort; } write_byte(input_); @@ -542,7 +541,7 @@ void i8272::posit_event(int event_type) { write_crc(); expects_input_ = false; WAIT_FOR_EVENT(Event::DataWritten); - end_writing(); + get_drive().end_writing(); if(sector_ != command_[6]) { sector_++; @@ -648,7 +647,7 @@ void i8272::posit_event(int event_type) { switch(event_type) { case (int)Event::IndexHole: SetOverrun(); - end_writing(); + get_drive().end_writing(); goto abort; break; case (int)Event::DataWritten: @@ -685,7 +684,7 @@ void i8272::posit_event(int event_type) { WAIT_FOR_EVENT((int)Event::DataWritten | (int)Event::IndexHole); if(event_type != (int)Event::IndexHole) goto format_track_pad; - end_writing(); + get_drive().end_writing(); cylinder_ = header_[0]; head_ = header_[1]; diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 0244ddb41..34cf8d650 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -587,7 +587,8 @@ class FDC: public Intel::i8272::i8272 { FDC() : i8272(bus_handler_, Cycles(8000000), 16, 300) {} void set_motor_on(bool on) { - Intel::i8272::i8272::set_motor_on(on); + // TODO: should set all motors on, not just the one active drive. + get_drive().set_motor_on(on); } }; diff --git a/Machines/Commodore/1540/Implementation/C1540.cpp b/Machines/Commodore/1540/Implementation/C1540.cpp index 6f3ded472..abe95e663 100644 --- a/Machines/Commodore/1540/Implementation/C1540.cpp +++ b/Machines/Commodore/1540/Implementation/C1540.cpp @@ -91,7 +91,7 @@ void Machine::run_for(const Cycles cycles) { m6502_.run_for(cycles); bool drive_motor = drive_VIA_port_handler_.get_motor_enabled(); - set_motor_on(drive_motor); + get_drive().set_motor_on(drive_motor); if(drive_motor) Storage::Disk::Controller::run_for(cycles); } @@ -105,7 +105,7 @@ void MachineBase::mos6522_did_change_interrupt_status(void *mos6522) { #pragma mark - Disk drive -void MachineBase::process_input_bit(int value, unsigned int cycles_since_index_hole) { +void MachineBase::process_input_bit(int value) { shift_register_ = (shift_register_ << 1) | value; if((shift_register_ & 0x3ff) == 0x3ff) { drive_VIA_port_handler_.set_sync_detected(true); @@ -130,7 +130,7 @@ void MachineBase::process_index_hole() {} #pragma mak - Drive VIA delegate void MachineBase::drive_via_did_step_head(void *driveVIA, int direction) { - step(direction); + get_drive().step(direction); } void MachineBase::drive_via_did_set_data_density(void *driveVIA, int density) { diff --git a/Machines/Commodore/1540/Implementation/C1540Base.hpp b/Machines/Commodore/1540/Implementation/C1540Base.hpp index d08165ea7..f96d4b464 100644 --- a/Machines/Commodore/1540/Implementation/C1540Base.hpp +++ b/Machines/Commodore/1540/Implementation/C1540Base.hpp @@ -147,7 +147,7 @@ class MachineBase: MOS::MOS6522::MOS6522 serial_port_VIA_; int shift_register_, bit_window_offset_; - virtual void process_input_bit(int value, unsigned int cycles_since_index_hole); + virtual void process_input_bit(int value); virtual void process_index_hole(); }; diff --git a/Machines/Electron/Plus3.cpp b/Machines/Electron/Plus3.cpp index e0e12bb61..d79057839 100644 --- a/Machines/Electron/Plus3.cpp +++ b/Machines/Electron/Plus3.cpp @@ -42,7 +42,6 @@ void Plus3::set_control_register(uint8_t control, uint8_t changes) { } } if(changes & 0x04) { - invalidate_track(); if(drives_[0]) drives_[0]->set_head((control & 0x04) ? 1 : 0); if(drives_[1]) drives_[1]->set_head((control & 0x04) ? 1 : 0); } diff --git a/Machines/Oric/Microdisc.cpp b/Machines/Oric/Microdisc.cpp index 90c82cc48..d04b5c832 100644 --- a/Machines/Oric/Microdisc.cpp +++ b/Machines/Oric/Microdisc.cpp @@ -95,7 +95,7 @@ uint8_t Microdisc::get_data_request_register() { } void Microdisc::set_head_load_request(bool head_load) { - set_motor_on(head_load); + get_drive().set_motor_on(head_load); if(head_load) { head_load_request_counter_ = 0; } else { diff --git a/StaticAnalyser/Commodore/Disk.cpp b/StaticAnalyser/Commodore/Disk.cpp index 63221d758..d9349b762 100644 --- a/StaticAnalyser/Commodore/Disk.cpp +++ b/StaticAnalyser/Commodore/Disk.cpp @@ -24,7 +24,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller { CommodoreGCRParser() : Storage::Disk::Controller(4000000, 1, 300), shift_register_(0), track_(1) { drive.reset(new Storage::Disk::Drive(4000000, 300)); set_drive(drive); - set_motor_on(true); + get_drive().set_motor_on(true); } struct Sector { @@ -47,7 +47,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller { int direction = difference < 0 ? -1 : 1; difference *= 2 * direction; - for(int c = 0; c < difference; c++) step(direction); + for(int c = 0; c < difference; c++) get_drive().step(direction); unsigned int zone = 3; if(track >= 18) zone = 2; @@ -66,7 +66,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller { uint8_t track_; std::shared_ptr sector_cache_[65536]; - void process_input_bit(int value, unsigned int cycles_since_index_hole) { + void process_input_bit(int value) { shift_register_ = ((shift_register_ << 1) | (unsigned int)value) & 0x3ff; bit_count_++; } diff --git a/Storage/Disk/DiskController.cpp b/Storage/Disk/DiskController.cpp index 772750e88..4b2ee7a9e 100644 --- a/Storage/Disk/DiskController.cpp +++ b/Storage/Disk/DiskController.cpp @@ -7,43 +7,19 @@ // #include "DiskController.hpp" -#include "UnformattedTrack.hpp" + #include "../../NumberTheory/Factors.hpp" -#include using namespace Storage::Disk; Controller::Controller(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) : clock_rate_(clock_rate.as_int() * clock_rate_multiplier), clock_rate_multiplier_(clock_rate_multiplier), - rotational_multiplier_(60, revolutions_per_minute), - - cycles_since_index_hole_(0), - motor_is_on_(false), - - is_reading_(true), - - TimedEventLoop((unsigned int)(clock_rate.as_int() * clock_rate_multiplier)) { + empty_drive_(new Drive((unsigned int)clock_rate.as_int(), 1)) { // seed this class with a PLL, any PLL, so that it's safe to assume non-nullptr later Time one(1); set_expected_bit_length(one); -} - -void Controller::setup_track() { - track_ = drive_->get_track(); - if(!track_) { - track_.reset(new UnformattedTrack); - } - - Time offset; - Time track_time_now = get_time_into_track(); - assert(track_time_now >= Time(0) && current_event_.length <= Time(1)); - - Time time_found = track_->seek_to(track_time_now); - assert(time_found >= Time(0) && time_found <= track_time_now); - offset = track_time_now - time_found; - - get_next_event(offset); + set_drive(empty_drive_); } void Controller::set_component_is_sleeping(void *component, bool is_sleeping) { @@ -51,125 +27,32 @@ void Controller::set_component_is_sleeping(void *component, bool is_sleeping) { } bool Controller::is_sleeping() { - return !(drive_ && drive_->has_disk() && motor_is_on_); + return !drive_ || drive_->is_sleeping(); } void Controller::run_for(const Cycles cycles) { - Time zero(0); + if(drive_) drive_->run_for(cycles); +} - if(drive_ && drive_->has_disk() && motor_is_on_) { - if(!track_) setup_track(); +Drive &Controller::get_drive() { + return *drive_.get(); +} - int number_of_cycles = clock_rate_multiplier_ * cycles.as_int(); - while(number_of_cycles) { - int cycles_until_next_event = (int)get_cycles_until_next_event(); - int cycles_to_run_for = std::min(cycles_until_next_event, number_of_cycles); - if(!is_reading_ && cycles_until_bits_written_ > zero) { - int write_cycles_target = (int)cycles_until_bits_written_.get_unsigned_int(); - if(cycles_until_bits_written_.length % cycles_until_bits_written_.clock_rate) write_cycles_target++; - cycles_to_run_for = std::min(cycles_to_run_for, write_cycles_target); - } +#pragma mark - Drive::EventDelegate - cycles_since_index_hole_ += (unsigned int)cycles_to_run_for; - - number_of_cycles -= cycles_to_run_for; - if(is_reading_) { - pll_->run_for(Cycles(cycles_to_run_for)); - } else { - if(cycles_until_bits_written_ > zero) { - Storage::Time cycles_to_run_for_time(cycles_to_run_for); - if(cycles_until_bits_written_ <= cycles_to_run_for_time) { - process_write_completed(); - if(cycles_until_bits_written_ <= cycles_to_run_for_time) - cycles_until_bits_written_.set_zero(); - else - cycles_until_bits_written_ -= cycles_to_run_for_time; - } else { - cycles_until_bits_written_ -= cycles_to_run_for_time; - } - } - } - TimedEventLoop::run_for(Cycles(cycles_to_run_for)); - } +void Controller::process_event(const Track::Event &event) { + switch(event.type) { + case Track::Event::FluxTransition: pll_->add_pulse(); break; + case Track::Event::IndexHole: process_index_hole(); break; } } -#pragma mark - Track timed event loop - -void Controller::get_next_event(const Time &duration_already_passed) { - if(track_) { - current_event_ = track_->get_next_event(); - } else { - current_event_.length.length = 1; - current_event_.length.clock_rate = 1; - current_event_.type = Track::Event::IndexHole; - } - - // divide interval, which is in terms of a single rotation of the disk, by rotation speed to - // convert it into revolutions per second; this is achieved by multiplying by rotational_multiplier_ - assert(current_event_.length <= Time(1) && current_event_.length >= Time(0)); - Time interval = (current_event_.length - duration_already_passed) * rotational_multiplier_; - set_next_event_time_interval(interval); +void Controller::advance(const Cycles cycles) { + pll_->run_for(cycles); } -void Controller::process_next_event() -{ - switch(current_event_.type) { - case Track::Event::FluxTransition: - if(is_reading_) pll_->add_pulse(); - break; - case Track::Event::IndexHole: -// printf("%p %d [/%d = %d]\n", this, cycles_since_index_hole_, clock_rate_multiplier_, cycles_since_index_hole_ / clock_rate_multiplier_); - cycles_since_index_hole_ = 0; - process_index_hole(); - break; - } - get_next_event(Time(0)); -} - -Storage::Time Controller::get_time_into_track() { - // this is proportion of a second - Time result(cycles_since_index_hole_, 8000000 * clock_rate_multiplier_); - result /= rotational_multiplier_; - result.simplify(); - return result; -} - -#pragma mark - Writing - -void Controller::begin_writing(bool clamp_to_index_hole) { - is_reading_ = false; - clamp_writing_to_index_hole_ = clamp_to_index_hole; - - write_segment_.length_of_a_bit = bit_length_ / rotational_multiplier_; - write_segment_.data.clear(); - write_segment_.number_of_bits = 0; - - write_start_time_ = get_time_into_track(); -} - -void Controller::write_bit(bool value) { - bool needs_new_byte = !(write_segment_.number_of_bits&7); - if(needs_new_byte) write_segment_.data.push_back(0); - if(value) write_segment_.data[write_segment_.number_of_bits >> 3] |= 0x80 >> (write_segment_.number_of_bits & 7); - write_segment_.number_of_bits++; - - cycles_until_bits_written_ += cycles_per_bit_; -} - -void Controller::end_writing() { - is_reading_ = true; - - if(!patched_track_) { - // Avoid creating a new patched track if this one is already patched - patched_track_ = std::dynamic_pointer_cast(track_); - if(!patched_track_) { - patched_track_.reset(new PCMPatchedTrack(track_)); - } - } - patched_track_->add_segment(write_start_time_, write_segment_, clamp_writing_to_index_hole_); - cycles_since_index_hole_ %= 8000000 * clock_rate_multiplier_; - invalidate_track(); // TEMPORARY: to force a seek +void Controller::process_write_completed() { + // Provided for subclasses to override. } #pragma mark - PLL control and delegate @@ -178,66 +61,43 @@ void Controller::set_expected_bit_length(Time bit_length) { bit_length_ = bit_length; bit_length_.simplify(); - cycles_per_bit_ = Storage::Time(clock_rate_) * bit_length; - cycles_per_bit_.simplify(); + Time cycles_per_bit = Storage::Time(clock_rate_) * bit_length; + cycles_per_bit.simplify(); // this conversion doesn't need to be exact because there's a lot of variation to be taken // account of in rotation speed, air turbulence, etc, so a direct conversion will do - int clocks_per_bit = (int)cycles_per_bit_.get_unsigned_int(); + int clocks_per_bit = (int)cycles_per_bit.get_unsigned_int(); pll_.reset(new DigitalPhaseLockedLoop(clocks_per_bit, 3)); pll_->set_delegate(this); } void Controller::digital_phase_locked_loop_output_bit(int value) { - process_input_bit(value, (unsigned int)cycles_since_index_hole_); -} - -#pragma mark - Drive actions - -bool Controller::get_is_track_zero() { - if(!drive_) return false; - return drive_->get_is_track_zero(); -} - -bool Controller::get_drive_is_ready() { - if(!drive_) return false; - return drive_->has_disk(); -} - -bool Controller::get_drive_is_read_only() { - if(!drive_) return false; - return drive_->get_is_read_only(); -} - -void Controller::step(int direction) { - invalidate_track(); - if(drive_) drive_->step(direction); -} - -void Controller::set_motor_on(bool motor_on) { - motor_is_on_ = motor_on; - update_sleep_observer(); -} - -bool Controller::get_motor_on() { - return motor_is_on_; + process_input_bit(value); } void Controller::set_drive(std::shared_ptr drive) { if(drive_ != drive) { - invalidate_track(); + bool was_sleeping = is_sleeping(); +// invalidate_track(); + + if(drive_) { + drive_->set_event_delegate(nullptr); + drive_->set_sleep_observer(nullptr); + } drive_ = drive; - drive->set_sleep_observer(this); - update_sleep_observer(); + if(drive_) { + drive_->set_event_delegate(this); + drive_->set_sleep_observer(this); + } else { + drive_ = empty_drive_; + } + + if(is_sleeping() != was_sleeping) { + update_sleep_observer(); + } } } -void Controller::invalidate_track() { - track_ = nullptr; - if(patched_track_) { - drive_->set_track(patched_track_); - patched_track_ = nullptr; - } +void Controller::begin_writing(bool clamp_to_index_hole) { + get_drive().begin_writing(bit_length_, clamp_to_index_hole); } - -void Controller::process_write_completed() {} diff --git a/Storage/Disk/DiskController.hpp b/Storage/Disk/DiskController.hpp index 4d35eaecc..2f6585729 100644 --- a/Storage/Disk/DiskController.hpp +++ b/Storage/Disk/DiskController.hpp @@ -13,7 +13,6 @@ #include "DigitalPhaseLockedLoop.hpp" #include "PCMSegment.hpp" #include "PCMPatchedTrack.hpp" -#include "../TimedEventLoop.hpp" #include "../../ClockReceiver/ClockReceiver.hpp" #include "../../ClockReceiver/Sleeper.hpp" @@ -30,7 +29,7 @@ namespace Disk { TODO: communication of head size and permissible stepping extents, appropriate simulation of gain. */ -class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop, public Sleeper, public Sleeper::SleepObserver { +class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDelegate, public Sleeper, public Sleeper::SleepObserver { protected: /*! Constructs a @c DiskDrive that will be run at @c clock_rate and runs its PLL at @c clock_rate*clock_rate_multiplier, @@ -49,51 +48,14 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop void run_for(const Cycles cycles); /*! - Sets the current drive. + Sets the current drive. This drive is the one the PLL listens to. */ void set_drive(std::shared_ptr drive); /*! - Announces that the track the drive sees is about to change for a reason unknownt to the controller. + Should be implemented by subclasses; communicates each bit that the PLL recognises. */ - void invalidate_track(); - - /*! - Enables or disables the disk motor. - */ - void set_motor_on(bool motor_on); - - /*! - @returns @c true if the motor is on; @c false otherwise. - */ - bool get_motor_on(); - - /*! - Begins write mode, initiating a PCM sampled region of data. Bits should be written via - @c write_bit. They will be written with the length set via @c set_expected_bit_length. - It is acceptable to supply a backlog of bits. Flux transition events will not be reported - while writing. - - @param clamp_to_index_hole If @c true then writing will automatically be truncated by - the index hole. Writing will continue over the index hole otherwise. - */ - void begin_writing(bool clamp_to_index_hole); - - /*! - Writes the bit @c value as the next in the PCM stream initiated by @c begin_writing. - */ - void write_bit(bool value); - - /*! - Ends write mode, switching back to read mode. The drive will stop overwriting events. - */ - void end_writing(); - - /*! - Should be implemented by subclasses; communicates each bit that the PLL recognises, also specifying - the amount of time since the index hole was last seen. - */ - virtual void process_input_bit(int value, unsigned int cycles_since_index_hole) = 0; + virtual void process_input_bit(int value) = 0; /*! Should be implemented by subclasses; communicates that the index hole has been reached. @@ -106,16 +68,19 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop */ virtual void process_write_completed(); - // for TimedEventLoop - virtual void process_next_event(); + /*! + Puts the drive returned by get_drive() into write mode, supplying the current bit length. - // to satisfy DigitalPhaseLockedLoop::Delegate - void digital_phase_locked_loop_output_bit(int value); + @param clamp_to_index_hole If @c true then writing will automatically be truncated by + the index hole. Writing will continue over the index hole otherwise. + */ + void begin_writing(bool clamp_to_index_hole); - bool get_is_track_zero(); - void step(int direction); - virtual bool get_drive_is_ready(); - bool get_drive_is_read_only(); + /*! + Returns the connected drive or, if none is connected, an invented one. No guarantees are + made about the lifetime or the exclusivity of the invented drive. + */ + Drive &get_drive(); bool is_sleeping(); @@ -127,26 +92,17 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop std::shared_ptr pll_; std::shared_ptr drive_; - std::shared_ptr track_; - int cycles_since_index_hole_; - inline void get_next_event(const Time &duration_already_passed); - Track::Event current_event_; - bool motor_is_on_; - - bool is_reading_; - bool clamp_writing_to_index_hole_; - std::shared_ptr patched_track_; - PCMSegment write_segment_; - Time write_start_time_; - - Time cycles_until_bits_written_; - Time cycles_per_bit_; - - void setup_track(); - Time get_time_into_track(); + std::shared_ptr empty_drive_; void set_component_is_sleeping(void *component, bool is_sleeping); + + // for Drive::EventDelegate + void process_event(const Track::Event &event); + void advance(const Cycles cycles); + + // to satisfy DigitalPhaseLockedLoop::Delegate + void digital_phase_locked_loop_output_bit(int value); }; } diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index 47f5d1750..6e12dc70f 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -83,6 +83,14 @@ void Drive::set_motor_on(bool motor_is_on) { update_sleep_observer(); } +bool Drive::get_motor_on() { + return motor_is_on_; +} + +void Drive::set_event_delegate(Storage::Disk::Drive::EventDelegate *delegate) { + event_delegate_ = delegate; +} + void Drive::run_for(const Cycles cycles) { Time zero(0); @@ -178,6 +186,10 @@ void Drive::setup_track() { void Drive::invalidate_track() { track_ = nullptr; + if(patched_track_) { + set_track(patched_track_); + patched_track_ = nullptr; + } } #pragma mark - Writing @@ -186,6 +198,9 @@ void Drive::begin_writing(Time bit_length, bool clamp_to_index_hole) { is_reading_ = false; clamp_writing_to_index_hole_ = clamp_to_index_hole; + cycles_per_bit_ = Storage::Time(get_input_clock_rate()) * bit_length; + cycles_per_bit_.simplify(); + write_segment_.length_of_a_bit = bit_length / rotational_multiplier_; write_segment_.data.clear(); write_segment_.number_of_bits = 0; diff --git a/Storage/Disk/Drive.hpp b/Storage/Disk/Drive.hpp index 97f12ed49..b11d23fb6 100644 --- a/Storage/Disk/Drive.hpp +++ b/Storage/Disk/Drive.hpp @@ -109,10 +109,10 @@ class Drive: public Sleeper, public TimedEventLoop { If the drive is in write mode, announces that all queued bits have now been written. If the controller provides further bits now then there will be no gap in written data. */ - virtual void process_write_completed() {} + virtual void process_write_completed() = 0; /// Informs the delegate of the passing of @c cycles. - virtual void advance(const Cycles cycles) {} + virtual void advance(const Cycles cycles) = 0; }; /// Sets the current event delegate. diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index d9544f3b5..6a03d5ab2 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -246,7 +246,7 @@ Parser::Parser(bool is_mfm) : drive_.reset(new Storage::Disk::Drive(4000000, 300)); set_drive(drive_); - set_motor_on(true); + drive_->set_motor_on(true); } Parser::Parser(bool is_mfm, const std::shared_ptr &disk) : @@ -267,7 +267,7 @@ void Parser::seek_to_track(uint8_t track) { int direction = difference < 0 ? -1 : 1; difference *= direction; - for(int c = 0; c < difference; c++) step(direction); + for(int c = 0; c < difference; c++) drive_->step(direction); } } @@ -275,7 +275,6 @@ std::shared_ptr Parser::get_sector(uint8_t head, uint8_t track, uint8_t // Switch head and track if necessary. if(head_ != head) { drive_->set_head(head); - invalidate_track(); } seek_to_track(track); int track_index = get_index(head, track, 0); @@ -315,7 +314,7 @@ std::vector Parser::get_track(uint8_t track) { return get_track(); } -void Parser::process_input_bit(int value, unsigned int cycles_since_index_hole) { +void Parser::process_input_bit(int value) { shift_register_ = ((shift_register_ << 1) | (unsigned int)value) & 0xffff; bit_count_++; } diff --git a/Storage/Disk/Encodings/MFM.hpp b/Storage/Disk/Encodings/MFM.hpp index 2ae651555..be521f2a2 100644 --- a/Storage/Disk/Encodings/MFM.hpp +++ b/Storage/Disk/Encodings/MFM.hpp @@ -129,7 +129,7 @@ class Parser: public Storage::Disk::Controller { bool is_mfm_; void seek_to_track(uint8_t track); - void process_input_bit(int value, unsigned int cycles_since_index_hole); + void process_input_bit(int value); void process_index_hole(); uint8_t get_next_byte(); diff --git a/Storage/Disk/MFMDiskController.cpp b/Storage/Disk/MFMDiskController.cpp index 1ae81df9a..63d3e2ac0 100644 --- a/Storage/Disk/MFMDiskController.cpp +++ b/Storage/Disk/MFMDiskController.cpp @@ -53,7 +53,7 @@ NumberTheory::CRC16 &MFMController::get_crc_generator() { return crc_generator_; } -void MFMController::process_input_bit(int value, unsigned int cycles_since_index_hole) { +void MFMController::process_input_bit(int value) { if(data_mode_ == DataMode::Writing) return; shift_register_ = (shift_register_ << 1) | value; @@ -156,12 +156,12 @@ void MFMController::process_input_bit(int value, unsigned int cycles_since_index void MFMController::write_bit(int bit) { if(is_double_density_) { - Controller::write_bit(!bit && !last_bit_); - Controller::write_bit(!!bit); + get_drive().write_bit(!bit && !last_bit_); + get_drive().write_bit(!!bit); last_bit_ = bit; } else { - Controller::write_bit(true); - Controller::write_bit(!!bit); + get_drive().write_bit(true); + get_drive().write_bit(!!bit); } } @@ -172,7 +172,7 @@ void MFMController::write_byte(uint8_t byte) { void MFMController::write_raw_short(uint16_t value) { for(int c = 0; c < 16; c++) { - Controller::write_bit(!!((value << c)&0x8000)); + get_drive().write_bit(!!((value << c)&0x8000)); } } diff --git a/Storage/Disk/MFMDiskController.hpp b/Storage/Disk/MFMDiskController.hpp index 206246ba2..bcd96854c 100644 --- a/Storage/Disk/MFMDiskController.hpp +++ b/Storage/Disk/MFMDiskController.hpp @@ -146,7 +146,7 @@ class MFMController: public Controller { private: // Storage::Disk::Controller - virtual void process_input_bit(int value, unsigned int cycles_since_index_hole); + virtual void process_input_bit(int value); virtual void process_index_hole(); virtual void process_write_completed(); From 8882aa496fc0ac4eb79cfeab8558dd4c59fa40c0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Sep 2017 20:51:05 -0400 Subject: [PATCH 09/22] Corrected wiring to get `advance` signals through to Drive event delegates. --- Storage/Disk/DiskController.cpp | 2 +- Storage/Disk/Drive.cpp | 9 ++++++--- Storage/Disk/Drive.hpp | 3 ++- Storage/Disk/Encodings/MFM.cpp | 1 - 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Storage/Disk/DiskController.cpp b/Storage/Disk/DiskController.cpp index 4b2ee7a9e..62a66ee79 100644 --- a/Storage/Disk/DiskController.cpp +++ b/Storage/Disk/DiskController.cpp @@ -48,7 +48,7 @@ void Controller::process_event(const Track::Event &event) { } void Controller::advance(const Cycles cycles) { - pll_->run_for(cycles); + pll_->run_for(Cycles(cycles.as_int() * clock_rate_multiplier_)); } void Controller::process_write_completed() { diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index 6e12dc70f..afa5e4938 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -91,6 +91,10 @@ void Drive::set_event_delegate(Storage::Disk::Drive::EventDelegate *delegate) { event_delegate_ = delegate; } +void Drive::advance(const Cycles cycles) { + if(event_delegate_) event_delegate_->advance(cycles); +} + void Drive::run_for(const Cycles cycles) { Time zero(0); @@ -111,9 +115,7 @@ void Drive::run_for(const Cycles cycles) { cycles_since_index_hole_ += (unsigned int)cycles_to_run_for; number_of_cycles -= cycles_to_run_for; - if(is_reading_) { - if(event_delegate_) event_delegate_->advance(Cycles(cycles_to_run_for)); - } else { + if(!is_reading_) { if(cycles_until_bits_written_ > zero) { Storage::Time cycles_to_run_for_time(cycles_to_run_for); if(cycles_until_bits_written_ <= cycles_to_run_for_time) { @@ -152,6 +154,7 @@ void Drive::get_next_event(const Time &duration_already_passed) { void Drive::process_next_event() { // TODO: ready test here. + if(current_event_.type == Track::Event::IndexHole) cycles_since_index_hole_ = 0; if(event_delegate_) event_delegate_->process_event(current_event_); get_next_event(Time(0)); } diff --git a/Storage/Disk/Drive.hpp b/Storage/Disk/Drive.hpp index b11d23fb6..59a25201c 100644 --- a/Storage/Disk/Drive.hpp +++ b/Storage/Disk/Drive.hpp @@ -146,7 +146,7 @@ class Drive: public Sleeper, public TimedEventLoop { // If the drive is not currently reading then it is writing. While writing // it can optionally be told to clamp to the index hole. - bool is_reading_ = false; + bool is_reading_ = true; bool clamp_writing_to_index_hole_ = false; // If writing is occurring then the drive will be accumulating a write segment, @@ -166,6 +166,7 @@ class Drive: public Sleeper, public TimedEventLoop { // TimedEventLoop call-ins and state. void process_next_event(); void get_next_event(const Time &duration_already_passed); + void advance(const Cycles cycles); Track::Event current_event_; // Helper for track changes. diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index 6a03d5ab2..3c7919db5 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -426,7 +426,6 @@ std::vector Parser::get_track() { return result; } - std::shared_ptr Parser::get_next_sector() { std::shared_ptr sector(new Sector); index_count_ = 0; From dc0b65f9c96bc714f0b66c697bfcc148bef97fff Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Sep 2017 20:51:21 -0400 Subject: [PATCH 10/22] Corrects initial event loop timing state. --- Storage/TimedEventLoop.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Storage/TimedEventLoop.hpp b/Storage/TimedEventLoop.hpp index e383e9683..8f6549916 100644 --- a/Storage/TimedEventLoop.hpp +++ b/Storage/TimedEventLoop.hpp @@ -100,8 +100,8 @@ namespace Storage { Time get_time_into_next_event(); private: - unsigned int input_clock_rate_; - int cycles_until_event_; + unsigned int input_clock_rate_ = 0; + int cycles_until_event_ = 0; Time subcycles_until_event_; }; From 6d6cac429dcfd6930ad34640372452f9977596b3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Sep 2017 22:44:14 -0400 Subject: [PATCH 11/22] Fixes extra time accumulation during track running. Introduces a bunch of further asserts, which aided me in determining the fix, i.e. that Drives being responsible for their own setup_track could double-pump the event loop. --- Storage/Disk/Drive.cpp | 23 +++++++++++++++-------- Storage/TimedEventLoop.cpp | 16 ++++++++++++++++ 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index afa5e4938..21d769a97 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -66,6 +66,7 @@ Storage::Time Drive::get_time_into_track() { Time result(cycles_since_index_hole_, (int)get_input_clock_rate()); result /= rotational_multiplier_; result.simplify(); + assert(result <= Time(1)); return result; } @@ -92,6 +93,7 @@ void Drive::set_event_delegate(Storage::Disk::Drive::EventDelegate *delegate) { } void Drive::advance(const Cycles cycles) { + cycles_since_index_hole_ += (unsigned int)cycles.as_int(); if(event_delegate_) event_delegate_->advance(cycles); } @@ -99,9 +101,6 @@ void Drive::run_for(const Cycles cycles) { Time zero(0); if(has_disk_ && motor_is_on_) { - // Grab a new track if not already in possession of one. - if(!track_) setup_track(); - int number_of_cycles = cycles.as_int(); while(number_of_cycles) { int cycles_until_next_event = (int)get_cycles_until_next_event(); @@ -112,8 +111,6 @@ void Drive::run_for(const Cycles cycles) { cycles_to_run_for = std::min(cycles_to_run_for, write_cycles_target); } - cycles_since_index_hole_ += (unsigned int)cycles_to_run_for; - number_of_cycles -= cycles_to_run_for; if(!is_reading_) { if(cycles_until_bits_written_ > zero) { @@ -137,6 +134,13 @@ void Drive::run_for(const Cycles cycles) { #pragma mark - Track timed event loop void Drive::get_next_event(const Time &duration_already_passed) { + // Grab a new track if not already in possession of one. This will recursively call get_next_event, + // supplying a proper duration_already_passed. + if(!track_) { + setup_track(); + return; + } + if(track_) { current_event_ = track_->get_next_event(); } else { @@ -154,7 +158,10 @@ void Drive::get_next_event(const Time &duration_already_passed) { void Drive::process_next_event() { // TODO: ready test here. - if(current_event_.type == Track::Event::IndexHole) cycles_since_index_hole_ = 0; + if(current_event_.type == Track::Event::IndexHole) { + assert(get_time_into_track() == Time(1) || get_time_into_track() == Time(0)); + cycles_since_index_hole_ = 0; + } if(event_delegate_) event_delegate_->process_event(current_event_); get_next_event(Time(0)); } @@ -181,9 +188,9 @@ void Drive::setup_track() { assert(track_time_now >= Time(0) && current_event_.length <= Time(1)); Time time_found = track_->seek_to(track_time_now); - assert(time_found >= Time(0) && time_found <= track_time_now); - offset = track_time_now - time_found; + assert(time_found >= Time(0) && time_found < Time(1) && time_found <= track_time_now); + offset = track_time_now - time_found; get_next_event(offset); } diff --git a/Storage/TimedEventLoop.cpp b/Storage/TimedEventLoop.cpp index 6b1b1a597..ad54c1051 100644 --- a/Storage/TimedEventLoop.cpp +++ b/Storage/TimedEventLoop.cpp @@ -8,7 +8,9 @@ #include "TimedEventLoop.hpp" #include "../NumberTheory/Factors.hpp" + #include +#include using namespace Storage; @@ -17,8 +19,14 @@ TimedEventLoop::TimedEventLoop(unsigned int input_clock_rate) : void TimedEventLoop::run_for(const Cycles cycles) { int remaining_cycles = cycles.as_int(); +#ifndef NDEBUG + int cycles_advanced = 0; +#endif while(cycles_until_event_ <= remaining_cycles) { +#ifndef NDEBUG + cycles_advanced += cycles_until_event_; +#endif advance(cycles_until_event_); remaining_cycles -= cycles_until_event_; cycles_until_event_ = 0; @@ -27,8 +35,14 @@ void TimedEventLoop::run_for(const Cycles cycles) { if(remaining_cycles) { cycles_until_event_ -= remaining_cycles; +#ifndef NDEBUG + cycles_advanced += remaining_cycles; +#endif advance(remaining_cycles); } + + assert(cycles_advanced == cycles.as_int()); + assert(cycles_until_event_ > 0); } unsigned int TimedEventLoop::get_cycles_until_next_event() { @@ -65,7 +79,9 @@ void TimedEventLoop::set_next_event_time_interval(Time interval) { // So this event will fire in the integral number of cycles from now, putting us at the remainder // number of subcycles + assert(cycles_until_event_ == 0); cycles_until_event_ += (int)(numerator / denominator); + assert(cycles_until_event_ >= 0); subcycles_until_event_.length = (unsigned int)(numerator % denominator); subcycles_until_event_.clock_rate = (unsigned int)denominator; } From 96bf133924cff35db6d72e73e926f8c1822c43fe Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Sep 2017 22:56:05 -0400 Subject: [PATCH 12/22] Withdraws requirement for DiskController users to specify a PLL multiplier or to provide rotation speed. In the latter case because it's no longer of any interest to the controller, and in the former because I'd rather it be picked automatically. --- Components/1770/1770.cpp | 2 +- Components/8272/i8272.cpp | 4 ++-- Components/8272/i8272.hpp | 2 +- Machines/AmstradCPC/AmstradCPC.cpp | 4 ++-- Machines/Commodore/1540/Implementation/C1540.cpp | 2 +- StaticAnalyser/Commodore/Disk.cpp | 4 ++-- Storage/Disk/DiskController.cpp | 6 +++--- Storage/Disk/DiskController.hpp | 8 +++----- Storage/Disk/Encodings/MFM.cpp | 2 +- Storage/Disk/MFMDiskController.cpp | 4 ++-- Storage/Disk/MFMDiskController.hpp | 2 +- 11 files changed, 19 insertions(+), 21 deletions(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index 5852d0b4e..991f5efdc 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -25,7 +25,7 @@ WD1770::Status::Status() : busy(false) {} WD1770::WD1770(Personality p) : - Storage::Disk::MFMController(8000000, 16, 300), + Storage::Disk::MFMController(8000000), interesting_event_mask_((int)Event1770::Command), resume_point_(0), delay_time_(0), diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 437533249..fea0e1094 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -75,8 +75,8 @@ namespace { const uint8_t CommandSenseDriveStatus = 0x04; } -i8272::i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) : - Storage::Disk::MFMController(clock_rate, clock_rate_multiplier, revolutions_per_minute), +i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) : + Storage::Disk::MFMController(clock_rate), bus_handler_(bus_handler), main_status_(0), interesting_event_mask_((int)Event8272::CommandByte), diff --git a/Components/8272/i8272.hpp b/Components/8272/i8272.hpp index c8fced878..6b01af86d 100644 --- a/Components/8272/i8272.hpp +++ b/Components/8272/i8272.hpp @@ -26,7 +26,7 @@ class BusHandler { class i8272: public Storage::Disk::MFMController { public: - i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute); + i8272(BusHandler &bus_handler, Cycles clock_rate); void run_for(Cycles); diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 34cf8d650..534aa2e78 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -584,10 +584,10 @@ class FDC: public Intel::i8272::i8272 { Intel::i8272::BusHandler bus_handler_; public: - FDC() : i8272(bus_handler_, Cycles(8000000), 16, 300) {} + FDC() : i8272(bus_handler_, Cycles(8000000)) {} void set_motor_on(bool on) { - // TODO: should set all motors on, not just the one active drive. + // TODO: should set all motors on, not 8272.hjust the one active drive. get_drive().set_motor_on(on); } }; diff --git a/Machines/Commodore/1540/Implementation/C1540.cpp b/Machines/Commodore/1540/Implementation/C1540.cpp index abe95e663..7bad7bcf9 100644 --- a/Machines/Commodore/1540/Implementation/C1540.cpp +++ b/Machines/Commodore/1540/Implementation/C1540.cpp @@ -18,7 +18,7 @@ using namespace Commodore::C1540; MachineBase::MachineBase() : m6502_(*this), shift_register_(0), - Storage::Disk::Controller(1000000, 4, 300), + Storage::Disk::Controller(1000000), serial_port_(new SerialPort), serial_port_VIA_port_handler_(new SerialPortVIA(serial_port_VIA_)), drive_VIA_(drive_VIA_port_handler_), diff --git a/StaticAnalyser/Commodore/Disk.cpp b/StaticAnalyser/Commodore/Disk.cpp index d9349b762..b576afe5b 100644 --- a/StaticAnalyser/Commodore/Disk.cpp +++ b/StaticAnalyser/Commodore/Disk.cpp @@ -21,10 +21,10 @@ class CommodoreGCRParser: public Storage::Disk::Controller { public: std::shared_ptr drive; - CommodoreGCRParser() : Storage::Disk::Controller(4000000, 1, 300), shift_register_(0), track_(1) { + CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) { drive.reset(new Storage::Disk::Drive(4000000, 300)); set_drive(drive); - get_drive().set_motor_on(true); + drive->set_motor_on(true); } struct Sector { diff --git a/Storage/Disk/DiskController.cpp b/Storage/Disk/DiskController.cpp index 62a66ee79..0444fb62b 100644 --- a/Storage/Disk/DiskController.cpp +++ b/Storage/Disk/DiskController.cpp @@ -12,9 +12,9 @@ using namespace Storage::Disk; -Controller::Controller(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) : - clock_rate_(clock_rate.as_int() * clock_rate_multiplier), - clock_rate_multiplier_(clock_rate_multiplier), +Controller::Controller(Cycles clock_rate) : + clock_rate_multiplier_(128000000 / clock_rate.as_int()), + clock_rate_(clock_rate.as_int() * clock_rate_multiplier_), empty_drive_(new Drive((unsigned int)clock_rate.as_int(), 1)) { // seed this class with a PLL, any PLL, so that it's safe to assume non-nullptr later Time one(1); diff --git a/Storage/Disk/DiskController.hpp b/Storage/Disk/DiskController.hpp index 2f6585729..ec0b63016 100644 --- a/Storage/Disk/DiskController.hpp +++ b/Storage/Disk/DiskController.hpp @@ -32,10 +32,9 @@ namespace Disk { class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDelegate, public Sleeper, public Sleeper::SleepObserver { protected: /*! - Constructs a @c DiskDrive that will be run at @c clock_rate and runs its PLL at @c clock_rate*clock_rate_multiplier, - spinning inserted disks at @c revolutions_per_minute. + Constructs a @c Controller that will be run at @c clock_rate. */ - Controller(Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute); + Controller(Cycles clock_rate); /*! Communicates to the PLL the expected length of a bit as a fraction of a second. @@ -86,9 +85,8 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDe private: Time bit_length_; - int clock_rate_; int clock_rate_multiplier_; - Time rotational_multiplier_; + int clock_rate_; std::shared_ptr pll_; std::shared_ptr drive_; diff --git a/Storage/Disk/Encodings/MFM.cpp b/Storage/Disk/Encodings/MFM.cpp index 3c7919db5..62eef12d7 100644 --- a/Storage/Disk/Encodings/MFM.cpp +++ b/Storage/Disk/Encodings/MFM.cpp @@ -235,7 +235,7 @@ std::unique_ptr Storage::Encodings::MFM::GetFMEncoder(std::vector Date: Mon, 11 Sep 2017 21:25:26 -0400 Subject: [PATCH 13/22] Separates the 8272's drive selection signalling from actual drive ownership. Thereby returns working motor control to the CPC. --- Components/8272/i8272.cpp | 33 ++++++++++++++---------------- Components/8272/i8272.hpp | 15 ++++++-------- Machines/AmstradCPC/AmstradCPC.cpp | 18 +++++++++++++--- 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index fea0e1094..39a7e9035 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -119,11 +119,12 @@ void i8272::run_for(Cycles cycles) { // Perform a step. int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1; printf("Target %d versus believed %d\n", drives_[c].target_head_position, drives_[c].head_position); - drives_[c].drive->step(direction); + select_drive(c); + get_drive().step(direction); if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction; // Check for completion. - if(drives_[c].seek_is_satisfied()) { + if(seek_is_satisfied(c)) { drives_[c].phase = Drive::CompletedSeeking; drives_seeking_--; break; @@ -192,12 +193,6 @@ uint8_t i8272::get_register(int address) { } } -void i8272::set_disk(std::shared_ptr disk, int drive) { - if(drive < 4 && drive >= 0) { - drives_[drive].drive->set_disk(disk); - } -} - #define BEGIN_SECTION() switch(resume_point_) { default: #define END_SECTION() } @@ -235,7 +230,7 @@ void i8272::set_disk(std::shared_ptr disk, int drive) { #define SET_DRIVE_HEAD_MFM() \ active_drive_ = command_[1]&3; \ active_head_ = (command_[1] >> 2)&1; \ - set_drive(drives_[active_drive_].drive); \ + select_drive(active_drive_); \ get_drive().set_head((unsigned int)active_head_); \ set_is_double_density(command_[0] & 0x40); @@ -503,7 +498,7 @@ void i8272::posit_event(int event_type) { write_data: printf("Write [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]); - if(drives_[active_drive_].drive->get_is_read_only()) { + if(get_drive().get_is_read_only()) { SetNotWriteable(); goto abort; } @@ -618,7 +613,7 @@ void i8272::posit_event(int event_type) { // Performs format [/write] track. format_track: printf("Format track\n"); - if(drives_[active_drive_].drive->get_is_read_only()) { + if(get_drive().get_is_read_only()) { SetNotWriteable(); goto abort; } @@ -711,6 +706,7 @@ void i8272::posit_event(int event_type) { seek: { int drive = command_[1]&3; + select_drive(drive); // Increment the seeking count if this drive wasn't already seeking. if(drives_[drive].phase != Drive::Seeking) { @@ -740,7 +736,7 @@ void i8272::posit_event(int event_type) { } // Check whether any steps are even needed; if not then mark as completed already. - if(drives_[drive].seek_is_satisfied()) { + if(seek_is_satisfied(drive)) { drives_[drive].phase = Drive::CompletedSeeking; drives_seeking_--; } @@ -792,12 +788,13 @@ void i8272::posit_event(int event_type) { printf("Sense drive status\n"); { int drive = command_[1] & 3; + select_drive(drive); result_stack_.push_back( (command_[1] & 7) | // drive and head number 0x08 | // single sided - (drives_[drive].drive->get_is_track_zero() ? 0x10 : 0x00) | - (drives_[drive].drive->get_is_ready() ? 0x20 : 0x00) | - (drives_[drive].drive->get_is_read_only() ? 0x40 : 0x00) + (get_drive().get_is_track_zero() ? 0x10 : 0x00) | + (get_drive().get_is_ready() ? 0x20 : 0x00) | + (get_drive().get_is_read_only() ? 0x40 : 0x00) ); } goto post_result; @@ -852,9 +849,9 @@ void i8272::posit_event(int event_type) { END_SECTION() } -bool i8272::Drive::seek_is_satisfied() { - return (target_head_position == head_position) || - (target_head_position == -1 && drive->get_is_track_zero()); +bool i8272::seek_is_satisfied(int drive) { + return (drives_[drive].target_head_position == drives_[drive].head_position) || + (drives_[drive].target_head_position == -1 && get_drive().get_is_track_zero()); } void i8272::set_dma_acknowledge(bool dack) { diff --git a/Components/8272/i8272.hpp b/Components/8272/i8272.hpp index 6b01af86d..2edab4e24 100644 --- a/Components/8272/i8272.hpp +++ b/Components/8272/i8272.hpp @@ -39,10 +39,11 @@ class i8272: public Storage::Disk::MFMController { void set_dma_acknowledge(bool dack); void set_terminal_count(bool tc); - void set_disk(std::shared_ptr disk, int drive); - bool is_sleeping(); + protected: + virtual void select_drive(int number) = 0; + private: // The bus handler, for interrupt and DMA-driven usage. BusHandler &bus_handler_; @@ -91,24 +92,20 @@ class i8272: public Storage::Disk::MFMController { int steps_taken; int target_head_position; // either an actual number, or -1 to indicate to step until track zero - /// @returns @c true if the currently queued-up seek or recalibrate has reached where it should be. - bool seek_is_satisfied(); - // Head state. int head_unload_delay[2]; bool head_is_loaded[2]; - // The connected drive. - std::shared_ptr drive; - Drive() : head_position(0), phase(NotSeeking), - drive(new Storage::Disk::Drive(8000000, 300)), // TODO: these constants can't live here. head_is_loaded{false, false}, head_unload_delay{0, 0} {}; } drives_[4]; int drives_seeking_; + /// @returns @c true if the selected drive, which is number @c drive, can stop seeking. + bool seek_is_satisfied(int drive); + // User-supplied parameters; as per the specify command. int step_rate_time_; int head_unload_time_; diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 534aa2e78..fe46a63c7 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -582,13 +582,25 @@ class KeyboardState: public GI::AY38910::PortHandler { class FDC: public Intel::i8272::i8272 { private: Intel::i8272::BusHandler bus_handler_; + std::shared_ptr drive_; public: - FDC() : i8272(bus_handler_, Cycles(8000000)) {} + FDC() : + i8272(bus_handler_, Cycles(8000000)), + drive_(new Storage::Disk::Drive(8000000, 300)) { + set_drive(drive_); + } void set_motor_on(bool on) { - // TODO: should set all motors on, not 8272.hjust the one active drive. - get_drive().set_motor_on(on); + drive_->set_motor_on(on); + } + + void select_drive(int c) { + // TODO: support more than one drive. + } + + void set_disk(std::shared_ptr disk, int drive) { + drive_->set_disk(disk); } }; From fb9fd26af7a24a9cc3e4a075d7b6677bccabb6f0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 11 Sep 2017 22:08:10 -0400 Subject: [PATCH 14/22] Updates the 1540 for the slightly-more modern world of decoupled drives and disks (!). --- Machines/Commodore/1540/Implementation/C1540.cpp | 14 ++++++++------ .../Commodore/1540/Implementation/C1540Base.hpp | 1 + 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Machines/Commodore/1540/Implementation/C1540.cpp b/Machines/Commodore/1540/Implementation/C1540.cpp index 7bad7bcf9..1944e8f7c 100644 --- a/Machines/Commodore/1540/Implementation/C1540.cpp +++ b/Machines/Commodore/1540/Implementation/C1540.cpp @@ -22,7 +22,8 @@ MachineBase::MachineBase() : serial_port_(new SerialPort), serial_port_VIA_port_handler_(new SerialPortVIA(serial_port_VIA_)), drive_VIA_(drive_VIA_port_handler_), - serial_port_VIA_(*serial_port_VIA_port_handler_) { + serial_port_VIA_(*serial_port_VIA_port_handler_), + drive_(new Storage::Disk::Drive(1000000, 300)) { // attach the serial port to its VIA and vice versa serial_port_->set_serial_port_via(serial_port_VIA_port_handler_); serial_port_VIA_port_handler_->set_serial_port(serial_port_); @@ -34,6 +35,9 @@ MachineBase::MachineBase() : // set a bit rate set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3)); + + // attach the only drive there is + set_drive(drive_); } void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) { @@ -82,16 +86,14 @@ void Machine::set_rom(const std::vector &rom) { } void Machine::set_disk(std::shared_ptr disk) { - std::shared_ptr drive(new Storage::Disk::Drive(1000000, 300)); - drive->set_disk(disk); - set_drive(drive); + drive_->set_disk(disk); } void Machine::run_for(const Cycles cycles) { m6502_.run_for(cycles); bool drive_motor = drive_VIA_port_handler_.get_motor_enabled(); - get_drive().set_motor_on(drive_motor); + drive_->set_motor_on(drive_motor); if(drive_motor) Storage::Disk::Controller::run_for(cycles); } @@ -130,7 +132,7 @@ void MachineBase::process_index_hole() {} #pragma mak - Drive VIA delegate void MachineBase::drive_via_did_step_head(void *driveVIA, int direction) { - get_drive().step(direction); + drive_->step(direction); } void MachineBase::drive_via_did_set_data_density(void *driveVIA, int density) { diff --git a/Machines/Commodore/1540/Implementation/C1540Base.hpp b/Machines/Commodore/1540/Implementation/C1540Base.hpp index f96d4b464..a179a3f58 100644 --- a/Machines/Commodore/1540/Implementation/C1540Base.hpp +++ b/Machines/Commodore/1540/Implementation/C1540Base.hpp @@ -135,6 +135,7 @@ class MachineBase: protected: CPU::MOS6502::Processor m6502_; + std::shared_ptr drive_; uint8_t ram_[0x800]; uint8_t rom_[0x4000]; From 2f13517f38151e6eab6f204dbd34149a637d5929 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 11 Sep 2017 22:10:56 -0400 Subject: [PATCH 15/22] Adjusts the 1770 not to talk directly to the drive about motor status. --- Components/1770/1770.cpp | 5 +++-- Components/1770/1770.hpp | 1 + Machines/Electron/Plus3.cpp | 6 ++++++ Machines/Electron/Plus3.hpp | 2 ++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index 991f5efdc..f235068b4 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -145,7 +145,7 @@ void WD1770::run_for(const Cycles cycles) { #define LINE_LABEL INDIRECT_CONCATENATE(label, __LINE__) #define SPIN_UP() \ - get_drive().set_motor_on(true); \ + set_motor_on(true); \ index_hole_count_ = 0; \ index_hole_count_target_ = 6; \ WAIT_FOR_EVENT(Event1770::IndexHoleTarget); \ @@ -178,7 +178,7 @@ void WD1770::posit_event(int new_event_type) { // motor power-down if(index_hole_count_ == 9 && !status_.busy && has_motor_on_line()) { - get_drive().set_motor_on(false); + set_motor_on(false); } // head unload @@ -781,6 +781,7 @@ void WD1770::update_status(std::function updater) { } void WD1770::set_head_load_request(bool head_load) {} +void WD1770::set_motor_on(bool motor_on) {} void WD1770::set_head_loaded(bool head_loaded) { head_is_loaded_ = head_loaded; diff --git a/Components/1770/1770.hpp b/Components/1770/1770.hpp index 9d3f46617..f68054f02 100644 --- a/Components/1770/1770.hpp +++ b/Components/1770/1770.hpp @@ -76,6 +76,7 @@ class WD1770: public Storage::Disk::MFMController { protected: virtual void set_head_load_request(bool head_load); + virtual void set_motor_on(bool motor_on); void set_head_loaded(bool head_loaded); private: diff --git a/Machines/Electron/Plus3.cpp b/Machines/Electron/Plus3.cpp index d79057839..c6b4fa9a6 100644 --- a/Machines/Electron/Plus3.cpp +++ b/Machines/Electron/Plus3.cpp @@ -47,3 +47,9 @@ void Plus3::set_control_register(uint8_t control, uint8_t changes) { } if(changes & 0x08) set_is_double_density(!(control & 0x08)); } + +void Plus3::set_motor_on(bool on) { + // TODO: this status should transfer if the selected drive changes. But the same goes for + // writing state, so plenty of work to do in general here. + get_drive().set_motor_on(on); +} diff --git a/Machines/Electron/Plus3.hpp b/Machines/Electron/Plus3.hpp index 054056211..71b8b1c4b 100644 --- a/Machines/Electron/Plus3.hpp +++ b/Machines/Electron/Plus3.hpp @@ -25,6 +25,8 @@ class Plus3 : public WD::WD1770 { std::shared_ptr drives_[2]; int selected_drive_ = 0; uint8_t last_control_ = 0; + + void set_motor_on(bool on); }; } From 42616da7ff03edb4200552150fbba7c64526dd35 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 11 Sep 2017 22:15:54 -0400 Subject: [PATCH 16/22] Adjusts the Oric Microdisc to propagate motor control more widely. --- Machines/Oric/Microdisc.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Machines/Oric/Microdisc.cpp b/Machines/Oric/Microdisc.cpp index d04b5c832..fb8265c87 100644 --- a/Machines/Oric/Microdisc.cpp +++ b/Machines/Oric/Microdisc.cpp @@ -95,7 +95,14 @@ uint8_t Microdisc::get_data_request_register() { } void Microdisc::set_head_load_request(bool head_load) { - get_drive().set_motor_on(head_load); + // The drive motors (at present: I believe **all drive motors** regardless of the selected drive) receive + // the current head load request state. + for(int c = 0; c < 4; c++) { + if(drives_[c]) drives_[c]->set_motor_on(head_load); + } + + // A request to load the head results in a delay until the head is confirmed loaded. This delay is handled + // in ::run_for. A request to unload the head results in an instant answer that the head is unloaded. if(head_load) { head_load_request_counter_ = 0; } else { From 9ac831b09c7a75bb7095f7148111ca6ecb607fc7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 11 Sep 2017 22:24:24 -0400 Subject: [PATCH 17/22] Added an additional protection against overflow. --- Storage/TimedEventLoop.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Storage/TimedEventLoop.cpp b/Storage/TimedEventLoop.cpp index ad54c1051..43d1aee86 100644 --- a/Storage/TimedEventLoop.cpp +++ b/Storage/TimedEventLoop.cpp @@ -84,6 +84,7 @@ void TimedEventLoop::set_next_event_time_interval(Time interval) { assert(cycles_until_event_ >= 0); subcycles_until_event_.length = (unsigned int)(numerator % denominator); subcycles_until_event_.clock_rate = (unsigned int)denominator; + subcycles_until_event_.simplify(); } Time TimedEventLoop::get_time_into_next_event() { From 82b13e98f22d9761111203ff7ff83fde8e0dd35a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 11 Sep 2017 22:27:50 -0400 Subject: [PATCH 18/22] =?UTF-8?q?Implements=20the=20real=20hardware=20read?= =?UTF-8?q?y=20test=20for=20Drives=20=E2=80=94=20motor=20on=20plus=20two?= =?UTF-8?q?=20index=20holes.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Storage/Disk/Drive.cpp | 9 +++++---- Storage/Disk/Drive.hpp | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index 21d769a97..b019f5c4e 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -26,9 +26,6 @@ void Drive::set_disk(const std::shared_ptr &disk) { invalidate_track(); update_sleep_observer(); - - // TODO: implement ready properly. - is_ready_ = true; } bool Drive::has_disk() { @@ -76,11 +73,14 @@ bool Drive::get_is_read_only() { } bool Drive::get_is_ready() { - return is_ready_; + return ready_index_count_ == 2; } void Drive::set_motor_on(bool motor_is_on) { motor_is_on_ = motor_is_on; + if(!motor_is_on) { + ready_index_count_ = 0; + } update_sleep_observer(); } @@ -160,6 +160,7 @@ void Drive::process_next_event() { // TODO: ready test here. if(current_event_.type == Track::Event::IndexHole) { assert(get_time_into_track() == Time(1) || get_time_into_track() == Time(0)); + if(ready_index_count_ < 2) ready_index_count_++; cycles_since_index_hole_ = 0; } if(event_delegate_) event_delegate_->process_event(current_event_); diff --git a/Storage/Disk/Drive.hpp b/Storage/Disk/Drive.hpp index 59a25201c..81e01c48c 100644 --- a/Storage/Disk/Drive.hpp +++ b/Storage/Disk/Drive.hpp @@ -155,8 +155,8 @@ class Drive: public Sleeper, public TimedEventLoop { PCMSegment write_segment_; Time write_start_time_; - // Indicates drive ready state. - bool is_ready_ = false; + // Indicates progress towards drive ready state. + int ready_index_count_ = 0; // Maintains appropriate counting to know when to indicate that writing // is complete. From b62f3e726a9e970fa88bba20f0f0f6d7f3613007 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 12 Sep 2017 20:43:53 -0400 Subject: [PATCH 19/22] Adds a start-of-execution-phase get-out for drives that aren't ready. --- Components/8272/i8272.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 39a7e9035..62ffab6c9 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -335,6 +335,10 @@ void i8272::posit_event(int event_type) { if(!dma_mode_) SetNonDMAExecution(); SET_DRIVE_HEAD_MFM(); LOAD_HEAD(); + if(!get_drive().get_is_ready()) { + SetNotReady(); + goto abort; + } } // Jump to the proper place. From 4d4a0cf1d2912c23aac3abcd34bda4022c3e30f6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 14 Sep 2017 22:30:40 -0400 Subject: [PATCH 20/22] Puts the disk controller back into the loop with knowledge about reading mode, and uses that knowledge to cut off the PLL. --- Components/1770/1770.cpp | 8 ++++---- Components/8272/i8272.cpp | 8 ++++---- Storage/Disk/DiskController.cpp | 14 ++++++++++++-- Storage/Disk/DiskController.hpp | 20 +++++++++++++++++--- 4 files changed, 37 insertions(+), 13 deletions(-) diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index f235068b4..43f59df80 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -541,7 +541,7 @@ void WD1770::posit_event(int new_event_type) { }); WAIT_FOR_EVENT(Event::DataWritten); if(status_.data_request) { - get_drive().end_writing(); + end_writing(); update_status([] (Status &status) { status.lost_data = true; }); @@ -554,7 +554,7 @@ void WD1770::posit_event(int new_event_type) { write_crc(); write_byte(0xff); WAIT_FOR_EVENT(Event::DataWritten); - get_drive().end_writing(); + end_writing(); if(command_ & 0x10) { sector_++; @@ -755,11 +755,11 @@ void WD1770::posit_event(int new_event_type) { update_status([] (Status &status) { status.lost_data = true; }); - get_drive().end_writing(); + end_writing(); goto wait_for_command; } if(index_hole_count_) { - get_drive().end_writing(); + end_writing(); goto wait_for_command; } diff --git a/Components/8272/i8272.cpp b/Components/8272/i8272.cpp index 62ffab6c9..30cbb0cb1 100644 --- a/Components/8272/i8272.cpp +++ b/Components/8272/i8272.cpp @@ -525,7 +525,7 @@ void i8272::posit_event(int event_type) { WAIT_FOR_EVENT(Event::DataWritten); if(!has_input_) { SetOverrun(); - get_drive().end_writing(); + end_writing(); goto abort; } write_byte(input_); @@ -540,7 +540,7 @@ void i8272::posit_event(int event_type) { write_crc(); expects_input_ = false; WAIT_FOR_EVENT(Event::DataWritten); - get_drive().end_writing(); + end_writing(); if(sector_ != command_[6]) { sector_++; @@ -646,7 +646,7 @@ void i8272::posit_event(int event_type) { switch(event_type) { case (int)Event::IndexHole: SetOverrun(); - get_drive().end_writing(); + end_writing(); goto abort; break; case (int)Event::DataWritten: @@ -683,7 +683,7 @@ void i8272::posit_event(int event_type) { WAIT_FOR_EVENT((int)Event::DataWritten | (int)Event::IndexHole); if(event_type != (int)Event::IndexHole) goto format_track_pad; - get_drive().end_writing(); + end_writing(); cylinder_ = header_[0]; head_ = header_[1]; diff --git a/Storage/Disk/DiskController.cpp b/Storage/Disk/DiskController.cpp index 0444fb62b..429f94695 100644 --- a/Storage/Disk/DiskController.cpp +++ b/Storage/Disk/DiskController.cpp @@ -48,7 +48,7 @@ void Controller::process_event(const Track::Event &event) { } void Controller::advance(const Cycles cycles) { - pll_->run_for(Cycles(cycles.as_int() * clock_rate_multiplier_)); + if(is_reading_) pll_->run_for(Cycles(cycles.as_int() * clock_rate_multiplier_)); } void Controller::process_write_completed() { @@ -72,7 +72,7 @@ void Controller::set_expected_bit_length(Time bit_length) { } void Controller::digital_phase_locked_loop_output_bit(int value) { - process_input_bit(value); + if(is_reading_) process_input_bit(value); } void Controller::set_drive(std::shared_ptr drive) { @@ -99,5 +99,15 @@ void Controller::set_drive(std::shared_ptr drive) { } void Controller::begin_writing(bool clamp_to_index_hole) { + is_reading_ = false; get_drive().begin_writing(bit_length_, clamp_to_index_hole); } + +void Controller::end_writing() { + is_reading_ = true; + get_drive().end_writing(); +} + +bool Controller::is_reading() { + return is_reading_; +} diff --git a/Storage/Disk/DiskController.hpp b/Storage/Disk/DiskController.hpp index ec0b63016..a259d1dda 100644 --- a/Storage/Disk/DiskController.hpp +++ b/Storage/Disk/DiskController.hpp @@ -68,13 +68,25 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDe virtual void process_write_completed(); /*! - Puts the drive returned by get_drive() into write mode, supplying the current bit length. + Puts the drive returned by get_drive() into write mode, supplying the current bit length + and marks the controller as being in write mode. @param clamp_to_index_hole If @c true then writing will automatically be truncated by the index hole. Writing will continue over the index hole otherwise. */ void begin_writing(bool clamp_to_index_hole); + /*! + Puts the drive returned by get_drive() out of write mode, and marks the controller + as no longer being in write mode. + */ + void end_writing(); + + /*! + @returns @c true if the controller is in reading mode; @c false otherwise. + */ + bool is_reading(); + /*! Returns the connected drive or, if none is connected, an invented one. No guarantees are made about the lifetime or the exclusivity of the invented drive. @@ -85,8 +97,10 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDe private: Time bit_length_; - int clock_rate_multiplier_; - int clock_rate_; + int clock_rate_multiplier_ = 1; + int clock_rate_ = 1; + + bool is_reading_ = true; std::shared_ptr pll_; std::shared_ptr drive_; From bf20c717fbad7bdc0d623652c057e54eabfb5404 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 14 Sep 2017 22:32:13 -0400 Subject: [PATCH 21/22] =?UTF-8?q?The=20Drive=20now=20no=20longer=20produce?= =?UTF-8?q?s=20input=20when=20in=20writing=20mode=20=E2=80=94=20other=20th?= =?UTF-8?q?an=20announcing=20the=20index=20hole.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Storage/Disk/Drive.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index b019f5c4e..14f379c8d 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -98,9 +98,9 @@ void Drive::advance(const Cycles cycles) { } void Drive::run_for(const Cycles cycles) { - Time zero(0); - if(has_disk_ && motor_is_on_) { + Time zero(0); + int number_of_cycles = cycles.as_int(); while(number_of_cycles) { int cycles_until_next_event = (int)get_cycles_until_next_event(); @@ -163,7 +163,12 @@ void Drive::process_next_event() { if(ready_index_count_ < 2) ready_index_count_++; cycles_since_index_hole_ = 0; } - if(event_delegate_) event_delegate_->process_event(current_event_); + if( + event_delegate_ && + (current_event_.type == Track::Event::IndexHole || is_reading_) + ){ + event_delegate_->process_event(current_event_); + } get_next_event(Time(0)); } From 662d031e3cfa608412f8c5b9aa5cd595aa66b313 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 15 Sep 2017 19:14:36 -0400 Subject: [PATCH 22/22] Adds exposition on the meaning of a disk controller being in write mode. --- Storage/Disk/DiskController.hpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Storage/Disk/DiskController.hpp b/Storage/Disk/DiskController.hpp index a259d1dda..47a3b248f 100644 --- a/Storage/Disk/DiskController.hpp +++ b/Storage/Disk/DiskController.hpp @@ -68,8 +68,11 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDe virtual void process_write_completed(); /*! - Puts the drive returned by get_drive() into write mode, supplying the current bit length - and marks the controller as being in write mode. + Puts the controller and the drive returned by get_drive() into write mode, supplying to + the drive the current bit length. + + While the controller is in write mode it disconnects the PLL. So subclasses will not + receive any calls to @c process_input_bit. @param clamp_to_index_hole If @c true then writing will automatically be truncated by the index hole. Writing will continue over the index hole otherwise. @@ -93,6 +96,9 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDe */ Drive &get_drive(); + /*! + As per Sleeper. + */ bool is_sleeping(); private: