1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-25 04:29:09 +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:
Thomas Harte 2017-08-20 12:29:32 -04:00 committed by GitHub
commit 4614a56843
12 changed files with 151 additions and 14 deletions

View File

@ -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
View 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 */

View File

@ -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

View File

@ -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_;

View File

@ -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_;

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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);
};
}

View File

@ -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;
}

View File

@ -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_;

View File

@ -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) {

View File

@ -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);