// // DigitalPhaseLockedLoop.hpp // Clock Signal // // Created by Thomas Harte on 11/07/2016. // Copyright 2016 Thomas Harte. All rights reserved. // #ifndef DigitalPhaseLockedLoop_hpp #define DigitalPhaseLockedLoop_hpp #include #include #include #include #include "../../../ClockReceiver/ClockReceiver.hpp" namespace Storage { /*! Template parameters: @c bit_handler A class that must implement a method, digital_phase_locked_loop_output_bit(int) for receving bits from the DPLL. @c length_of_history The number of historic pulses to consider in locking to phase. */ template class DigitalPhaseLockedLoop { public: /*! Instantiates a @c DigitalPhaseLockedLoop. @param clocks_per_bit The expected number of cycles between each bit of input. */ DigitalPhaseLockedLoop(int clocks_per_bit, BitHandler &handler) : bit_handler_(handler), window_length_(clocks_per_bit), clocks_per_bit_(clocks_per_bit) {} /*! Changes the expected window length. */ void set_clocks_per_bit(int clocks_per_bit) { window_length_ = clocks_per_bit_ = clocks_per_bit; } /*! Runs the loop, impliedly posting no pulses during that period. @c number_of_cycles The time to run the loop for. */ void run_for(const Cycles cycles) { offset_ += cycles.as_integral(); phase_ += cycles.as_integral(); if(phase_ >= window_length_) { auto windows_crossed = phase_ / window_length_; // Check whether this triggers any 0s. if(window_was_filled_) --windows_crossed; for(int c = 0; c < windows_crossed; c++) bit_handler_.digital_phase_locked_loop_output_bit(0); window_was_filled_ = false; phase_ %= window_length_; } } /*! Announces a pulse at the current time. */ void add_pulse() { if(!window_was_filled_) { bit_handler_.digital_phase_locked_loop_output_bit(1); window_was_filled_ = true; post_phase_offset(phase_, offset_); offset_ = 0; } } private: BitHandler &bit_handler_; void post_phase_offset(Cycles::IntType new_phase, Cycles::IntType new_offset) { // Erase the effect of whatever is currently in this slot. total_divisor_ -= offset_history_[offset_history_pointer_].divisor; total_spacing_ -= offset_history_[offset_history_pointer_].spacing; // Fill in the new fields. const auto multiple = std::max((new_offset + (clocks_per_bit_ >> 1)) / clocks_per_bit_, Cycles::IntType(1)); offset_history_[offset_history_pointer_].divisor = multiple; offset_history_[offset_history_pointer_].spacing = new_offset; // Add in the new values; total_divisor_ += offset_history_[offset_history_pointer_].divisor; total_spacing_ += offset_history_[offset_history_pointer_].spacing; // Advance the write slot. offset_history_pointer_ = (offset_history_pointer_ + 1) % offset_history_.size(); #ifndef NDEBUG Cycles::IntType td = 0, ts = 0; for(auto offset: offset_history_) { td += offset.divisor; ts += offset.spacing; } assert(ts == total_spacing_); assert(td == total_divisor_); #endif // In net: use an unweighted average of the stored offsets to compute current window size, // bucketing them by rounding to the nearest multiple of the base clocks per bit window_length_ = std::max(total_spacing_ / total_divisor_, Cycles::IntType(1)); // Also apply a difference to phase, use a simple spring mechanism as a lowpass filter. const auto error = new_phase - (window_length_ >> 1); phase_ -= (error + 1) >> 1; } struct LoggedOffset { Cycles::IntType divisor = 1, spacing = 1; }; std::array offset_history_; std::size_t offset_history_pointer_ = 0; Cycles::IntType total_spacing_ = length_of_history; Cycles::IntType total_divisor_ = length_of_history; Cycles::IntType phase_ = 0; Cycles::IntType window_length_ = 0; Cycles::IntType offset_ = 0; bool window_was_filled_ = false; int clocks_per_bit_ = 0; }; } #endif /* DigitalPhaseLockedLoop_hpp */