1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-30 23:29:08 +00:00

Merge pull request #702 from TomHarte/NZStory

Corrects WD track-zero and write-protect flags.
This commit is contained in:
Thomas Harte 2019-12-24 22:22:24 -05:00 committed by GitHub
commit 42dd70dbff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 87 additions and 52 deletions

View File

@ -61,20 +61,28 @@ uint8_t WD1770::get_register(int address) {
status.interrupt_request = false; status.interrupt_request = false;
}); });
uint8_t status = uint8_t status =
(status_.write_protect ? Flag::WriteProtect : 0) |
(status_.crc_error ? Flag::CRCError : 0) | (status_.crc_error ? Flag::CRCError : 0) |
(status_.busy ? Flag::Busy : 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) { switch(status_.type) {
case Status::One: case Status::One:
status |= status |=
(get_drive().get_is_track_zero() ? Flag::TrackZero : 0) | (status_.track_zero ? Flag::TrackZero : 0) |
(status_.seek_error ? Flag::SeekError : 0); (status_.seek_error ? Flag::SeekError : 0) |
// TODO: index hole (get_drive().get_is_read_only() ? Flag::WriteProtect : 0) |
(get_drive().get_index_pulse() ? Flag::Index : 0);
break; break;
case Status::Two: case Status::Two:
case Status::Three: case Status::Three:
status |= status |=
(status_.write_protect ? Flag::WriteProtect : 0) |
(status_.record_type ? Flag::RecordType : 0) | (status_.record_type ? Flag::RecordType : 0) |
(status_.lost_data ? Flag::LostData : 0) | (status_.lost_data ? Flag::LostData : 0) |
(status_.data_request ? Flag::DataRequest : 0) | (status_.data_request ? Flag::DataRequest : 0) |
@ -91,7 +99,7 @@ uint8_t WD1770::get_register(int address) {
if(status_.type == Status::One) if(status_.type == Status::One)
status |= (status_.spin_up ? Flag::SpinUp : 0); 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; return status;
} }
case 1: case 1:
@ -192,6 +200,7 @@ void WD1770::posit_event(int new_event_type) {
update_status([] (Status &status) { update_status([] (Status &status) {
status.type = Status::One; status.type = Status::One;
status.data_request = false; status.data_request = false;
status.spin_up = false;
}); });
} else { } else {
if(!(interesting_event_mask_ & int(new_event_type))) return; 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) { update_status([] (Status &status) {
status.busy = true; status.busy = true;
status.interrupt_request = false; 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_)); LOG("Starting " << PADHEX(2) << int(command_));
@ -282,7 +292,7 @@ void WD1770::posit_event(int new_event_type) {
} }
perform_seek_or_restore_command: perform_seek_or_restore_command:
if(track_ == data_) goto verify; if(track_ == data_) goto verify_seek;
step_direction_ = (data_ > track_); step_direction_ = (data_ > track_);
adjust_track: adjust_track:
@ -291,7 +301,7 @@ void WD1770::posit_event(int new_event_type) {
perform_step: perform_step:
if(!step_direction_ && get_drive().get_is_track_zero()) { if(!step_direction_ && get_drive().get_is_track_zero()) {
track_ = 0; track_ = 0;
goto verify; goto verify_seek;
} }
get_drive().step(Storage::Disk::HeadPosition(step_direction_ ? 1 : -1)); get_drive().step(Storage::Disk::HeadPosition(step_direction_ ? 1 : -1));
Cycles::IntType time_to_wait; 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; case 3: time_to_wait = (personality_ == P1772) ? 3 : 30; break;
} }
WAIT_FOR_TIME(time_to_wait); WAIT_FOR_TIME(time_to_wait);
if(command_ >> 5) goto verify; if(command_ >> 5) goto verify_seek;
goto perform_seek_or_restore_command; goto perform_seek_or_restore_command;
perform_step_command: perform_step_command:
if(command_ & 0x10) goto adjust_track; if(command_ & 0x10) goto adjust_track;
goto perform_step; goto perform_step;
verify: verify_seek:
update_status([this] (Status &status) {
status.track_zero = get_drive().get_is_track_zero();
});
if(!(command_ & 0x04)) { if(!(command_ & 0x04)) {
goto wait_for_command; goto wait_for_command;
} }

