mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-25 18:30:21 +00:00
230 lines
7.0 KiB
C++
230 lines
7.0 KiB
C++
//
|
|
// Flywheel.hpp
|
|
// Clock Signal
|
|
//
|
|
// Created by Thomas Harte on 11/02/2016.
|
|
// Copyright © 2016 Thomas Harte. All rights reserved.
|
|
//
|
|
|
|
#ifndef Flywheel_hpp
|
|
#define Flywheel_hpp
|
|
|
|
#include <stdlib.h>
|
|
|
|
namespace Outputs {
|
|
namespace CRT {
|
|
|
|
/*!
|
|
Provides timing for a two-phase signal consisting of a retrace phase followed by a scan phase,
|
|
announcing the start and end of retrace and providing the abiliy to read the current
|
|
scanning position.
|
|
|
|
The @c Flywheel will attempt to converge with timing implied by synchronisation pulses.
|
|
*/
|
|
struct Flywheel
|
|
{
|
|
/*!
|
|
Constructs an instance of @c Flywheel.
|
|
|
|
@param standard_period The expected amount of time between one synchronisation and the next.
|
|
@param retrace_time The amount of time it takes to complete a retrace.
|
|
@param sync_error_window The permitted deviation of sync timings from the norm.
|
|
*/
|
|
Flywheel(unsigned int standard_period, unsigned int retrace_time, unsigned int sync_error_window) :
|
|
_standard_period(standard_period),
|
|
_retrace_time(retrace_time),
|
|
_sync_error_window(sync_error_window),
|
|
_counter(0),
|
|
_expected_next_sync(standard_period),
|
|
_counter_before_retrace(standard_period - retrace_time),
|
|
_number_of_surprises(0) {}
|
|
|
|
enum SyncEvent {
|
|
/// Indicates that no synchronisation events will occur in the queried window.
|
|
None,
|
|
/// Indicates that the next synchronisation event will be a transition into retrce.
|
|
StartRetrace,
|
|
/// Indicates that the next synchronisation event will be a transition out of retrace.
|
|
EndRetrace
|
|
};
|
|
/*!
|
|
Asks the flywheel for the first synchronisation event that will occur in a given time period,
|
|
indicating whether a synchronisation request occurred at the start of the query window.
|
|
|
|
@param sync_is_requested @c true indicates that the flywheel should act as though having
|
|
received a synchronisation request now; @c false indicates no such event was detected.
|
|
|
|
@param cycles_to_run_for The number of cycles to look ahead.
|
|
|
|
@param cycles_advanced After this method has completed, contains the amount of time until
|
|
the returned synchronisation event.
|
|
|
|
@returns The next synchronisation event.
|
|
*/
|
|
inline SyncEvent get_next_event_in_period(bool sync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced)
|
|
{
|
|
// do we recognise this hsync, thereby adjusting future time expectations?
|
|
if(sync_is_requested)
|
|
{
|
|
if(_counter < _sync_error_window || _counter > _expected_next_sync - _sync_error_window)
|
|
{
|
|
unsigned int time_now = (_counter < _sync_error_window) ? _expected_next_sync + _counter : _counter;
|
|
_expected_next_sync = (3*_expected_next_sync + time_now) >> 2;
|
|
}
|
|
else
|
|
{
|
|
_number_of_surprises++;
|
|
|
|
if(_counter < _retrace_time + (_expected_next_sync >> 1))
|
|
{
|
|
_expected_next_sync = (3*_expected_next_sync + _standard_period + _sync_error_window) >> 2;
|
|
}
|
|
else
|
|
{
|
|
_expected_next_sync = (3*_expected_next_sync + _standard_period - _sync_error_window) >> 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
SyncEvent proposed_event = SyncEvent::None;
|
|
unsigned int proposed_sync_time = cycles_to_run_for;
|
|
|
|
// will we end an ongoing retrace?
|
|
if(_counter < _retrace_time && _counter + proposed_sync_time >= _retrace_time)
|
|
{
|
|
proposed_sync_time = _retrace_time - _counter;
|
|
proposed_event = SyncEvent::EndRetrace;
|
|
}
|
|
|
|
// will we start a retrace?
|
|
if(_counter + proposed_sync_time >= _expected_next_sync)
|
|
{
|
|
proposed_sync_time = _expected_next_sync - _counter;
|
|
proposed_event = SyncEvent::StartRetrace;
|
|
}
|
|
|
|
*cycles_advanced = proposed_sync_time;
|
|
return proposed_event;
|
|
}
|
|
|
|
/*!
|
|
Advances a nominated amount of time, applying a previously returned synchronisation event
|
|
at the end of that period.
|
|
|
|
@param cycles_advanced The amount of time to run for.
|
|
|
|
@param event The synchronisation event to apply after that period.
|
|
*/
|
|
inline void apply_event(unsigned int cycles_advanced, SyncEvent event)
|
|
{
|
|
_counter += cycles_advanced;
|
|
|
|
switch(event)
|
|
{
|
|
default: return;
|
|
case StartRetrace:
|
|
_counter_before_retrace = _counter - _retrace_time;
|
|
_counter = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
Returns the current output position; while in retrace this will go down towards 0, while in scan
|
|
it will go upward.
|
|
|
|
@returns The current output position.
|
|
*/
|
|
inline unsigned int get_current_output_position()
|
|
{
|
|
if(_counter < _retrace_time)
|
|
{
|
|
unsigned int retrace_distance = (_counter * _standard_period) / _retrace_time;
|
|
if(retrace_distance > _counter_before_retrace) return 0;
|
|
return _counter_before_retrace - retrace_distance;
|
|
}
|
|
|
|
return _counter - _retrace_time;
|
|
}
|
|
|
|
/*!
|
|
@returns the amount of time since retrace last began. Time then counts monotonically up from zero.
|
|
*/
|
|
inline unsigned int get_current_time()
|
|
{
|
|
return _counter;
|
|
}
|
|
|
|
/*!
|
|
@returns whether the output is currently retracing.
|
|
*/
|
|
inline bool is_in_retrace()
|
|
{
|
|
return _counter < _retrace_time;
|
|
}
|
|
|
|
/*!
|
|
@returns the expected length of the scan period (excluding retrace).
|
|
*/
|
|
inline unsigned int get_scan_period()
|
|
{
|
|
return _standard_period - _retrace_time;
|
|
}
|
|
|
|
/*!
|
|
@returns the expected length of a complete scan and retrace cycle.
|
|
*/
|
|
inline unsigned int get_standard_period()
|
|
{
|
|
return _standard_period;
|
|
}
|
|
|
|
/*!
|
|
@returns the number of synchronisation events that have seemed surprising since the last time this method was called;
|
|
a low number indicates good synchronisation.
|
|
*/
|
|
inline unsigned int get_and_reset_number_of_surprises()
|
|
{
|
|
unsigned int result = _number_of_surprises;
|
|
_number_of_surprises = 0;
|
|
return result;
|
|
}
|
|
|
|
/*!
|
|
@returns `true` if a sync is expected soon or the time at which it was expected was recent.
|
|
*/
|
|
inline bool is_near_expected_sync()
|
|
{
|
|
return abs((int)_counter - (int)_expected_next_sync) < (int)_standard_period / 50;
|
|
}
|
|
|
|
private:
|
|
unsigned int _standard_period; // the normal length of time between syncs
|
|
const unsigned int _retrace_time; // a constant indicating the amount of time it takes to perform a retrace
|
|
const unsigned int _sync_error_window; // a constant indicating the window either side of the next expected sync in which we'll accept other syncs
|
|
|
|
unsigned int _counter; // time since the _start_ of the last sync
|
|
unsigned int _counter_before_retrace; // the value of _counter immediately before retrace began
|
|
unsigned int _expected_next_sync; // our current expection of when the next sync will be encountered (which implies velocity)
|
|
|
|
unsigned int _number_of_surprises; // a count of the surprising syncs
|
|
|
|
/*
|
|
Implementation notes:
|
|
|
|
Retrace takes a fixed amount of time and runs during [0, _retrace_time).
|
|
|
|
For the current line, scan then occurs from [_retrace_time, _expected_next_sync), at which point
|
|
retrace begins and the internal counter is reset.
|
|
|
|
All synchronisation events that occur within (-_sync_error_window, _sync_error_window) of the
|
|
expected synchronisation time will cause a proportional adjustment in the expected time for the next
|
|
synchronisation. Other synchronisation events are clamped as though they occurred in that range.
|
|
*/
|
|
};
|
|
|
|
}
|
|
}
|
|
|
|
#endif /* Flywheel_hpp */
|