diff --git a/Components/1770/1770.cpp b/Components/1770/1770.cpp index 7f4bb95bd..076384588 100644 --- a/Components/1770/1770.cpp +++ b/Components/1770/1770.cpp @@ -61,20 +61,28 @@ uint8_t WD1770::get_register(int address) { status.interrupt_request = false; }); uint8_t status = - (status_.write_protect ? Flag::WriteProtect : 0) | - (status_.crc_error ? Flag::CRCError : 0) | - (status_.busy ? Flag::Busy : 0); + (status_.crc_error ? Flag::CRCError : 0) | + (status_.busy ? Flag::Busy : 0); + + // Per Jean Louis-Guérin's documentation: + // + // * the write-protect bit is locked into place by a type 2 or type 3 command, but is + // read live after a type 1. + // * the track 0 bit is captured during a type 1 instruction and lost upon any other type, + // it is not live sampled. switch(status_.type) { case Status::One: status |= - (get_drive().get_is_track_zero() ? Flag::TrackZero : 0) | - (status_.seek_error ? Flag::SeekError : 0); - // TODO: index hole + (status_.track_zero ? Flag::TrackZero : 0) | + (status_.seek_error ? Flag::SeekError : 0) | + (get_drive().get_is_read_only() ? Flag::WriteProtect : 0) | + (get_drive().get_index_pulse() ? Flag::Index : 0); break; case Status::Two: case Status::Three: status |= + (status_.write_protect ? Flag::WriteProtect : 0) | (status_.record_type ? Flag::RecordType : 0) | (status_.lost_data ? Flag::LostData : 0) | (status_.data_request ? Flag::DataRequest : 0) | @@ -91,7 +99,7 @@ uint8_t WD1770::get_register(int address) { if(status_.type == Status::One) status |= (status_.spin_up ? Flag::SpinUp : 0); } - LOG("Returned status " << PADHEX(2) << int(status) << " of type " << 1+int(status_.type)); +// LOG("Returned status " << PADHEX(2) << int(status) << " of type " << 1+int(status_.type)); return status; } case 1: @@ -192,6 +200,7 @@ void WD1770::posit_event(int new_event_type) { update_status([] (Status &status) { status.type = Status::One; status.data_request = false; + status.spin_up = false; }); } else { if(!(interesting_event_mask_ & int(new_event_type))) return; @@ -217,6 +226,7 @@ void WD1770::posit_event(int new_event_type) { update_status([] (Status &status) { status.busy = true; status.interrupt_request = false; + status.track_zero = false; // Always reset by a non-type 1; so reset regardless and set properly later. }); LOG("Starting " << PADHEX(2) << int(command_)); @@ -282,7 +292,7 @@ void WD1770::posit_event(int new_event_type) { } perform_seek_or_restore_command: - if(track_ == data_) goto verify; + if(track_ == data_) goto verify_seek; step_direction_ = (data_ > track_); adjust_track: @@ -291,7 +301,7 @@ void WD1770::posit_event(int new_event_type) { perform_step: if(!step_direction_ && get_drive().get_is_track_zero()) { track_ = 0; - goto verify; + goto verify_seek; } get_drive().step(Storage::Disk::HeadPosition(step_direction_ ? 1 : -1)); Cycles::IntType time_to_wait; @@ -303,14 +313,17 @@ void WD1770::posit_event(int new_event_type) { case 3: time_to_wait = (personality_ == P1772) ? 3 : 30; break; } WAIT_FOR_TIME(time_to_wait); - if(command_ >> 5) goto verify; + if(command_ >> 5) goto verify_seek; goto perform_seek_or_restore_command; perform_step_command: if(command_ & 0x10) goto adjust_track; goto perform_step; - verify: + verify_seek: + update_status([this] (Status &status) { + status.track_zero = get_drive().get_is_track_zero(); + }); if(!(command_ & 0x04)) { goto wait_for_command; } diff --git a/Components/1770/1770.hpp b/Components/1770/1770.hpp index 35e349b10..95e47aff4 100644 --- a/Components/1770/1770.hpp +++ b/Components/1770/1770.hpp @@ -96,6 +96,7 @@ class WD1770: public Storage::Disk::MFMController { bool data_request = false; bool interrupt_request = false; bool busy = false; + bool track_zero = false; enum { One, Two, Three } type = One; diff --git a/Machines/Atari/ST/DMAController.cpp b/Machines/Atari/ST/DMAController.cpp index 1bfe10270..d99676f07 100644 --- a/Machines/Atari/ST/DMAController.cpp +++ b/Machines/Atari/ST/DMAController.cpp @@ -121,6 +121,7 @@ void DMAController::write(int address, uint16_t value) { } void DMAController::set_floppy_drive_selection(bool drive1, bool drive2, bool side2) { +// LOG("Selected: " << (drive1 ? "1" : "-") << (drive2 ? "2" : "-") << (side2 ? "s" : "-")); fdc_.set_floppy_drive_selection(drive1, drive2, side2); } @@ -190,11 +191,11 @@ int DMAController::bus_grant(uint16_t *ram, size_t size) { // Check that the older buffer is full; stop if not. if(!buffer_[active_buffer_ ^ 1].is_full) return 0; -#define b(i, n) " " << PADHEX(2) << buffer_[i].contents[n] +#define b(i, n) " " << PADHEX(2) << int(buffer_[i].contents[n]) #define b2(i, n) b(i, n) << b(i, n+1) #define b4(i, n) b2(i, n) << b2(i, n+2) #define b16(i) b4(i, 0) << b4(i, 4) << b4(i, 8) << b4(i, 12) - LOG("[1] to " << PADHEX(6) << address_ << b16(active_buffer_ ^ 1)); +// LOG("[1] to " << PADHEX(6) << address_ << b16(active_buffer_ ^ 1)); for(int c = 0; c < 8; ++c) { if(size_t(address_) < size) { @@ -210,7 +211,7 @@ int DMAController::bus_grant(uint16_t *ram, size_t size) { // Check that the newer buffer is full; stop if not. if(!buffer_[active_buffer_ ].is_full) return 8; - LOG("[2] to " << PADHEX(6) << address_ << b16(active_buffer_)); +// LOG("[2] to " << PADHEX(6) << address_ << b16(active_buffer_)); #undef b16 #undef b4 #undef b2 diff --git a/Storage/Disk/Drive.cpp b/Storage/Disk/Drive.cpp index c4087236c..be90395ba 100644 --- a/Storage/Disk/Drive.cpp +++ b/Storage/Disk/Drive.cpp @@ -63,7 +63,7 @@ void Drive::set_disk(const std::shared_ptr &disk) { update_clocking_observer(); } -bool Drive::has_disk() { +bool Drive::has_disk() const { return has_disk_; } @@ -71,7 +71,7 @@ ClockingHint::Preference Drive::preferred_clocking() { return (!motor_is_on_ || !has_disk_) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime; } -bool Drive::get_is_track_zero() { +bool Drive::get_is_track_zero() const { return head_position_ == HeadPosition(0); } @@ -114,11 +114,11 @@ void Drive::set_head(int head) { } } -int Drive::get_head_count() { +int Drive::get_head_count() const { return available_heads_; } -bool Drive::get_tachometer() { +bool Drive::get_tachometer() const { // I have made a guess here that the tachometer is a symmetric square wave; // if that is correct then around 60 beats per rotation appears to be correct // to proceed beyond the speed checks I've so far uncovered. @@ -126,22 +126,22 @@ bool Drive::get_tachometer() { return int(get_rotation() * 2.0f * ticks_per_rotation) & 1; } -float Drive::get_rotation() { +float Drive::get_rotation() const { return get_time_into_track(); } -float Drive::get_time_into_track() { +float Drive::get_time_into_track() const { // i.e. amount of time since the index hole was seen, as a proportion of a second, // converted to a proportion of a rotation. return float(cycles_since_index_hole_) / (float(get_input_clock_rate()) * rotational_multiplier_); } -bool Drive::get_is_read_only() { +bool Drive::get_is_read_only() const { if(disk_) return disk_->get_is_read_only(); return true; } -bool Drive::get_is_ready() { +bool Drive::get_is_ready() const { return ready_index_count_ == 2; } @@ -164,10 +164,14 @@ void Drive::set_motor_on(bool motor_is_on) { } } -bool Drive::get_motor_on() { +bool Drive::get_motor_on() const { return motor_is_on_; } +bool Drive::get_index_pulse() const { + return index_pulse_remaining_ > Cycles(0); +} + void Drive::set_event_delegate(Storage::Disk::Drive::EventDelegate *delegate) { event_delegate_ = delegate; } @@ -178,6 +182,9 @@ void Drive::advance(const Cycles cycles) { } void Drive::run_for(const Cycles cycles) { + // Assumed: the index pulse pulses even if the drive has stopped spinning. + index_pulse_remaining_ = std::max(index_pulse_remaining_ - cycles, Cycles(0)); + if(motor_is_on_) { if(has_disk_) { Time zero(0); @@ -258,6 +265,11 @@ void Drive::get_next_event(float duration_already_passed) { current_event_.type = Track::Event::IndexHole; } + // Begin a 2ms period of holding the index line pulse active if this is an index pulse event. + if(current_event_.type == Track::Event::IndexHole) { + index_pulse_remaining_ = Cycles((get_input_clock_rate() * 2) / 1000); + } + // 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_ float interval = std::max((current_event_.length - duration_already_passed) * rotational_multiplier_, 0.0f); @@ -384,7 +396,7 @@ void Drive::end_writing() { } } -bool Drive::is_writing() { +bool Drive::is_writing() const { return !is_reading_; } diff --git a/Storage/Disk/Drive.hpp b/Storage/Disk/Drive.hpp index f33b0d23a..330cdd734 100644 --- a/Storage/Disk/Drive.hpp +++ b/Storage/Disk/Drive.hpp @@ -36,12 +36,12 @@ class Drive: public ClockingHint::Source, public TimedEventLoop { /*! @returns @c true if a disk is currently inserted; @c false otherwise. */ - bool has_disk(); + bool has_disk() const; /*! @returns @c true if the drive head is currently at track zero; @c false otherwise. */ - bool get_is_track_zero(); + bool get_is_track_zero() const; /*! Steps the disk head the specified number of tracks. Positive numbers step inwards (i.e. away from track 0), @@ -57,17 +57,17 @@ class Drive: public ClockingHint::Source, public TimedEventLoop { /*! Gets the head count for this disk. */ - int get_head_count(); + int get_head_count() const; /*! @returns @c true if the inserted disk is read-only or no disk is inserted; @c false otherwise. */ - bool get_is_read_only(); + bool get_is_read_only() const; /*! @returns @c true if the drive is ready; @c false otherwise. */ - bool get_is_ready(); + bool get_is_ready() const; /*! Sets whether the disk motor is on. @@ -77,7 +77,12 @@ class Drive: public ClockingHint::Source, public TimedEventLoop { /*! @returns @c true if the motor is on; @c false otherwise. */ - bool get_motor_on(); + bool get_motor_on() const; + + /*! + @returns @c true if the index pulse output is active; @c false otherwise. + */ + bool get_index_pulse() const; /*! Begins write mode, initiating a PCM sampled region of data. Bits should be written via @@ -104,7 +109,7 @@ class Drive: public ClockingHint::Source, public TimedEventLoop { @returns @c true if the drive has received a call to begin_writing but not yet a call to end_writing; @c false otherwise. */ - bool is_writing(); + bool is_writing() const; /*! Advances the drive by @c number_of_cycles cycles. @@ -163,7 +168,7 @@ class Drive: public ClockingHint::Source, public TimedEventLoop { /*! @returns the current value of the tachometer pulse offered by some drives. */ - bool get_tachometer(); + bool get_tachometer() const; protected: /*! @@ -180,7 +185,7 @@ class Drive: public ClockingHint::Source, public TimedEventLoop { @returns the current rotation of the disk, a float in the half-open range 0.0 (the index hole) to 1.0 (back to the index hole, a whole rotation later). */ - float get_rotation(); + float get_rotation() const; private: // Drives contain an entire disk; from that a certain track @@ -210,6 +215,9 @@ class Drive: public ClockingHint::Source, public TimedEventLoop { // Motor control state. bool motor_is_on_ = false; + // Current state of the index pulse output. + Cycles index_pulse_remaining_; + // 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_ = true; @@ -235,7 +243,7 @@ class Drive: public ClockingHint::Source, public TimedEventLoop { void advance(const Cycles cycles) override; // Helper for track changes. - float get_time_into_track(); + float get_time_into_track() const; // The target (if any) for track events. EventDelegate *event_delegate_ = nullptr; diff --git a/Storage/Disk/Track/Track.hpp b/Storage/Disk/Track/Track.hpp index b35d9293a..d86af916f 100644 --- a/Storage/Disk/Track/Track.hpp +++ b/Storage/Disk/Track/Track.hpp @@ -21,40 +21,40 @@ namespace Disk { class HeadPosition { public: /// Creates an instance decribing position @c value at a resolution of @c scale ticks per track. - HeadPosition(int value, int scale) : position_(value * (4/scale)) {} - explicit HeadPosition(int value) : HeadPosition(value, 1) {} - HeadPosition() : HeadPosition(0) {} + constexpr HeadPosition(int value, int scale) : position_(value * (4/scale)) {} + constexpr explicit HeadPosition(int value) : HeadPosition(value, 1) {} + constexpr HeadPosition() : HeadPosition(0) {} /// @returns the whole number part of the position. - int as_int() const { return position_ >> 2; } + constexpr int as_int() const { return position_ >> 2; } /// @returns n where n/2 is the head position. - int as_half() const { return position_ >> 1; } + constexpr int as_half() const { return position_ >> 1; } /// @returns n where n/4 is the head position. - int as_quarter() const { return position_; } + constexpr int as_quarter() const { return position_; } /// @returns the head position at maximal but unspecified precision. - int as_largest() const { return as_quarter(); } + constexpr int as_largest() const { return as_quarter(); } HeadPosition &operator +=(const HeadPosition &rhs) { position_ += rhs.position_; return *this; } - bool operator ==(const HeadPosition &rhs) const { + constexpr bool operator ==(const HeadPosition &rhs) const { return position_ == rhs.position_; } - bool operator !=(const HeadPosition &rhs) const { + constexpr bool operator !=(const HeadPosition &rhs) const { return position_ != rhs.position_; } - bool operator <(const HeadPosition &rhs) const { + constexpr bool operator <(const HeadPosition &rhs) const { return position_ < rhs.position_; } - bool operator <=(const HeadPosition &rhs) const { + constexpr bool operator <=(const HeadPosition &rhs) const { return position_ <= rhs.position_; } - bool operator >(const HeadPosition &rhs) const { + constexpr bool operator >(const HeadPosition &rhs) const { return position_ > rhs.position_; } - bool operator >=(const HeadPosition &rhs) const { + constexpr bool operator >=(const HeadPosition &rhs) const { return position_ >= rhs.position_; } @@ -79,7 +79,7 @@ class Track { int head; HeadPosition position; - bool operator < (const Address &rhs) const { + constexpr bool operator < (const Address &rhs) const { int largest_position = position.as_largest(); int rhs_largest_position = rhs.position.as_largest(); return std::tie(head, largest_position) < std::tie(rhs.head, rhs_largest_position); diff --git a/Storage/TimedEventLoop.cpp b/Storage/TimedEventLoop.cpp index c7326bb13..aba194ae2 100644 --- a/Storage/TimedEventLoop.cpp +++ b/Storage/TimedEventLoop.cpp @@ -46,11 +46,11 @@ void TimedEventLoop::run_for(const Cycles cycles) { assert(cycles_until_event_ > 0); } -Cycles::IntType TimedEventLoop::get_cycles_until_next_event() { +Cycles::IntType TimedEventLoop::get_cycles_until_next_event() const { return std::max(cycles_until_event_, Cycles::IntType(0)); } -Cycles::IntType TimedEventLoop::get_input_clock_rate() { +Cycles::IntType TimedEventLoop::get_input_clock_rate() const { return input_clock_rate_; } diff --git a/Storage/TimedEventLoop.hpp b/Storage/TimedEventLoop.hpp index 465e47292..64d4ce3a0 100644 --- a/Storage/TimedEventLoop.hpp +++ b/Storage/TimedEventLoop.hpp @@ -52,12 +52,12 @@ namespace Storage { /*! @returns the number of whole cycles remaining until the next event is triggered. */ - Cycles::IntType get_cycles_until_next_event(); + Cycles::IntType get_cycles_until_next_event() const; /*! @returns the input clock rate. */ - Cycles::IntType get_input_clock_rate(); + Cycles::IntType get_input_clock_rate() const; protected: /*!