mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-26 23:52:26 +00:00
Merge pull request #702 from TomHarte/NZStory
Corrects WD track-zero and write-protect flags.
This commit is contained in:
commit
42dd70dbff
@ -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);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -63,7 +63,7 @@ void Drive::set_disk(const std::shared_ptr<Disk> &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_;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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_;
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
/*!
|
||||
|
Loading…
Reference in New Issue
Block a user