mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-25 18:30:21 +00:00
Merge pull request #456 from TomHarte/TristateSleeper
Commutes `Sleeper` to `ClockingHint::Source`
This commit is contained in:
commit
a26ab7086d
@ -52,79 +52,79 @@
|
||||
*/
|
||||
template <class T> class WrappedInt {
|
||||
public:
|
||||
inline WrappedInt(int l) : length_(l) {}
|
||||
inline WrappedInt() : length_(0) {}
|
||||
constexpr WrappedInt(int l) : length_(l) {}
|
||||
constexpr WrappedInt() : length_(0) {}
|
||||
|
||||
inline T &operator =(const T &rhs) {
|
||||
T &operator =(const T &rhs) {
|
||||
length_ = rhs.length_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline T &operator +=(const T &rhs) {
|
||||
T &operator +=(const T &rhs) {
|
||||
length_ += rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator -=(const T &rhs) {
|
||||
T &operator -=(const T &rhs) {
|
||||
length_ -= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator ++() {
|
||||
T &operator ++() {
|
||||
++ length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator ++(int) {
|
||||
T &operator ++(int) {
|
||||
length_ ++;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator --() {
|
||||
T &operator --() {
|
||||
-- length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator --(int) {
|
||||
T &operator --(int) {
|
||||
length_ --;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator %=(const T &rhs) {
|
||||
T &operator %=(const T &rhs) {
|
||||
length_ %= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T &operator &=(const T &rhs) {
|
||||
T &operator &=(const T &rhs) {
|
||||
length_ &= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
inline T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
|
||||
inline T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
|
||||
constexpr T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
|
||||
constexpr T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
|
||||
|
||||
inline T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
|
||||
inline T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
|
||||
constexpr T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
|
||||
constexpr T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
|
||||
|
||||
inline T operator -() const { return T(- length_); }
|
||||
constexpr T operator -() const { return T(- length_); }
|
||||
|
||||
inline bool operator <(const T &rhs) const { return length_ < rhs.length_; }
|
||||
inline bool operator >(const T &rhs) const { return length_ > rhs.length_; }
|
||||
inline bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
|
||||
inline bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
|
||||
inline bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
|
||||
inline bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
|
||||
constexpr bool operator <(const T &rhs) const { return length_ < rhs.length_; }
|
||||
constexpr bool operator >(const T &rhs) const { return length_ > rhs.length_; }
|
||||
constexpr bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
|
||||
constexpr bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
|
||||
constexpr bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
|
||||
constexpr bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
|
||||
|
||||
inline bool operator !() const { return !length_; }
|
||||
constexpr bool operator !() const { return !length_; }
|
||||
// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse
|
||||
|
||||
inline int as_int() const { return length_; }
|
||||
constexpr int as_int() const { return length_; }
|
||||
|
||||
/*!
|
||||
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
|
||||
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
|
||||
*/
|
||||
inline T divide(const T &divisor) {
|
||||
T divide(const T &divisor) {
|
||||
T result(length_ / divisor.length_);
|
||||
length_ %= divisor.length_;
|
||||
return result;
|
||||
@ -134,7 +134,7 @@ template <class T> class WrappedInt {
|
||||
Flushes the value in @c this. The current value is returned, and the internal value
|
||||
is reset to zero.
|
||||
*/
|
||||
inline T flush() {
|
||||
T flush() {
|
||||
T result(length_);
|
||||
length_ = 0;
|
||||
return result;
|
||||
@ -150,34 +150,34 @@ template <class T> class WrappedInt {
|
||||
/// Describes an integer number of whole cycles: pairs of clock signal transitions.
|
||||
class Cycles: public WrappedInt<Cycles> {
|
||||
public:
|
||||
inline Cycles(int l) : WrappedInt<Cycles>(l) {}
|
||||
inline Cycles() : WrappedInt<Cycles>() {}
|
||||
inline Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {}
|
||||
constexpr Cycles(int l) : WrappedInt<Cycles>(l) {}
|
||||
constexpr Cycles() : WrappedInt<Cycles>() {}
|
||||
constexpr Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {}
|
||||
};
|
||||
|
||||
/// Describes an integer number of half cycles: single clock signal transitions.
|
||||
class HalfCycles: public WrappedInt<HalfCycles> {
|
||||
public:
|
||||
inline HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
|
||||
inline HalfCycles() : WrappedInt<HalfCycles>() {}
|
||||
constexpr HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
|
||||
constexpr HalfCycles() : WrappedInt<HalfCycles>() {}
|
||||
|
||||
inline HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
|
||||
inline HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {}
|
||||
constexpr HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
|
||||
constexpr HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {}
|
||||
|
||||
/// @returns The number of whole cycles completely covered by this span of half cycles.
|
||||
inline Cycles cycles() {
|
||||
constexpr Cycles cycles() {
|
||||
return Cycles(length_ >> 1);
|
||||
}
|
||||
|
||||
/// Flushes the whole cycles in @c this, subtracting that many from the total stored here.
|
||||
inline Cycles flush_cycles() {
|
||||
Cycles flush_cycles() {
|
||||
Cycles result(length_ >> 1);
|
||||
length_ &= 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Flushes the half cycles in @c this, returning the number stored and setting this total to zero.
|
||||
inline HalfCycles flush() {
|
||||
HalfCycles flush() {
|
||||
HalfCycles result(length_);
|
||||
length_ = 0;
|
||||
return result;
|
||||
@ -187,7 +187,7 @@ class HalfCycles: public WrappedInt<HalfCycles> {
|
||||
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
|
||||
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
|
||||
*/
|
||||
inline Cycles divide_cycles(const Cycles &divisor) {
|
||||
Cycles divide_cycles(const Cycles &divisor) {
|
||||
HalfCycles half_divisor = HalfCycles(divisor);
|
||||
Cycles result(length_ / half_divisor.length_);
|
||||
length_ %= half_divisor.length_;
|
||||
@ -203,7 +203,6 @@ template <class T> class HalfClockReceiver: public T {
|
||||
public:
|
||||
using T::T;
|
||||
|
||||
using T::run_for;
|
||||
inline void run_for(const HalfCycles half_cycles) {
|
||||
half_cycles_ += half_cycles;
|
||||
T::run_for(half_cycles_.flush_cycles());
|
||||
|
88
ClockReceiver/ClockingHintSource.hpp
Normal file
88
ClockReceiver/ClockingHintSource.hpp
Normal file
@ -0,0 +1,88 @@
|
||||
//
|
||||
// ClockingHintSource.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 20/08/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ClockingHintSource_hpp
|
||||
#define ClockingHintSource_hpp
|
||||
|
||||
namespace ClockingHint {
|
||||
|
||||
enum class Preference {
|
||||
/// The component doesn't currently require a clock signal.
|
||||
None,
|
||||
/// The component can be clocked only immediate prior to (explicit) accesses.
|
||||
JustInTime,
|
||||
/// The component require real-time clocking.
|
||||
RealTime
|
||||
};
|
||||
|
||||
class Source;
|
||||
|
||||
struct Observer {
|
||||
/// Called to inform an observer that the component @c component has changed its clocking requirements.
|
||||
virtual void set_component_prefers_clocking(Source *component, Preference clocking) = 0;
|
||||
};
|
||||
|
||||
/*!
|
||||
An clocking hint source is any component that can provide hints as to the type of
|
||||
clocking required for accurate emulation. A disk controller is an archetypal example.
|
||||
|
||||
Types of clocking are:
|
||||
|
||||
- none:
|
||||
a component that acts and reacts to direct contact but does not have a state that autonomously evolves.
|
||||
E.g. a ROM, RAM, or some kinds of disk controller when not in the process of performing a command.
|
||||
|
||||
- just-in-time:
|
||||
a component that has an evolving state but can receive clock updates only immediately before a
|
||||
direct contact. This is possibly the most common kind of component.
|
||||
|
||||
- real-time:
|
||||
a component that needs to be clocked in 'real time' (i.e. in terms of the emulated machine). For example
|
||||
so that it can announce an interrupt at the proper moment, because it is monitoring some aspect of
|
||||
the machine rather than waiting to be called upon, or because there's some other non-obvious relationship
|
||||
at play.
|
||||
|
||||
A clocking hint source can signal changes in preferred clocking to an observer.
|
||||
|
||||
This is intended to allow for performance improvements to machines with components that can be messaged selectively.
|
||||
The observer callout is virtual so the intended use case is that a machine holds a component that might go through
|
||||
periods of different clocking requirements.
|
||||
|
||||
Transitions should be sufficiently infrequent that a virtual call to announce them costs little enough that
|
||||
the saved or deferred ::run_fors add up to a substantial amount.
|
||||
|
||||
The hint provided is just that: a hint. Owners may perform ::run_for at a greater frequency.
|
||||
*/
|
||||
class Source {
|
||||
public:
|
||||
/// Registers @c observer as the new clocking observer.
|
||||
void set_clocking_hint_observer(Observer *observer) {
|
||||
observer_ = observer;
|
||||
update_clocking_observer();
|
||||
}
|
||||
|
||||
/// @returns the current preferred clocking strategy.
|
||||
virtual Preference preferred_clocking() = 0;
|
||||
|
||||
private:
|
||||
Observer *observer_ = nullptr;
|
||||
|
||||
protected:
|
||||
/*!
|
||||
Provided for subclasses; call this whenever the clocking preference might have changed.
|
||||
This will notify the observer if there is one.
|
||||
*/
|
||||
void update_clocking_observer() {
|
||||
if(!observer_) return;
|
||||
observer_->set_component_prefers_clocking(this, preferred_clocking());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* ClockingHintSource_h */
|
@ -1,60 +0,0 @@
|
||||
//
|
||||
// Sleeper.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 20/08/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Sleeper_hpp
|
||||
#define Sleeper_hpp
|
||||
|
||||
/*!
|
||||
A sleeper is any component that sometimes requires a clock but at other times is 'asleep', i.e. is not doing
|
||||
any clock-derived work, so needn't receive a clock. A disk controller is an archetypal example.
|
||||
|
||||
A sleeper will signal sleeps and wakes to an observer.
|
||||
|
||||
This is intended to allow for performance improvements to machines with components that can sleep. The observer
|
||||
callout is virtual so the intended use case is that a machine holds a component that might sleep. Its transitions
|
||||
into and out of sleep are sufficiently infrequent that a virtual call to announce them costs sufficiently little that
|
||||
the saved ::run_fors add up to a substantial amount.
|
||||
|
||||
By convention, sleeper components must be willing to accept ::run_for even after announcing sleep. It's a hint,
|
||||
not a command.
|
||||
*/
|
||||
class Sleeper {
|
||||
public:
|
||||
Sleeper() : sleep_observer_(nullptr) {}
|
||||
|
||||
class SleepObserver {
|
||||
public:
|
||||
/// Called to inform an observer that the component @c component has either gone to sleep or become awake.
|
||||
virtual void set_component_is_sleeping(Sleeper *component, bool is_sleeping) = 0;
|
||||
};
|
||||
|
||||
/// Registers @c observer as the new sleep observer;
|
||||
void set_sleep_observer(SleepObserver *observer) {
|
||||
sleep_observer_ = observer;
|
||||
}
|
||||
|
||||
/// @returns @c true if the component is currently sleeping; @c false otherwise.
|
||||
virtual bool is_sleeping() = 0;
|
||||
|
||||
protected:
|
||||
/// Provided for subclasses; send sleep announcements to the sleep_observer_.
|
||||
SleepObserver *sleep_observer_;
|
||||
|
||||
/*!
|
||||
Provided for subclasses; call this whenever is_sleeping might have changed, and the observer will be notified,
|
||||
if one exists.
|
||||
|
||||
@c is_sleeping will be called only if there is an observer.
|
||||
*/
|
||||
void update_sleep_observer() {
|
||||
if(!sleep_observer_) return;
|
||||
sleep_observer_->set_component_is_sleeping(this, is_sleeping());
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* Sleeper_h */
|
@ -43,7 +43,6 @@ class WD1770: public Storage::Disk::MFMController {
|
||||
|
||||
/// Runs the controller for @c number_of_cycles cycles.
|
||||
void run_for(const Cycles cycles);
|
||||
using Storage::Disk::Controller::run_for;
|
||||
|
||||
enum Flag: uint8_t {
|
||||
NotReady = 0x80,
|
||||
|
@ -83,8 +83,10 @@ i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) :
|
||||
posit_event(static_cast<int>(Event8272::CommandByte));
|
||||
}
|
||||
|
||||
bool i8272::is_sleeping() {
|
||||
return is_sleeping_ && Storage::Disk::MFMController::is_sleeping();
|
||||
ClockingHint::Preference i8272::preferred_clocking() {
|
||||
const auto mfm_controller_preferred_clocking = Storage::Disk::MFMController::preferred_clocking();
|
||||
if(mfm_controller_preferred_clocking != ClockingHint::Preference::None) return mfm_controller_preferred_clocking;
|
||||
return is_sleeping_ ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
|
||||
}
|
||||
|
||||
void i8272::run_for(Cycles cycles) {
|
||||
@ -159,7 +161,7 @@ void i8272::run_for(Cycles cycles) {
|
||||
}
|
||||
|
||||
is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_;
|
||||
if(is_sleeping_) update_sleep_observer();
|
||||
if(is_sleeping_) update_clocking_observer();
|
||||
}
|
||||
|
||||
void i8272::set_register(int address, uint8_t value) {
|
||||
@ -198,7 +200,7 @@ uint8_t i8272::get_register(int address) {
|
||||
|
||||
#define MS_TO_CYCLES(x) x * 8000
|
||||
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__:
|
||||
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_sleep_observer(); case __LINE__: if(delay_time_) return;
|
||||
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_clocking_observer(); case __LINE__: if(delay_time_) return;
|
||||
|
||||
#define PASTE(x, y) x##y
|
||||
#define CONCAT(x, y) PASTE(x, y)
|
||||
@ -257,7 +259,7 @@ uint8_t i8272::get_register(int address) {
|
||||
if(drives_[active_drive_].head_unload_delay[active_head_] == 0) { \
|
||||
head_timers_running_++; \
|
||||
is_sleeping_ = false; \
|
||||
update_sleep_observer(); \
|
||||
update_clocking_observer(); \
|
||||
} \
|
||||
drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\
|
||||
}
|
||||
@ -720,7 +722,7 @@ void i8272::posit_event(int event_type) {
|
||||
if(drives_[drive].phase != Drive::Seeking) {
|
||||
drives_seeking_++;
|
||||
is_sleeping_ = false;
|
||||
update_sleep_observer();
|
||||
update_clocking_observer();
|
||||
}
|
||||
|
||||
// Set currently seeking, with a step to occur right now (yes, it sounds like jamming these
|
||||
|
@ -39,7 +39,7 @@ class i8272: public Storage::Disk::MFMController {
|
||||
void set_dma_acknowledge(bool dack);
|
||||
void set_terminal_count(bool tc);
|
||||
|
||||
bool is_sleeping();
|
||||
ClockingHint::Preference preferred_clocking() override;
|
||||
|
||||
protected:
|
||||
virtual void select_drive(int number) = 0;
|
||||
@ -67,7 +67,7 @@ class i8272: public Storage::Disk::MFMController {
|
||||
ResultEmpty = (1 << 5),
|
||||
NoLongerReady = (1 << 6)
|
||||
};
|
||||
void posit_event(int type);
|
||||
void posit_event(int type) override;
|
||||
int interesting_event_mask_ = static_cast<int>(Event8272::CommandByte);
|
||||
int resume_point_ = 0;
|
||||
bool is_access_command_ = false;
|
||||
|
@ -19,12 +19,13 @@ namespace {
|
||||
const uint8_t input_flux = 0x1;
|
||||
}
|
||||
|
||||
DiskII::DiskII() :
|
||||
DiskII::DiskII(int clock_rate) :
|
||||
clock_rate_(clock_rate),
|
||||
inputs_(input_command),
|
||||
drives_{{2045454, 300, 1}, {2045454, 300, 1}}
|
||||
drives_{{static_cast<unsigned int>(clock_rate), 300, 1}, {static_cast<unsigned int>(clock_rate), 300, 1}}
|
||||
{
|
||||
drives_[0].set_sleep_observer(this);
|
||||
drives_[1].set_sleep_observer(this);
|
||||
drives_[0].set_clocking_hint_observer(this);
|
||||
drives_[1].set_clocking_hint_observer(this);
|
||||
drives_[active_drive_].set_event_delegate(this);
|
||||
}
|
||||
|
||||
@ -73,71 +74,75 @@ void DiskII::select_drive(int drive) {
|
||||
}
|
||||
|
||||
void DiskII::run_for(const Cycles cycles) {
|
||||
if(is_sleeping()) return;
|
||||
if(preferred_clocking() == ClockingHint::Preference::None) return;
|
||||
|
||||
if(!controller_can_sleep_) {
|
||||
int integer_cycles = cycles.as_int();
|
||||
while(integer_cycles--) {
|
||||
const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6);
|
||||
inputs_ |= input_flux;
|
||||
state_ = state_machine_[static_cast<std::size_t>(address)];
|
||||
switch(state_ & 0xf) {
|
||||
default: shift_register_ = 0; break; // clear
|
||||
case 0x8: break; // nop
|
||||
int integer_cycles = cycles.as_int();
|
||||
while(integer_cycles--) {
|
||||
const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6);
|
||||
inputs_ |= input_flux;
|
||||
state_ = state_machine_[static_cast<std::size_t>(address)];
|
||||
switch(state_ & 0xf) {
|
||||
default: shift_register_ = 0; break; // clear
|
||||
case 0x8: break; // nop
|
||||
|
||||
case 0x9: shift_register_ = static_cast<uint8_t>(shift_register_ << 1); break; // shift left, bringing in a zero
|
||||
case 0xd: shift_register_ = static_cast<uint8_t>((shift_register_ << 1) | 1); break; // shift left, bringing in a one
|
||||
case 0x9: shift_register_ = static_cast<uint8_t>(shift_register_ << 1); break; // shift left, bringing in a zero
|
||||
case 0xd: shift_register_ = static_cast<uint8_t>((shift_register_ << 1) | 1); break; // shift left, bringing in a one
|
||||
|
||||
case 0xa: // shift right, bringing in write protected status
|
||||
shift_register_ = (shift_register_ >> 1) | (is_write_protected() ? 0x80 : 0x00);
|
||||
case 0xa: // shift right, bringing in write protected status
|
||||
shift_register_ = (shift_register_ >> 1) | (is_write_protected() ? 0x80 : 0x00);
|
||||
|
||||
// If the controller is in the sense write protect loop but the register will never change,
|
||||
// short circuit further work and return now.
|
||||
if(shift_register_ == is_write_protected() ? 0xff : 0x00) {
|
||||
if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(integer_cycles));
|
||||
if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(integer_cycles));
|
||||
set_controller_can_sleep();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 0xb: shift_register_ = data_input_; break; // load data register from data bus
|
||||
}
|
||||
|
||||
// Currently writing?
|
||||
if(inputs_&input_mode) {
|
||||
// state_ & 0x80 should be the current level sent to the disk;
|
||||
// therefore transitions in that bit should become flux transitions
|
||||
drives_[active_drive_].write_bit(!!((state_ ^ address) & 0x80));
|
||||
}
|
||||
|
||||
// TODO: surely there's a less heavyweight solution than this?
|
||||
if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(1));
|
||||
if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(1));
|
||||
// If the controller is in the sense write protect loop but the register will never change,
|
||||
// short circuit further work and return now.
|
||||
if(shift_register_ == (is_write_protected() ? 0xff : 0x00)) {
|
||||
if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(integer_cycles));
|
||||
if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(integer_cycles));
|
||||
decide_clocking_preference();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 0xb: shift_register_ = data_input_; break; // load data register from data bus
|
||||
}
|
||||
} else {
|
||||
if(!drive_is_sleeping_[0]) drives_[0].run_for(cycles);
|
||||
if(!drive_is_sleeping_[1]) drives_[1].run_for(cycles);
|
||||
|
||||
// Currently writing?
|
||||
if(inputs_&input_mode) {
|
||||
// state_ & 0x80 should be the current level sent to the disk;
|
||||
// therefore transitions in that bit should become flux transitions
|
||||
drives_[active_drive_].write_bit(!!((state_ ^ address) & 0x80));
|
||||
}
|
||||
|
||||
// TODO: surely there's a less heavyweight solution than inline updates?
|
||||
if(!drive_is_sleeping_[0]) drives_[0].run_for(Cycles(1));
|
||||
if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(1));
|
||||
}
|
||||
|
||||
set_controller_can_sleep();
|
||||
decide_clocking_preference();
|
||||
}
|
||||
|
||||
void DiskII::set_controller_can_sleep() {
|
||||
// Permit the controller to sleep if it's in sense write protect mode, and the shift register
|
||||
// has already filled with the result of shifting eight times.
|
||||
bool controller_could_sleep = controller_can_sleep_;
|
||||
controller_can_sleep_ =
|
||||
(
|
||||
(inputs_ == input_flux) &&
|
||||
!motor_is_enabled_ &&
|
||||
!shift_register_
|
||||
) ||
|
||||
(
|
||||
(inputs_ == (input_command | input_flux)) &&
|
||||
(shift_register_ == (is_write_protected() ? 0xff : 0x00))
|
||||
);
|
||||
if(controller_could_sleep != controller_can_sleep_)
|
||||
update_sleep_observer();
|
||||
void DiskII::decide_clocking_preference() {
|
||||
ClockingHint::Preference prior_preference = clocking_preference_;
|
||||
|
||||
// If in read mode, clocking is either:
|
||||
//
|
||||
// just-in-time, if drives are running or the shift register has any 1s in it or a flux event hasn't yet passed; or
|
||||
// none, given that drives are not running, the shift register has already emptied and there's no flux about to fire.
|
||||
if(!(inputs_ & ~input_flux)) {
|
||||
clocking_preference_ = (!motor_is_enabled_ && !shift_register_ && !(inputs_&input_flux)) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
|
||||
}
|
||||
|
||||
// If in writing mode, clocking is real time.
|
||||
if(inputs_ & input_mode) {
|
||||
clocking_preference_ = ClockingHint::Preference::RealTime;
|
||||
}
|
||||
|
||||
// If in sense-write-protect mode, clocking is just-in-time if the shift register hasn't yet filled with the value that
|
||||
// corresponds to the current write protect status. Otherwise it is none.
|
||||
if((inputs_ & ~input_flux) == input_command) {
|
||||
clocking_preference_ = (shift_register_ == (is_write_protected() ? 0xff : 0x00)) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
|
||||
}
|
||||
|
||||
// Announce a change if there was one.
|
||||
if(prior_preference != clocking_preference_)
|
||||
update_clocking_observer();
|
||||
}
|
||||
|
||||
bool DiskII::is_write_protected() {
|
||||
@ -195,18 +200,18 @@ void DiskII::set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int driv
|
||||
void DiskII::process_event(const Storage::Disk::Track::Event &event) {
|
||||
if(event.type == Storage::Disk::Track::Event::FluxTransition) {
|
||||
inputs_ &= ~input_flux;
|
||||
set_controller_can_sleep();
|
||||
decide_clocking_preference();
|
||||
}
|
||||
}
|
||||
|
||||
void DiskII::set_component_is_sleeping(Sleeper *component, bool is_sleeping) {
|
||||
drive_is_sleeping_[0] = drives_[0].is_sleeping();
|
||||
drive_is_sleeping_[1] = drives_[1].is_sleeping();
|
||||
update_sleep_observer();
|
||||
void DiskII::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) {
|
||||
drive_is_sleeping_[0] = drives_[0].preferred_clocking() == ClockingHint::Preference::None;
|
||||
drive_is_sleeping_[1] = drives_[1].preferred_clocking() == ClockingHint::Preference::None;
|
||||
decide_clocking_preference();
|
||||
}
|
||||
|
||||
bool DiskII::is_sleeping() {
|
||||
return controller_can_sleep_ && drive_is_sleeping_[0] && drive_is_sleeping_[1];
|
||||
ClockingHint::Preference DiskII::preferred_clocking() {
|
||||
return clocking_preference_;
|
||||
}
|
||||
|
||||
void DiskII::set_data_input(uint8_t input) {
|
||||
@ -243,11 +248,11 @@ int DiskII::read_address(int address) {
|
||||
break;
|
||||
case 0xf:
|
||||
if(!(inputs_ & input_mode))
|
||||
drives_[active_drive_].begin_writing(Storage::Time(1, 2045454), false);
|
||||
drives_[active_drive_].begin_writing(Storage::Time(1, clock_rate_), false);
|
||||
inputs_ |= input_mode;
|
||||
break;
|
||||
}
|
||||
set_controller_can_sleep();
|
||||
decide_clocking_preference();
|
||||
return (address & 1) ? 0xff : shift_register_;
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
#define DiskII_hpp
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../ClockReceiver/Sleeper.hpp"
|
||||
#include "../../ClockReceiver/ClockingHintSource.hpp"
|
||||
|
||||
#include "../../Storage/Disk/Disk.hpp"
|
||||
#include "../../Storage/Disk/Drive.hpp"
|
||||
@ -28,10 +28,10 @@ namespace Apple {
|
||||
*/
|
||||
class DiskII:
|
||||
public Storage::Disk::Drive::EventDelegate,
|
||||
public Sleeper::SleepObserver,
|
||||
public Sleeper {
|
||||
public ClockingHint::Source,
|
||||
public ClockingHint::Observer {
|
||||
public:
|
||||
DiskII();
|
||||
DiskII(int clock_rate);
|
||||
|
||||
/// Sets the current external value of the data bus.
|
||||
void set_data_input(uint8_t input);
|
||||
@ -76,7 +76,7 @@ class DiskII:
|
||||
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
|
||||
|
||||
// As per Sleeper.
|
||||
bool is_sleeping() override;
|
||||
ClockingHint::Preference preferred_clocking() override;
|
||||
|
||||
// The Disk II functions as a potential target for @c Activity::Sources.
|
||||
void set_activity_observer(Activity::Observer *observer);
|
||||
@ -95,7 +95,9 @@ class DiskII:
|
||||
|
||||
uint8_t trigger_address(int address, uint8_t value);
|
||||
void process_event(const Storage::Disk::Track::Event &event) override;
|
||||
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override;
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override;
|
||||
|
||||
const int clock_rate_ = 0;
|
||||
|
||||
uint8_t state_ = 0;
|
||||
uint8_t inputs_ = 0;
|
||||
@ -108,11 +110,11 @@ class DiskII:
|
||||
std::array<uint8_t, 256> state_machine_;
|
||||
Storage::Disk::Drive drives_[2];
|
||||
bool drive_is_sleeping_[2];
|
||||
bool controller_can_sleep_ = false;
|
||||
int active_drive_ = 0;
|
||||
bool motor_is_enabled_ = false;
|
||||
|
||||
void set_controller_can_sleep();
|
||||
void decide_clocking_preference();
|
||||
ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::RealTime;
|
||||
|
||||
uint8_t data_input_ = 0;
|
||||
};
|
||||
|
@ -694,7 +694,7 @@ class ConcreteMachine:
|
||||
public KeyboardMachine::Machine,
|
||||
public Utility::TypeRecipient,
|
||||
public CPU::Z80::BusHandler,
|
||||
public Sleeper::SleepObserver,
|
||||
public ClockingHint::Observer,
|
||||
public Machine,
|
||||
public Activity::Source {
|
||||
public:
|
||||
@ -714,11 +714,8 @@ class ConcreteMachine:
|
||||
Memory::Fuzz(ram_, sizeof(ram_));
|
||||
|
||||
// register this class as the sleep observer for the FDC and tape
|
||||
fdc_.set_sleep_observer(this);
|
||||
fdc_is_sleeping_ = fdc_.is_sleeping();
|
||||
|
||||
tape_player_.set_sleep_observer(this);
|
||||
tape_player_is_sleeping_ = tape_player_.is_sleeping();
|
||||
fdc_.set_clocking_hint_observer(this);
|
||||
tape_player_.set_clocking_hint_observer(this);
|
||||
|
||||
ay_.ay().set_port_handler(&key_state_);
|
||||
}
|
||||
@ -749,7 +746,7 @@ class ConcreteMachine:
|
||||
ay_.run_for(cycle.length);
|
||||
|
||||
// Clock the FDC, if connected, using a lazy scale by two
|
||||
if(has_fdc_ && !fdc_is_sleeping_) fdc_.run_for(Cycles(cycle.length.as_int()));
|
||||
time_since_fdc_update_ += cycle.length;
|
||||
|
||||
// Update typing activity
|
||||
if(typer_) typer_->run_for(cycle.length);
|
||||
@ -796,11 +793,13 @@ class ConcreteMachine:
|
||||
|
||||
// Check for an FDC access
|
||||
if(has_fdc_ && (address & 0x580) == 0x100) {
|
||||
flush_fdc();
|
||||
fdc_.set_register(address & 1, *cycle.value);
|
||||
}
|
||||
|
||||
// Check for a disk motor access
|
||||
if(has_fdc_ && !(address & 0x580)) {
|
||||
flush_fdc();
|
||||
fdc_.set_motor_on(!!(*cycle.value));
|
||||
}
|
||||
break;
|
||||
@ -815,6 +814,7 @@ class ConcreteMachine:
|
||||
|
||||
// Check for an FDC access
|
||||
if(has_fdc_ && (address & 0x580) == 0x100) {
|
||||
flush_fdc();
|
||||
*cycle.value &= fdc_.get_register(address & 1);
|
||||
}
|
||||
|
||||
@ -858,6 +858,7 @@ class ConcreteMachine:
|
||||
// Just flush the AY.
|
||||
ay_.update();
|
||||
ay_.flush();
|
||||
flush_fdc();
|
||||
}
|
||||
|
||||
/// A CRTMachine function; indicates that outputs should be created now.
|
||||
@ -967,9 +968,10 @@ class ConcreteMachine:
|
||||
return true;
|
||||
}
|
||||
|
||||
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override final {
|
||||
fdc_is_sleeping_ = fdc_.is_sleeping();
|
||||
tape_player_is_sleeping_ = tape_player_.is_sleeping();
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override final {
|
||||
fdc_is_sleeping_ = fdc_.preferred_clocking() == ClockingHint::Preference::None;
|
||||
tape_player_is_sleeping_ = tape_player_.preferred_clocking() == ClockingHint::Preference::None;
|
||||
printf("FDC: %s, tape %s\n", fdc_is_sleeping_ ? "sleeping" : "regular", tape_player_is_sleeping_ ? "sleeping" : "regular");
|
||||
}
|
||||
|
||||
// MARK: - Keyboard
|
||||
@ -1063,6 +1065,14 @@ class ConcreteMachine:
|
||||
Intel::i8255::i8255<i8255PortHandler> i8255_;
|
||||
|
||||
FDC fdc_;
|
||||
HalfCycles time_since_fdc_update_;
|
||||
void flush_fdc() {
|
||||
// Clock the FDC, if connected, using a lazy scale by two
|
||||
if(has_fdc_ && !fdc_is_sleeping_) {
|
||||
fdc_.run_for(Cycles(time_since_fdc_update_.as_int()));
|
||||
}
|
||||
time_since_fdc_update_ = HalfCycles(0);
|
||||
}
|
||||
|
||||
InterruptTimer interrupt_timer_;
|
||||
Storage::Tape::BinaryTapePlayer tape_player_;
|
||||
|
@ -24,6 +24,8 @@
|
||||
#include "DiskIICard.hpp"
|
||||
#include "Video.hpp"
|
||||
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
|
||||
#include "../../Analyser/Static/AppleII/Target.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
@ -203,7 +205,7 @@ class ConcreteMachine:
|
||||
return &speaker_;
|
||||
}
|
||||
|
||||
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
++ cycles_since_video_update_;
|
||||
++ cycles_since_card_update_;
|
||||
cycles_since_audio_update_ += Cycles(7);
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
using namespace AppleII;
|
||||
|
||||
DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) {
|
||||
DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) : diskii_(2045454) {
|
||||
auto roms = rom_fetcher(
|
||||
"DiskII",
|
||||
{
|
||||
@ -20,7 +20,7 @@ DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sec
|
||||
boot_ = std::move(*roms[0]);
|
||||
diskii_.set_state_machine(*roms[1]);
|
||||
set_select_constraints(None);
|
||||
diskii_.set_sleep_observer(this);
|
||||
diskii_.set_clocking_hint_observer(this);
|
||||
}
|
||||
|
||||
void DiskIICard::perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) {
|
||||
@ -41,7 +41,7 @@ void DiskIICard::perform_bus_operation(Select select, bool is_read, uint16_t add
|
||||
}
|
||||
|
||||
void DiskIICard::run_for(Cycles cycles, int stretches) {
|
||||
if(diskii_is_sleeping_) return;
|
||||
if(diskii_clocking_preference_ == ClockingHint::Preference::None) return;
|
||||
diskii_.run_for(Cycles(cycles.as_int() * 2));
|
||||
}
|
||||
|
||||
@ -53,7 +53,7 @@ void DiskIICard::set_activity_observer(Activity::Observer *observer) {
|
||||
diskii_.set_activity_observer(observer);
|
||||
}
|
||||
|
||||
void DiskIICard::set_component_is_sleeping(Sleeper *component, bool is_sleeping) {
|
||||
diskii_is_sleeping_ = is_sleeping;
|
||||
set_select_constraints(is_sleeping ? (IO | Device) : 0);
|
||||
void DiskIICard::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) {
|
||||
diskii_clocking_preference_ = preference;
|
||||
set_select_constraints((preference != ClockingHint::Preference::RealTime) ? (IO | Device) : 0);
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
#include "../../Components/DiskII/DiskII.hpp"
|
||||
#include "../../Storage/Disk/Disk.hpp"
|
||||
#include "../../ClockReceiver/Sleeper.hpp"
|
||||
#include "../../ClockReceiver/ClockingHintSource.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
@ -22,7 +22,7 @@
|
||||
|
||||
namespace AppleII {
|
||||
|
||||
class DiskIICard: public Card, public Sleeper::SleepObserver {
|
||||
class DiskIICard: public Card, public ClockingHint::Observer {
|
||||
public:
|
||||
DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector);
|
||||
|
||||
@ -34,10 +34,10 @@ class DiskIICard: public Card, public Sleeper::SleepObserver {
|
||||
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
|
||||
|
||||
private:
|
||||
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override;
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override;
|
||||
std::vector<uint8_t> boot_;
|
||||
Apple::DiskII diskii_;
|
||||
bool diskii_is_sleeping_ = false;
|
||||
ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -304,7 +304,7 @@ class ConcreteMachine:
|
||||
public Utility::TypeRecipient,
|
||||
public Storage::Tape::BinaryTapePlayer::Delegate,
|
||||
public Machine,
|
||||
public Sleeper::SleepObserver,
|
||||
public ClockingHint::Observer,
|
||||
public Activity::Source {
|
||||
public:
|
||||
ConcreteMachine() :
|
||||
@ -331,7 +331,7 @@ class ConcreteMachine:
|
||||
user_port_via_port_handler_->set_interrupt_delegate(this);
|
||||
keyboard_via_port_handler_->set_interrupt_delegate(this);
|
||||
tape_->set_delegate(this);
|
||||
tape_->set_sleep_observer(this);
|
||||
tape_->set_clocking_hint_observer(this);
|
||||
|
||||
// install a joystick
|
||||
joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_));
|
||||
@ -749,8 +749,8 @@ class ConcreteMachine:
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override {
|
||||
tape_is_sleeping_ = is_sleeping;
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override {
|
||||
tape_is_sleeping_ = clocking == ClockingHint::Preference::None;
|
||||
set_use_fast_tape();
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,7 @@ class ConcreteMachine:
|
||||
public KeyboardMachine::Machine,
|
||||
public Configurable::Device,
|
||||
public MemoryMap,
|
||||
public Sleeper::SleepObserver,
|
||||
public ClockingHint::Observer,
|
||||
public Activity::Source {
|
||||
public:
|
||||
ConcreteMachine():
|
||||
@ -108,7 +108,7 @@ class ConcreteMachine:
|
||||
|
||||
ay_.set_port_handler(&ay_port_handler_);
|
||||
speaker_.set_input_rate(3579545.0f / 2.0f);
|
||||
tape_player_.set_sleep_observer(this);
|
||||
tape_player_.set_clocking_hint_observer(this);
|
||||
|
||||
// Set the AY to 50% of available volume, the toggle to 10% and leave 40% for an SCC.
|
||||
mixer_.set_relative_volumes({0.5f, 0.1f, 0.4f});
|
||||
@ -555,8 +555,8 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
// MARK: - Sleeper
|
||||
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override {
|
||||
tape_player_is_sleeping_ = tape_player_.is_sleeping();
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override {
|
||||
tape_player_is_sleeping_ = tape_player_.preferred_clocking() == ClockingHint::Preference::None;
|
||||
set_use_fast_tape();
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,6 @@ class Microdisc: public WD::WD1770 {
|
||||
bool get_interrupt_request_line();
|
||||
|
||||
void run_for(const Cycles cycles);
|
||||
using WD::WD1770::run_for;
|
||||
|
||||
enum PagingFlags {
|
||||
/// Indicates that the BASIC ROM should be disabled; if this is set then either
|
||||
@ -52,8 +51,9 @@ class Microdisc: public WD::WD1770 {
|
||||
|
||||
private:
|
||||
void set_control_register(uint8_t control, uint8_t changes);
|
||||
void set_head_load_request(bool head_load);
|
||||
void set_head_load_request(bool head_load) override;
|
||||
bool get_drive_is_ready();
|
||||
|
||||
std::array<std::shared_ptr<Storage::Disk::Drive>, 4> drives_;
|
||||
size_t selected_drive_;
|
||||
bool irq_enable_ = false;
|
||||
|
@ -201,7 +201,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
public Utility::TypeRecipient,
|
||||
public Storage::Tape::BinaryTapePlayer::Delegate,
|
||||
public Microdisc::Delegate,
|
||||
public Sleeper::SleepObserver,
|
||||
public ClockingHint::Observer,
|
||||
public Activity::Source,
|
||||
public Machine {
|
||||
|
||||
@ -212,14 +212,15 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
ay8910_(audio_queue_),
|
||||
speaker_(ay8910_),
|
||||
via_port_handler_(audio_queue_, ay8910_, speaker_, tape_player_, keyboard_),
|
||||
via_(via_port_handler_) {
|
||||
via_(via_port_handler_),
|
||||
diskii_(2000000) {
|
||||
set_clock_rate(1000000);
|
||||
via_port_handler_.set_interrupt_delegate(this);
|
||||
tape_player_.set_delegate(this);
|
||||
Memory::Fuzz(ram_, sizeof(ram_));
|
||||
|
||||
if(disk_interface == Analyser::Static::Oric::Target::DiskInterface::Pravetz) {
|
||||
diskii_.set_sleep_observer(this);
|
||||
diskii_.set_clocking_hint_observer(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -410,6 +411,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
}
|
||||
}
|
||||
} else {
|
||||
flush_diskii();
|
||||
const int disk_value = diskii_.read_address(address);
|
||||
if(isReadOperation(operation) && disk_value != diskii_.DidNotLoad) *value = static_cast<uint8_t>(disk_value);
|
||||
}
|
||||
@ -444,9 +446,11 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
microdisc_.run_for(Cycles(8));
|
||||
break;
|
||||
case Analyser::Static::Oric::Target::DiskInterface::Pravetz:
|
||||
if(!diskii_is_sleeping_) {
|
||||
if(diskii_clocking_preference_ == ClockingHint::Preference::RealTime) {
|
||||
diskii_.set_data_input(*value);
|
||||
diskii_.run_for(Cycles(2));
|
||||
} else {
|
||||
cycles_since_diskii_update_ += Cycles(2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -457,6 +461,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
forceinline void flush() {
|
||||
update_video();
|
||||
via_port_handler_.flush();
|
||||
flush_diskii();
|
||||
}
|
||||
|
||||
// to satisfy CRTMachine::Machine
|
||||
@ -571,8 +576,8 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
}
|
||||
}
|
||||
|
||||
void set_component_is_sleeping(Sleeper *component, bool is_sleeping) override final {
|
||||
diskii_is_sleeping_ = diskii_.is_sleeping();
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override final {
|
||||
diskii_clocking_preference_ = diskii_.preferred_clocking();
|
||||
}
|
||||
|
||||
private:
|
||||
@ -617,9 +622,13 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
|
||||
|
||||
// the Pravetz/Disk II, if in use
|
||||
Apple::DiskII diskii_;
|
||||
Cycles cycles_since_diskii_update_;
|
||||
void flush_diskii() {
|
||||
diskii_.run_for(cycles_since_diskii_update_.flush());
|
||||
}
|
||||
std::vector<uint8_t> pravetz_rom_;
|
||||
std::size_t pravetz_rom_base_pointer_ = 0;
|
||||
bool diskii_is_sleeping_ = false;
|
||||
ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime;
|
||||
|
||||
// Overlay RAM
|
||||
uint16_t ram_top_ = basic_visible_ram_top_;
|
||||
|
@ -1037,7 +1037,7 @@
|
||||
4BB06B211F316A3F00600C7A /* ForceInline.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ForceInline.hpp; sourceTree = "<group>"; };
|
||||
4BB0A6592044FD3000FB3688 /* SN76489.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SN76489.cpp; sourceTree = "<group>"; };
|
||||
4BB0A65A2044FD3000FB3688 /* SN76489.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SN76489.hpp; sourceTree = "<group>"; };
|
||||
4BB146C61F49D7D700253439 /* Sleeper.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sleeper.hpp; sourceTree = "<group>"; };
|
||||
4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ClockingHintSource.hpp; sourceTree = "<group>"; };
|
||||
4BB17D4C1ED7909F00ABD1E1 /* tests.expected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.expected.json; path = FUSE/tests.expected.json; sourceTree = "<group>"; };
|
||||
4BB17D4D1ED7909F00ABD1E1 /* tests.in.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tests.in.json; path = FUSE/tests.in.json; sourceTree = "<group>"; };
|
||||
4BB297E51B587D8300A49093 /* start */ = {isa = PBXFileReference; lastKnownFileType = file; path = " start"; sourceTree = "<group>"; };
|
||||
@ -3124,7 +3124,7 @@
|
||||
children = (
|
||||
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */,
|
||||
4BB06B211F316A3F00600C7A /* ForceInline.hpp */,
|
||||
4BB146C61F49D7D700253439 /* Sleeper.hpp */,
|
||||
4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */,
|
||||
4B449C942063389900A095C8 /* TimeTypes.hpp */,
|
||||
);
|
||||
name = ClockReceiver;
|
||||
|
@ -22,12 +22,12 @@ Controller::Controller(Cycles clock_rate) :
|
||||
set_drive(empty_drive_);
|
||||
}
|
||||
|
||||
void Controller::set_component_is_sleeping(Sleeper *component, bool is_sleeping) {
|
||||
update_sleep_observer();
|
||||
void Controller::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) {
|
||||
update_clocking_observer();
|
||||
}
|
||||
|
||||
bool Controller::is_sleeping() {
|
||||
return !drive_ || drive_->is_sleeping();
|
||||
ClockingHint::Preference Controller::preferred_clocking() {
|
||||
return (!drive_ || (drive_->preferred_clocking() == ClockingHint::Preference::None)) ? ClockingHint::Preference::None : ClockingHint::Preference::RealTime;
|
||||
}
|
||||
|
||||
void Controller::run_for(const Cycles cycles) {
|
||||
@ -77,23 +77,23 @@ void Controller::digital_phase_locked_loop_output_bit(int value) {
|
||||
|
||||
void Controller::set_drive(std::shared_ptr<Drive> drive) {
|
||||
if(drive_ != drive) {
|
||||
bool was_sleeping = is_sleeping();
|
||||
ClockingHint::Preference former_prefernece = preferred_clocking();
|
||||
// invalidate_track();
|
||||
|
||||
if(drive_) {
|
||||
drive_->set_event_delegate(nullptr);
|
||||
drive_->set_sleep_observer(nullptr);
|
||||
drive_->set_clocking_hint_observer(nullptr);
|
||||
}
|
||||
drive_ = drive;
|
||||
if(drive_) {
|
||||
drive_->set_event_delegate(this);
|
||||
drive_->set_sleep_observer(this);
|
||||
drive_->set_clocking_hint_observer(this);
|
||||
} else {
|
||||
drive_ = empty_drive_;
|
||||
}
|
||||
|
||||
if(is_sleeping() != was_sleeping) {
|
||||
update_sleep_observer();
|
||||
if(preferred_clocking() != former_prefernece) {
|
||||
update_clocking_observer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
#include "../Track/PCMPatchedTrack.hpp"
|
||||
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../../ClockReceiver/Sleeper.hpp"
|
||||
#include "../../../ClockReceiver/ClockingHintSource.hpp"
|
||||
|
||||
namespace Storage {
|
||||
namespace Disk {
|
||||
@ -29,7 +29,11 @@ namespace Disk {
|
||||
|
||||
TODO: communication of head size and permissible stepping extents, appropriate simulation of gain.
|
||||
*/
|
||||
class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDelegate, public Sleeper, public Sleeper::SleepObserver {
|
||||
class Controller:
|
||||
public DigitalPhaseLockedLoop::Delegate,
|
||||
public Drive::EventDelegate,
|
||||
public ClockingHint::Source,
|
||||
public ClockingHint::Observer {
|
||||
protected:
|
||||
/*!
|
||||
Constructs a @c Controller that will be run at @c clock_rate.
|
||||
@ -65,7 +69,7 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDe
|
||||
Should be implemented by subclasses if they implement writing; communicates that
|
||||
all bits supplied to write_bit have now been written.
|
||||
*/
|
||||
virtual void process_write_completed();
|
||||
virtual void process_write_completed() override;
|
||||
|
||||
/*!
|
||||
Puts the controller and the drive returned by get_drive() into write mode, supplying to
|
||||
@ -97,9 +101,9 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDe
|
||||
Drive &get_drive();
|
||||
|
||||
/*!
|
||||
As per Sleeper.
|
||||
As per ClockingHint::Source.
|
||||
*/
|
||||
bool is_sleeping();
|
||||
ClockingHint::Preference preferred_clocking() override;
|
||||
|
||||
private:
|
||||
Time bit_length_;
|
||||
@ -113,14 +117,15 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public Drive::EventDe
|
||||
|
||||
std::shared_ptr<Drive> empty_drive_;
|
||||
|
||||
void set_component_is_sleeping(Sleeper *component, bool is_sleeping);
|
||||
// ClockingHint::Observer.
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override;
|
||||
|
||||
// for Drive::EventDelegate
|
||||
void process_event(const Track::Event &event);
|
||||
void advance(const Cycles cycles);
|
||||
void process_event(const Track::Event &event) override;
|
||||
void advance(const Cycles cycles) override ;
|
||||
|
||||
// to satisfy DigitalPhaseLockedLoop::Delegate
|
||||
void digital_phase_locked_loop_output_bit(int value);
|
||||
void digital_phase_locked_loop_output_bit(int value) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -46,15 +46,15 @@ void Drive::set_disk(const std::shared_ptr<Disk> &disk) {
|
||||
has_disk_ = !!disk_;
|
||||
|
||||
invalidate_track();
|
||||
update_sleep_observer();
|
||||
update_clocking_observer();
|
||||
}
|
||||
|
||||
bool Drive::has_disk() {
|
||||
return has_disk_;
|
||||
}
|
||||
|
||||
bool Drive::is_sleeping() {
|
||||
return !motor_is_on_ || !has_disk_;
|
||||
ClockingHint::Preference Drive::preferred_clocking() {
|
||||
return (!motor_is_on_ || !has_disk_) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
|
||||
}
|
||||
|
||||
bool Drive::get_is_track_zero() {
|
||||
@ -119,7 +119,7 @@ void Drive::set_motor_on(bool motor_is_on) {
|
||||
ready_index_count_ = 0;
|
||||
if(disk_) disk_->flush_tracks();
|
||||
}
|
||||
update_sleep_observer();
|
||||
update_clocking_observer();
|
||||
}
|
||||
|
||||
bool Drive::get_motor_on() {
|
||||
|
@ -15,14 +15,14 @@
|
||||
|
||||
#include "../TimedEventLoop.hpp"
|
||||
#include "../../Activity/Observer.hpp"
|
||||
#include "../../ClockReceiver/Sleeper.hpp"
|
||||
#include "../../ClockReceiver/ClockingHintSource.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Storage {
|
||||
namespace Disk {
|
||||
|
||||
class Drive: public Sleeper, public TimedEventLoop {
|
||||
class Drive: public ClockingHint::Source, public TimedEventLoop {
|
||||
public:
|
||||
Drive(unsigned int input_clock_rate, int revolutions_per_minute, int number_of_heads);
|
||||
~Drive();
|
||||
@ -121,7 +121,7 @@ class Drive: public Sleeper, public TimedEventLoop {
|
||||
void set_event_delegate(EventDelegate *);
|
||||
|
||||
// As per Sleeper.
|
||||
bool is_sleeping();
|
||||
ClockingHint::Preference preferred_clocking() override;
|
||||
|
||||
/// Adds an activity observer; it'll be notified of disk activity.
|
||||
/// The caller can specify whether to add an LED based on disk motor.
|
||||
@ -171,9 +171,9 @@ class Drive: public Sleeper, public TimedEventLoop {
|
||||
Time cycles_per_bit_;
|
||||
|
||||
// TimedEventLoop call-ins and state.
|
||||
void process_next_event();
|
||||
void process_next_event() override;
|
||||
void get_next_event(const Time &duration_already_passed);
|
||||
void advance(const Cycles cycles);
|
||||
void advance(const Cycles cycles) override;
|
||||
Track::Event current_event_;
|
||||
|
||||
// Helper for track changes.
|
||||
|
@ -65,15 +65,15 @@ void Tape::set_offset(uint64_t offset) {
|
||||
|
||||
// MARK: - Player
|
||||
|
||||
bool TapePlayer::is_sleeping() {
|
||||
return !tape_ || tape_->is_at_end();
|
||||
ClockingHint::Preference TapePlayer::preferred_clocking() {
|
||||
return (!tape_ || tape_->is_at_end()) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
|
||||
}
|
||||
|
||||
void TapePlayer::set_tape(std::shared_ptr<Storage::Tape::Tape> tape) {
|
||||
tape_ = tape;
|
||||
reset_timer();
|
||||
get_next_pulse();
|
||||
update_sleep_observer();
|
||||
update_clocking_observer();
|
||||
}
|
||||
|
||||
std::shared_ptr<Storage::Tape::Tape> TapePlayer::get_tape() {
|
||||
@ -88,7 +88,7 @@ void TapePlayer::get_next_pulse() {
|
||||
// get the new pulse
|
||||
if(tape_) {
|
||||
current_pulse_ = tape_->get_next_pulse();
|
||||
if(tape_->is_at_end()) update_sleep_observer();
|
||||
if(tape_->is_at_end()) update_clocking_observer();
|
||||
} else {
|
||||
current_pulse_.length.length = 1;
|
||||
current_pulse_.length.clock_rate = 1;
|
||||
@ -119,14 +119,15 @@ BinaryTapePlayer::BinaryTapePlayer(unsigned int input_clock_rate) :
|
||||
TapePlayer(input_clock_rate)
|
||||
{}
|
||||
|
||||
bool BinaryTapePlayer::is_sleeping() {
|
||||
return !motor_is_running_ || TapePlayer::is_sleeping();
|
||||
ClockingHint::Preference BinaryTapePlayer::preferred_clocking() {
|
||||
if(!motor_is_running_) return ClockingHint::Preference::None;
|
||||
return TapePlayer::preferred_clocking();
|
||||
}
|
||||
|
||||
void BinaryTapePlayer::set_motor_control(bool enabled) {
|
||||
if(motor_is_running_ != enabled) {
|
||||
motor_is_running_ = enabled;
|
||||
update_sleep_observer();
|
||||
update_clocking_observer();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
||||
#include <memory>
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../ClockReceiver/Sleeper.hpp"
|
||||
#include "../../ClockReceiver/ClockingHintSource.hpp"
|
||||
|
||||
#include "../TimedEventLoop.hpp"
|
||||
|
||||
@ -95,7 +95,7 @@ class Tape {
|
||||
Will call @c process_input_pulse instantaneously upon reaching *the end* of a pulse. Therefore a subclass
|
||||
can decode pulses into data within process_input_pulse, using the supplied pulse's @c length and @c type.
|
||||
*/
|
||||
class TapePlayer: public TimedEventLoop, public Sleeper {
|
||||
class TapePlayer: public TimedEventLoop, public ClockingHint::Source {
|
||||
public:
|
||||
TapePlayer(unsigned int input_clock_rate);
|
||||
|
||||
@ -107,10 +107,10 @@ class TapePlayer: public TimedEventLoop, public Sleeper {
|
||||
|
||||
void run_for_input_pulse();
|
||||
|
||||
bool is_sleeping();
|
||||
ClockingHint::Preference preferred_clocking() override;
|
||||
|
||||
protected:
|
||||
virtual void process_next_event();
|
||||
virtual void process_next_event() override;
|
||||
virtual void process_input_pulse(const Tape::Pulse &pulse) = 0;
|
||||
|
||||
private:
|
||||
@ -145,11 +145,11 @@ class BinaryTapePlayer: public TapePlayer {
|
||||
};
|
||||
void set_delegate(Delegate *delegate);
|
||||
|
||||
bool is_sleeping();
|
||||
ClockingHint::Preference preferred_clocking() override;
|
||||
|
||||
protected:
|
||||
Delegate *delegate_ = nullptr;
|
||||
virtual void process_input_pulse(const Storage::Tape::Tape::Pulse &pulse);
|
||||
void process_input_pulse(const Storage::Tape::Tape::Pulse &pulse) override;
|
||||
bool input_level_ = false;
|
||||
bool motor_is_running_ = false;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user