mirror of
https://github.com/TomHarte/CLK.git
synced 2024-09-08 03:54:27 +00:00
Merge pull request #214 from TomHarte/Sleeper
Experimentally introduces the concept of a 'sleeper' — a component that will volunteer to be unclocked for a period
This commit is contained in:
commit
4614a56843
@ -6,8 +6,8 @@
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ForceInline_h
|
||||
#define ForceInline_h
|
||||
#ifndef ForceInline_hpp
|
||||
#define ForceInline_hpp
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define forceinline __attribute__((always_inline)) inline
|
60
ClockReceiver/Sleeper.hpp
Normal file
60
ClockReceiver/Sleeper.hpp
Normal file
@ -0,0 +1,60 @@
|
||||
//
|
||||
// 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(void *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 */
|
@ -88,6 +88,10 @@ i8272::i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multipli
|
||||
posit_event((int)Event8272::CommandByte);
|
||||
}
|
||||
|
||||
bool i8272::is_sleeping() {
|
||||
return is_sleeping_ && Storage::Disk::MFMController::is_sleeping();
|
||||
}
|
||||
|
||||
void i8272::run_for(Cycles cycles) {
|
||||
Storage::Disk::MFMController::run_for(cycles);
|
||||
|
||||
@ -154,6 +158,7 @@ void i8272::run_for(Cycles cycles) {
|
||||
}
|
||||
|
||||
is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_;
|
||||
if(is_sleeping_) update_sleep_observer();
|
||||
}
|
||||
|
||||
void i8272::set_register(int address, uint8_t value) {
|
||||
@ -198,7 +203,7 @@ void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
|
||||
#define MS_TO_CYCLES(x) x * 8000
|
||||
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = (int)mask; return; case __LINE__:
|
||||
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = (int)Event8272::Timer; delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; case __LINE__: if(delay_time_) return;
|
||||
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = (int)Event8272::Timer; delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_sleep_observer(); case __LINE__: if(delay_time_) return;
|
||||
|
||||
#define PASTE(x, y) x##y
|
||||
#define CONCAT(x, y) PASTE(x, y)
|
||||
@ -257,6 +262,7 @@ void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
if(drives_[active_drive_].head_unload_delay[active_head_] == 0) { \
|
||||
head_timers_running_++; \
|
||||
is_sleeping_ = false; \
|
||||
update_sleep_observer(); \
|
||||
} \
|
||||
drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\
|
||||
}
|
||||
@ -711,6 +717,7 @@ void i8272::posit_event(int event_type) {
|
||||
if(drives_[drive].phase != Drive::Seeking) {
|
||||
drives_seeking_++;
|
||||
is_sleeping_ = false;
|
||||
update_sleep_observer();
|
||||
}
|
||||
|
||||
// Set currently seeking, with a step to occur right now (yes, it sounds like jamming these
|
||||
|
@ -41,6 +41,8 @@ class i8272: public Storage::Disk::MFMController {
|
||||
|
||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
|
||||
|
||||
bool is_sleeping();
|
||||
|
||||
private:
|
||||
// The bus handler, for interrupt and DMA-driven usage.
|
||||
BusHandler &bus_handler_;
|
||||
|
@ -76,7 +76,12 @@ class InterruptTimer {
|
||||
|
||||
/// @returns @c true if an interrupt is currently requested; @c false otherwise.
|
||||
inline bool get_request() {
|
||||
return interrupt_request_;
|
||||
return last_interrupt_request_ = interrupt_request_;
|
||||
}
|
||||
|
||||
/// Asks whether the interrupt status has changed.
|
||||
inline bool request_has_changed() {
|
||||
return last_interrupt_request_ != interrupt_request_;
|
||||
}
|
||||
|
||||
/// Resets the timer.
|
||||
@ -88,6 +93,7 @@ class InterruptTimer {
|
||||
private:
|
||||
int reset_counter_;
|
||||
bool interrupt_request_;
|
||||
bool last_interrupt_request_;
|
||||
int timer_;
|
||||
};
|
||||
|
||||
@ -659,6 +665,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
|
||||
class ConcreteMachine:
|
||||
public Utility::TypeRecipient,
|
||||
public CPU::Z80::BusHandler,
|
||||
public Sleeper::SleepObserver,
|
||||
public Machine {
|
||||
public:
|
||||
ConcreteMachine() :
|
||||
@ -674,6 +681,13 @@ class ConcreteMachine:
|
||||
|
||||
// ensure memory starts in a random state
|
||||
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();
|
||||
}
|
||||
|
||||
/// The entry point for performing a partial Z80 machine cycle.
|
||||
@ -689,17 +703,17 @@ class ConcreteMachine:
|
||||
crtc_counter_ += cycle.length;
|
||||
Cycles crtc_cycles = crtc_counter_.divide_cycles(Cycles(4));
|
||||
if(crtc_cycles > Cycles(0)) crtc_.run_for(crtc_cycles);
|
||||
z80_.set_interrupt_line(interrupt_timer_.get_request());
|
||||
if(interrupt_timer_.request_has_changed()) z80_.set_interrupt_line(interrupt_timer_.get_request());
|
||||
|
||||
// TODO (in the player, not here): adapt it to accept an input clock rate and
|
||||
// run_for as HalfCycles
|
||||
tape_player_.run_for(cycle.length.as_int());
|
||||
if(!tape_player_is_sleeping_) tape_player_.run_for(cycle.length.as_int());
|
||||
|
||||
// Pump the AY
|
||||
ay_.run_for(cycle.length);
|
||||
|
||||
// Clock the FDC, if connected, using a lazy scale by two
|
||||
if(has_fdc_) fdc_.run_for(Cycles(cycle.length.as_int()));
|
||||
if(has_fdc_ && !fdc_is_sleeping_) fdc_.run_for(Cycles(cycle.length.as_int()));
|
||||
|
||||
// Update typing activity
|
||||
if(typer_) typer_->run_for(cycle.length);
|
||||
@ -902,6 +916,11 @@ class ConcreteMachine:
|
||||
roms_[(int)type] = data;
|
||||
}
|
||||
|
||||
void set_component_is_sleeping(void *component, bool is_sleeping) {
|
||||
if(component == &fdc_) fdc_is_sleeping_ = is_sleeping;
|
||||
else tape_player_is_sleeping_ = is_sleeping;
|
||||
}
|
||||
|
||||
#pragma mark - Keyboard
|
||||
|
||||
void set_typer_for_string(const char *string) {
|
||||
@ -995,7 +1014,8 @@ class ConcreteMachine:
|
||||
|
||||
std::vector<uint8_t> roms_[7];
|
||||
int rom_model_;
|
||||
bool has_fdc_;
|
||||
bool has_fdc_, fdc_is_sleeping_;
|
||||
bool tape_player_is_sleeping_;
|
||||
bool has_128k_;
|
||||
bool upper_rom_is_paged_;
|
||||
int upper_rom_;
|
||||
|
@ -677,7 +677,8 @@
|
||||
4BACC5B01F3DFF7C0037C015 /* CharacterMapper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CharacterMapper.hpp; path = AmstradCPC/CharacterMapper.hpp; sourceTree = "<group>"; };
|
||||
4BAD9B941F43D7E900724854 /* UnformattedTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UnformattedTrack.cpp; sourceTree = "<group>"; };
|
||||
4BAD9B951F43D7E900724854 /* UnformattedTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = UnformattedTrack.hpp; sourceTree = "<group>"; };
|
||||
4BB06B211F316A3F00600C7A /* ForceInline.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ForceInline.h; sourceTree = "<group>"; };
|
||||
4BB06B211F316A3F00600C7A /* ForceInline.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ForceInline.hpp; sourceTree = "<group>"; };
|
||||
4BB146C61F49D7D700253439 /* Sleeper.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sleeper.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>"; };
|
||||
@ -2279,7 +2280,8 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */,
|
||||
4BB06B211F316A3F00600C7A /* ForceInline.h */,
|
||||
4BB06B211F316A3F00600C7A /* ForceInline.hpp */,
|
||||
4BB146C61F49D7D700253439 /* Sleeper.hpp */,
|
||||
);
|
||||
name = ClockReceiver;
|
||||
path = ../../ClockReceiver;
|
||||
|
@ -46,6 +46,14 @@ void Controller::setup_track() {
|
||||
get_next_event(offset);
|
||||
}
|
||||
|
||||
void Controller::set_component_is_sleeping(void *component, bool is_sleeping) {
|
||||
update_sleep_observer();
|
||||
}
|
||||
|
||||
bool Controller::is_sleeping() {
|
||||
return !(drive_ && drive_->has_disk() && motor_is_on_);
|
||||
}
|
||||
|
||||
void Controller::run_for(const Cycles cycles) {
|
||||
Time zero(0);
|
||||
|
||||
@ -208,6 +216,7 @@ void Controller::step(int direction) {
|
||||
|
||||
void Controller::set_motor_on(bool motor_on) {
|
||||
motor_is_on_ = motor_on;
|
||||
update_sleep_observer();
|
||||
}
|
||||
|
||||
bool Controller::get_motor_on() {
|
||||
@ -218,6 +227,8 @@ void Controller::set_drive(std::shared_ptr<Drive> drive) {
|
||||
if(drive_ != drive) {
|
||||
invalidate_track();
|
||||
drive_ = drive;
|
||||
drive->set_sleep_observer(this);
|
||||
update_sleep_observer();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,9 @@
|
||||
#include "PCMSegment.hpp"
|
||||
#include "PCMPatchedTrack.hpp"
|
||||
#include "../TimedEventLoop.hpp"
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../ClockReceiver/Sleeper.hpp"
|
||||
|
||||
namespace Storage {
|
||||
namespace Disk {
|
||||
@ -28,7 +30,7 @@ namespace Disk {
|
||||
|
||||
TODO: communication of head size and permissible stepping extents, appropriate simulation of gain.
|
||||
*/
|
||||
class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop {
|
||||
class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop, public Sleeper, public Sleeper::SleepObserver {
|
||||
protected:
|
||||
/*!
|
||||
Constructs a @c DiskDrive that will be run at @c clock_rate and runs its PLL at @c clock_rate*clock_rate_multiplier,
|
||||
@ -116,6 +118,8 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop
|
||||
virtual bool get_drive_is_ready();
|
||||
bool get_drive_is_read_only();
|
||||
|
||||
bool is_sleeping();
|
||||
|
||||
private:
|
||||
Time bit_length_;
|
||||
int clock_rate_;
|
||||
@ -142,6 +146,8 @@ class Controller: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop
|
||||
|
||||
void setup_track();
|
||||
Time get_time_into_track();
|
||||
|
||||
void set_component_is_sleeping(void *component, bool is_sleeping);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -18,18 +18,24 @@ void Drive::set_disk(const std::shared_ptr<Disk> &disk) {
|
||||
disk_ = disk;
|
||||
track_ = nullptr;
|
||||
has_disk_ = !!disk_;
|
||||
update_sleep_observer();
|
||||
}
|
||||
|
||||
void Drive::set_disk_with_track(const std::shared_ptr<Track> &track) {
|
||||
disk_ = nullptr;
|
||||
track_ = track;
|
||||
has_disk_ = !!track_;
|
||||
update_sleep_observer();
|
||||
}
|
||||
|
||||
bool Drive::has_disk() {
|
||||
return has_disk_;
|
||||
}
|
||||
|
||||
bool Drive::is_sleeping() {
|
||||
return !has_disk_;
|
||||
}
|
||||
|
||||
bool Drive::get_is_track_zero() {
|
||||
return head_position_ == 0;
|
||||
}
|
||||
|
@ -10,12 +10,14 @@
|
||||
#define Drive_hpp
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "Disk.hpp"
|
||||
#include "../../ClockReceiver/Sleeper.hpp"
|
||||
|
||||
namespace Storage {
|
||||
namespace Disk {
|
||||
|
||||
class Drive {
|
||||
class Drive: public Sleeper {
|
||||
public:
|
||||
Drive();
|
||||
|
||||
@ -70,6 +72,9 @@ class Drive {
|
||||
*/
|
||||
bool get_is_ready();
|
||||
|
||||
// As per Sleeper.
|
||||
bool is_sleeping();
|
||||
|
||||
private:
|
||||
std::shared_ptr<Track> track_;
|
||||
std::shared_ptr<Disk> disk_;
|
||||
|
@ -65,10 +65,15 @@ void Tape::set_offset(uint64_t offset) {
|
||||
|
||||
#pragma mark - Player
|
||||
|
||||
bool TapePlayer::is_sleeping() {
|
||||
return !tape_ || tape_->is_at_end();
|
||||
}
|
||||
|
||||
void TapePlayer::set_tape(std::shared_ptr<Storage::Tape::Tape> tape) {
|
||||
tape_ = tape;
|
||||
reset_timer();
|
||||
get_next_pulse();
|
||||
update_sleep_observer();
|
||||
}
|
||||
|
||||
std::shared_ptr<Storage::Tape::Tape> TapePlayer::get_tape() {
|
||||
@ -81,8 +86,10 @@ bool TapePlayer::has_tape() {
|
||||
|
||||
void TapePlayer::get_next_pulse() {
|
||||
// get the new pulse
|
||||
if(tape_)
|
||||
if(tape_) {
|
||||
current_pulse_ = tape_->get_next_pulse();
|
||||
if(tape_->is_at_end()) update_sleep_observer();
|
||||
}
|
||||
else {
|
||||
current_pulse_.length.length = 1;
|
||||
current_pulse_.length.clock_rate = 1;
|
||||
@ -113,8 +120,13 @@ BinaryTapePlayer::BinaryTapePlayer(unsigned int input_clock_rate) :
|
||||
TapePlayer(input_clock_rate), motor_is_running_(false), input_level_(false), delegate_(nullptr)
|
||||
{}
|
||||
|
||||
bool BinaryTapePlayer::is_sleeping() {
|
||||
return !motor_is_running_ || TapePlayer::is_sleeping();
|
||||
}
|
||||
|
||||
void BinaryTapePlayer::set_motor_control(bool enabled) {
|
||||
motor_is_running_ = enabled;
|
||||
update_sleep_observer();
|
||||
}
|
||||
|
||||
void BinaryTapePlayer::set_tape_output(bool set) {
|
||||
|
@ -12,6 +12,8 @@
|
||||
#include <memory>
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../ClockReceiver/Sleeper.hpp"
|
||||
|
||||
#include "../TimedEventLoop.hpp"
|
||||
|
||||
namespace Storage {
|
||||
@ -93,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 {
|
||||
class TapePlayer: public TimedEventLoop, public Sleeper {
|
||||
public:
|
||||
TapePlayer(unsigned int input_clock_rate);
|
||||
|
||||
@ -105,6 +107,8 @@ class TapePlayer: public TimedEventLoop {
|
||||
|
||||
void run_for_input_pulse();
|
||||
|
||||
bool is_sleeping();
|
||||
|
||||
protected:
|
||||
virtual void process_next_event();
|
||||
virtual void process_input_pulse(const Tape::Pulse &pulse) = 0;
|
||||
@ -139,6 +143,8 @@ class BinaryTapePlayer: public TapePlayer {
|
||||
};
|
||||
void set_delegate(Delegate *delegate);
|
||||
|
||||
bool is_sleeping();
|
||||
|
||||
protected:
|
||||
Delegate *delegate_;
|
||||
virtual void process_input_pulse(const Storage::Tape::Tape::Pulse &pulse);
|
||||
|
Loading…
Reference in New Issue
Block a user