View File

@ -96,6 +96,7 @@ class WD1770: public Storage::Disk::MFMController {
bool data_request = false; bool data_request = false;
bool interrupt_request = false; bool interrupt_request = false;
bool busy = false; bool busy = false;
bool track_zero = false;
enum { enum {
One, Two, Three One, Two, Three
} type = One; } type = One;

View File

@ -121,6 +121,7 @@ void DMAController::write(int address, uint16_t value) {
} }
void DMAController::set_floppy_drive_selection(bool drive1, bool drive2, bool side2) { 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); 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. // Check that the older buffer is full; stop if not.
if(!buffer_[active_buffer_ ^ 1].is_full) return 0; 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 b2(i, n) b(i, n) << b(i, n+1)
#define b4(i, n) b2(i, n) << b2(i, n+2) #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) #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) { for(int c = 0; c < 8; ++c) {
if(size_t(address_) < size) { 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. // Check that the newer buffer is full; stop if not.
if(!buffer_[active_buffer_ ].is_full) return 8; 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 b16
#undef b4 #undef b4
#undef b2 #undef b2

View File

@ -63,7 +63,7 @@ void Drive::set_disk(const std::shared_ptr<Disk> &disk) {
update_clocking_observer(); update_clocking_observer();
} }
bool Drive::has_disk() { bool Drive::has_disk() const {
return has_disk_; return has_disk_;
} }
@ -71,7 +71,7 @@ ClockingHint::Preference Drive::preferred_clocking() {
return (!motor_is_on_ || !has_disk_) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime; 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); 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_; 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; // 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 // 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. // 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; return int(get_rotation() * 2.0f * ticks_per_rotation) & 1;
} }
float Drive::get_rotation() { float Drive::get_rotation() const {
return get_time_into_track(); 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, // i.e. amount of time since the index hole was seen, as a proportion of a second,
// converted to a proportion of a rotation. // converted to a proportion of a rotation.
return float(cycles_since_index_hole_) / (float(get_input_clock_rate()) * rotational_multiplier_); 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(); if(disk_) return disk_->get_is_read_only();
return true; return true;
} }
bool Drive::get_is_ready() { bool Drive::get_is_ready() const {
return ready_index_count_ == 2; 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_; 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) { void Drive::set_event_delegate(Storage::Disk::Drive::EventDelegate *delegate) {
event_delegate_ = delegate; event_delegate_ = delegate;
} }
@ -178,6 +182,9 @@ void Drive::advance(const Cycles cycles) {
} }
void Drive::run_for(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(motor_is_on_) {
if(has_disk_) { if(has_disk_) {
Time zero(0); Time zero(0);
@ -258,6 +265,11 @@ void Drive::get_next_event(float duration_already_passed) {
current_event_.type = Track::Event::IndexHole; 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 // 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_ // 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); 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_; return !is_reading_;
} }

View File

@ -36,12 +36,12 @@ class Drive: public ClockingHint::Source, public TimedEventLoop {
/*! /*!
@returns @c true if a disk is currently inserted; @c false otherwise. @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. @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), 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. 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. @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. @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. 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. @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 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 @returns @c true if the drive has received a call to begin_writing but not yet a call to
end_writing; @c false otherwise. end_writing; @c false otherwise.
*/ */
bool is_writing(); bool is_writing() const;
/*! /*!
Advances the drive by @c number_of_cycles cycles. 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. @returns the current value of the tachometer pulse offered by some drives.
*/ */
bool get_tachometer(); bool get_tachometer() const;
protected: 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 @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). 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: private:
// Drives contain an entire disk; from that a certain track // Drives contain an entire disk; from that a certain track
@ -210,6 +215,9 @@ class Drive: public ClockingHint::Source, public TimedEventLoop {
// Motor control state. // Motor control state.
bool motor_is_on_ = false; 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 // If the drive is not currently reading then it is writing. While writing
// it can optionally be told to clamp to the index hole. // it can optionally be told to clamp to the index hole.
bool is_reading_ = true; bool is_reading_ = true;
@ -235,7 +243,7 @@ class Drive: public ClockingHint::Source, public TimedEventLoop {
void advance(const Cycles cycles) override; void advance(const Cycles cycles) override;
// Helper for track changes. // Helper for track changes.
float get_time_into_track(); float get_time_into_track() const;
// The target (if any) for track events. // The target (if any) for track events.
EventDelegate *event_delegate_ = nullptr; EventDelegate *event_delegate_ = nullptr;

View File

@ -21,40 +21,40 @@ namespace Disk {
class HeadPosition { class HeadPosition {
public: public:
/// Creates an instance decribing position @c value at a resolution of @c scale ticks per track. /// 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)) {} constexpr HeadPosition(int value, int scale) : position_(value * (4/scale)) {}
explicit HeadPosition(int value) : HeadPosition(value, 1) {} constexpr explicit HeadPosition(int value) : HeadPosition(value, 1) {}
HeadPosition() : HeadPosition(0) {} constexpr HeadPosition() : HeadPosition(0) {}
/// @returns the whole number part of the position. /// @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. /// @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. /// @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. /// @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) { HeadPosition &operator +=(const HeadPosition &rhs) {
position_ += rhs.position_; position_ += rhs.position_;
return *this; return *this;
} }
bool operator ==(const HeadPosition &rhs) const { constexpr bool operator ==(const HeadPosition &rhs) const {
return position_ == rhs.position_; return position_ == rhs.position_;
} }
bool operator !=(const HeadPosition &rhs) const { constexpr bool operator !=(const HeadPosition &rhs) const {
return position_ != rhs.position_; return position_ != rhs.position_;
} }
bool operator <(const HeadPosition &rhs) const { constexpr bool operator <(const HeadPosition &rhs) const {
return position_ < rhs.position_; return position_ < rhs.position_;
} }
bool operator <=(const HeadPosition &rhs) const { constexpr bool operator <=(const HeadPosition &rhs) const {
return position_ <= rhs.position_; return position_ <= rhs.position_;
} }
bool operator >(const HeadPosition &rhs) const { constexpr bool operator >(const HeadPosition &rhs) const {
return position_ > rhs.position_; return position_ > rhs.position_;
} }
bool operator >=(const HeadPosition &rhs) const { constexpr bool operator >=(const HeadPosition &rhs) const {
return position_ >= rhs.position_; return position_ >= rhs.position_;
} }
@ -79,7 +79,7 @@ class Track {
int head; int head;
HeadPosition position; HeadPosition position;
bool operator < (const Address &rhs) const { constexpr bool operator < (const Address &rhs) const {
int largest_position = position.as_largest(); int largest_position = position.as_largest();
int rhs_largest_position = rhs.position.as_largest(); int rhs_largest_position = rhs.position.as_largest();
return std::tie(head, largest_position) < std::tie(rhs.head, rhs_largest_position); return std::tie(head, largest_position) < std::tie(rhs.head, rhs_largest_position);

View File

@ -46,11 +46,11 @@ void TimedEventLoop::run_for(const Cycles cycles) {
assert(cycles_until_event_ > 0); 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)); 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_; return input_clock_rate_;
} }

View File

@ -52,12 +52,12 @@ namespace Storage {
/*! /*!
@returns the number of whole cycles remaining until the next event is triggered. @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. @returns the input clock rate.
*/ */
Cycles::IntType get_input_clock_rate(); Cycles::IntType get_input_clock_rate() const;
protected: protected:
/*! /*!