From bd611224ba4c2356c1ad4c3359781682c5c121da Mon Sep 17 00:00:00 2001
From: Andy Walls <awalls.cx18@gmail.com>
Date: Mon, 17 Jul 2017 16:11:21 -0700
Subject: gr-filter, digital: Add symbol_sync_{cc|ff} and auxiliary classes

---
 gr-digital/lib/symbol_sync_cc_impl.cc | 650 ++++++++++++++++++++++++++++++++++
 1 file changed, 650 insertions(+)
 create mode 100644 gr-digital/lib/symbol_sync_cc_impl.cc

(limited to 'gr-digital/lib/symbol_sync_cc_impl.cc')

diff --git a/gr-digital/lib/symbol_sync_cc_impl.cc b/gr-digital/lib/symbol_sync_cc_impl.cc
new file mode 100644
index 0000000000..3df84be134
--- /dev/null
+++ b/gr-digital/lib/symbol_sync_cc_impl.cc
@@ -0,0 +1,650 @@
+/* -*- c++ -*- */
+/*
+ * Copyright (C) 2017 Free Software Foundation, Inc.
+ *
+ * This file is part of GNU Radio
+ *
+ * GNU Radio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3, or (at your option)
+ * any later version.
+ *
+ * GNU Radio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Radio; see the file COPYING.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "symbol_sync_cc_impl.h"
+#include <boost/math/common_factor.hpp>
+#include <gnuradio/io_signature.h>
+#include <gnuradio/math.h>
+#include <stdexcept>
+
+namespace gr {
+  namespace digital {
+
+    symbol_sync_cc::sptr
+    symbol_sync_cc::make(enum ted_type detector_type,
+                         float sps,
+                         float loop_bw,
+                         float damping_factor,
+                         float max_deviation,
+                         int osps,
+                         constellation_sptr slicer,
+                         ir_type interp_type,
+                         int n_filters,
+                         const std::vector<float> &taps)
+    {
+      return gnuradio::get_initial_sptr
+        (new symbol_sync_cc_impl(detector_type,
+                                 sps,
+                                 loop_bw,
+                                 damping_factor,
+                                 max_deviation,
+                                 osps,
+                                 slicer,
+                                 interp_type,
+                                 n_filters,
+                                 taps));
+    }
+
+    symbol_sync_cc_impl::symbol_sync_cc_impl(
+                           enum ted_type detector_type,
+                           float sps,
+                           float loop_bw,
+                           float damping_factor,
+                           float max_deviation,
+                           int osps,
+                           constellation_sptr slicer,
+                           ir_type interp_type,
+                           int n_filters,
+                           const std::vector<float> &taps)
+      : block("symbol_sync_cc",
+              io_signature::make(1, 1, sizeof(gr_complex)),
+              io_signature::makev(1, 4, std::vector<int>(4, sizeof(float)))),
+        d_ted(NULL),
+        d_interp(NULL),
+        d_inst_output_period(sps / static_cast<float>(osps)),
+        d_inst_clock_period(sps),
+        d_avg_clock_period(sps),
+        d_osps(static_cast<float>(osps)),
+        d_osps_n(osps),
+        d_tags(),
+        d_new_tags(),
+        d_time_est_key(pmt::intern("time_est")),
+        d_clock_est_key(pmt::intern("clock_est")),
+        d_noutputs(1),
+        d_out_error(NULL),
+        d_out_instantaneous_clock_period(NULL),
+        d_out_average_clock_period(NULL)
+    {
+      // Brute force fix of the output io_signature, because I can't get
+      // an anonymous std::vector<int>() rvalue, with a const expression
+      // initializing the vector, to work.  Lvalues seem to make everything
+      // better.
+      int output_io_sizes[4] = {
+          sizeof(gr_complex),
+          sizeof(float), sizeof(float), sizeof(float)
+      };
+      std::vector<int> output_io_sizes_vector(&output_io_sizes[0],
+                                              &output_io_sizes[4]);
+      set_output_signature(io_signature::makev(1, 4, output_io_sizes_vector));
+
+      if (sps <= 1.0f)
+        throw std::out_of_range("nominal samples per symbol must be > 1");
+
+      if (osps < 1)
+        throw std::out_of_range("output samples per symbol must be > 0");
+
+      // Timing Error Detector
+      d_ted = timing_error_detector::make(detector_type, slicer);
+      if (d_ted == NULL)
+        throw std::runtime_error("unable to create timing_error_detector");
+
+      // Interpolating Resampler
+      d_interp = interpolating_resampler_ccf::make(interp_type,
+                                                   d_ted->needs_derivative(),
+                                                   n_filters, taps);
+      if (d_interp == NULL)
+       throw std::runtime_error("unable to create interpolating_resampler_ccf");
+
+      // Block Internal Clocks
+      d_interps_per_symbol_n = boost::math::lcm(d_ted->inputs_per_symbol(),
+                                                d_osps_n);
+      d_interps_per_ted_input_n =
+                            d_interps_per_symbol_n / d_ted->inputs_per_symbol();
+      d_interps_per_output_sample_n =
+                            d_interps_per_symbol_n / d_osps_n;
+
+      d_interps_per_symbol = static_cast<float>(d_interps_per_symbol_n);
+      d_interps_per_ted_input = static_cast<float>(d_interps_per_ted_input_n);
+
+      d_interp_clock = d_interps_per_symbol_n - 1;
+      sync_reset_internal_clocks();
+      d_inst_interp_period = d_inst_clock_period / d_interps_per_symbol;
+
+      if (d_interps_per_symbol > sps)
+          GR_LOG_WARN(d_logger,
+                      boost::format("block performing more interopolations per "
+                                    "symbol (%3f) than input samples per symbol"
+                                    "(%3f). Consider reducing osps or "
+                                    "increasing sps") % d_interps_per_symbol
+                                                      % sps);
+
+      // Symbol Clock Tracking and Estimation
+      d_clock = new clock_tracking_loop(loop_bw,
+                                        sps + max_deviation,
+                                        sps - max_deviation,
+                                        sps,
+                                        damping_factor);
+
+      // Timing Error Detector
+      d_ted->sync_reset();
+
+      // Interpolating Resampler
+      d_interp->sync_reset(sps);
+
+      // Tag Propagation and Clock Tracking Reset/Resync
+      set_relative_rate (d_osps / sps);
+      set_tag_propagation_policy(TPP_DONT);
+      d_filter_delay = (d_interp->ntaps() + 1) / 2;
+
+      set_output_multiple(d_osps_n);
+    }
+
+    symbol_sync_cc_impl::~symbol_sync_cc_impl()
+    {
+      delete d_ted;
+      delete d_interp;
+      delete d_clock;
+    }
+
+    //
+    // Block Internal Clocks
+    //
+    void
+    symbol_sync_cc_impl::update_internal_clock_outputs()
+    {
+          // a d_interp_clock boolean output would always be true.
+          d_ted_input_clock = (d_interp_clock % d_interps_per_ted_input_n == 0);
+          d_output_sample_clock =
+                          (d_interp_clock % d_interps_per_output_sample_n == 0);
+          d_symbol_clock = (d_interp_clock % d_interps_per_symbol_n == 0);
+    }
+
+    void
+    symbol_sync_cc_impl::advance_internal_clocks()
+    {
+          d_interp_clock = (d_interp_clock + 1) % d_interps_per_symbol_n;
+          update_internal_clock_outputs();
+    }
+
+    void
+    symbol_sync_cc_impl::revert_internal_clocks()
+    {
+          if (d_interp_clock == 0)
+              d_interp_clock = d_interps_per_symbol_n - 1;
+          else
+              d_interp_clock--;
+          update_internal_clock_outputs();
+    }
+
+    void
+    symbol_sync_cc_impl::sync_reset_internal_clocks()
+    {
+          d_interp_clock = d_interps_per_symbol_n - 1;
+          update_internal_clock_outputs();
+    }
+
+    //
+    // Tag Propagation and Clock Tracking Reset/Resync
+    //
+    void
+    symbol_sync_cc_impl::collect_tags(uint64_t nitems_rd, int count)
+    {
+        // Get all the tags in offset order
+        // d_new_tags is used to look for time_est and clock_est tags.
+        // d_tags is used for manual tag propagation.
+        d_new_tags.clear();
+        get_tags_in_range(d_new_tags, 0, nitems_rd, nitems_rd + count);
+        std::sort(d_new_tags.begin(), d_new_tags.end(), tag_t::offset_compare);
+        d_tags.insert(d_tags.end(), d_new_tags.begin(), d_new_tags.end());
+        std::sort(d_tags.begin(), d_tags.end(), tag_t::offset_compare);
+    }
+
+    bool
+    symbol_sync_cc_impl::find_sync_tag(uint64_t nitems_rd, int iidx,
+                                       int distance,
+                                       uint64_t &tag_offset,
+                                       float &timing_offset,
+                                       float &clock_period)
+    {
+        bool found;
+        uint64_t soffset, eoffset;
+        std::vector<tag_t>::iterator t;
+        std::vector<tag_t>::iterator t2;
+
+        // PLL Reset/Resynchronization to time_est & clock_est tags
+        //
+        // Look for a time_est tag between the current interpolated input sample
+        // and the next predicted interpolated input sample. (both rounded up)
+        soffset = nitems_rd + d_filter_delay + static_cast<uint64_t>(iidx + 1);
+        eoffset = soffset + distance;
+        found = false;
+        for (t = d_new_tags.begin();
+             t != d_new_tags.end();
+             t = d_new_tags.erase(t)) {
+
+            if (t->offset > eoffset) // search finished
+                break;
+
+            if (t->offset < soffset) // tag is in the past of what we care about
+                continue;
+
+            if (not pmt::eq(t->key, d_time_est_key) and  // not a time_est tag
+                not pmt::eq(t->key, d_clock_est_key)   ) // not a clock_est tag
+                continue;
+
+            found = true;
+            tag_offset = t->offset;
+            if (pmt::eq(t->key, d_time_est_key)) {
+                // got a time_est tag
+                timing_offset = static_cast<float>(pmt::to_double(t->value));
+                // next instantaneous clock period estimate will be nominal
+                clock_period = d_clock->get_nom_avg_period();
+
+                // Look for a clock_est tag at the same offset,
+                // as we prefer clock_est tags
+                for (t2 = ++t; t2 != d_new_tags.end(); ++t2) {
+                    if (t2->offset > t->offset) // search finished
+                        break;
+                    if (not pmt::eq(t->key, d_clock_est_key)) // not a clock_est
+                        continue;
+                    // Found a clock_est tag at the same offset
+                    tag_offset = t2->offset;
+                    timing_offset = static_cast<float>(
+                                  pmt::to_double(pmt::tuple_ref(t2->value, 0)));
+                    clock_period = static_cast<float>(
+                                  pmt::to_double(pmt::tuple_ref(t2->value, 1)));
+                    break;
+                }
+            } else {
+                // got a clock_est tag
+                timing_offset = static_cast<float>(
+                                   pmt::to_double(pmt::tuple_ref(t->value, 0)));
+                clock_period = static_cast<float>(
+                                   pmt::to_double(pmt::tuple_ref(t->value, 1)));
+            }
+
+            if (not(timing_offset >= -1.0f and timing_offset <= 1.0f)) {
+                // the time_est/clock_est tag's payload is invalid
+                GR_LOG_WARN(d_logger,
+                            boost::format("ignoring time_est/clock_est tag with"
+                                          " value %.2f, outside of allowed "
+                                          "range [-1.0, 1.0]") % timing_offset);
+                found = false;
+                continue;
+            }
+
+            if (t->offset == soffset and timing_offset < 0.0f) {
+                // already handled times earlier than this previously
+                found = false;
+                continue;
+            }
+
+            if (t->offset == eoffset and timing_offset >= 0.0f) {
+                // handle times greater than this later
+                found = false;
+                break;
+            }
+
+            if (found == true)
+                break;
+        }
+        return found;
+    }
+
+    void
+    symbol_sync_cc_impl::propagate_tags(uint64_t nitems_rd, int iidx,
+                                        float iidx_fraction,
+                                        float inst_output_period,
+                                        uint64_t nitems_wr, int oidx)
+    {
+        // Tag Propagation
+        //
+        // Onto this output sample, place all the remaining tags that
+        // came before the interpolated input sample, and all the tags
+        // on and after the interpolated input sample, up to half way to
+        // the next output sample.
+
+        uint64_t mid_period_offset = nitems_rd + d_filter_delay
+                   + static_cast<uint64_t>(iidx)
+                   + static_cast<uint64_t>(llroundf(iidx_fraction
+                                                    + inst_output_period/2.0f));
+
+        uint64_t output_offset = nitems_wr + static_cast<uint64_t>(oidx);
+
+        int i;
+        std::vector<tag_t>::iterator t;
+        for (t = d_tags.begin();
+             t != d_tags.end() and t->offset <= mid_period_offset;
+             t = d_tags.erase(t)) {
+            t->offset = output_offset;
+            for (i = 0; i < d_noutputs; i++)
+                add_item_tag(i, *t);
+        }
+    }
+
+    void
+    symbol_sync_cc_impl::save_expiring_tags(uint64_t nitems_rd,
+                                            int consumed)
+    {
+        // Deferred Tag Propagation
+        //
+        // Only save away input tags that will not be available
+        // in the next call to general_work().  Otherwise we would
+        // create duplicate tags next time around.
+        // Tags that have already been propagated, have already been erased
+        // from d_tags.
+
+        uint64_t consumed_offset = nitems_rd + static_cast<uint64_t>(consumed);
+        std::vector<tag_t>::iterator t;
+
+        for (t = d_tags.begin(); t != d_tags.end(); ) {
+            if (t->offset < consumed_offset)
+                ++t;
+            else
+                t = d_tags.erase(t);
+        }
+    }
+
+    //
+    // Optional Diagnostic Outputs
+    //
+    void
+    symbol_sync_cc_impl::setup_optional_outputs(
+                                              gr_vector_void_star &output_items)
+    {
+        d_noutputs = output_items.size();
+        d_out_error = NULL;
+        d_out_instantaneous_clock_period = NULL;
+        d_out_average_clock_period = NULL;
+
+        if (d_noutputs < 2)
+            return;
+        d_out_error = (float *) output_items[1];
+
+        if (d_noutputs < 3)
+            return;
+        d_out_instantaneous_clock_period = (float *) output_items[2];
+
+        if (d_noutputs < 4)
+            return;
+        d_out_average_clock_period = (float *) output_items[3];
+    }
+
+    void
+    symbol_sync_cc_impl::emit_optional_output(int oidx,
+                                              float error,
+                                              float inst_clock_period,
+                                              float avg_clock_period)
+    {
+        if (d_noutputs < 2)
+            return;
+        d_out_error[oidx] = error;
+
+        if (d_noutputs < 3)
+            return;
+        d_out_instantaneous_clock_period[oidx] = inst_clock_period;
+
+        if (d_noutputs < 4)
+            return;
+        d_out_average_clock_period[oidx] = avg_clock_period;
+    }
+
+    void
+    symbol_sync_cc_impl::forecast(int noutput_items,
+                                  gr_vector_int &ninput_items_required)
+    {
+        unsigned ninputs = ninput_items_required.size();
+
+        // The '+ 2' in the expression below is an effort to always have at
+        // least one output sample, even if the main loop decides it has to
+        // revert one computed sample and wait for the next call to
+        // general_work().
+        // The d_clock->get_max_avg_period() is also an effort to do the same,
+        // in case we have the worst case allowable clock timing deviation on
+        // input.
+        int answer = static_cast<int>(
+                                ceilf(static_cast<float>(noutput_items + 2)
+                                      * d_clock->get_max_avg_period() / d_osps))
+                     + static_cast<int>(d_interp->ntaps());
+
+        for(unsigned i = 0; i < ninputs; i++)
+            ninput_items_required[i] = answer;
+    }
+
+    int
+    symbol_sync_cc_impl::general_work(int noutput_items,
+                                      gr_vector_int &ninput_items,
+                                      gr_vector_const_void_star &input_items,
+                                      gr_vector_void_star &output_items)
+    {
+      // max input to consume
+      int ni = ninput_items[0] - static_cast<int>(d_interp->ntaps());
+      if (ni <= 0)
+          return 0;
+
+      const gr_complex *in = (const gr_complex *)input_items[0];
+      gr_complex *out = (gr_complex *)output_items[0];
+
+      setup_optional_outputs(output_items);
+
+      int ii = 0; // input index
+      int oo = 0; // output index
+      gr_complex interp_output;
+      gr_complex interp_derivative = gr_complex(0.0f, 0.0f);
+      float error;
+      float look_ahead_phase = 0.0f;
+      int look_ahead_phase_n = 0;
+      float look_ahead_phase_wrapped = 0.0f;
+
+      uint64_t nitems_rd = nitems_read(0);
+      uint64_t nitems_wr = nitems_written(0);
+      uint64_t sync_tag_offset;
+      float sync_timing_offset;
+      float sync_clock_period;
+
+      // Tag Propagation and Symbol Clock Tracking Reset/Resync
+      collect_tags(nitems_rd, ni);
+
+      while (oo < noutput_items) {
+        // Block Internal Clocks
+        advance_internal_clocks();
+
+        // Symbol Clock and Interpolator Positioning & Alignment
+        interp_output = d_interp->interpolate(&in[ii],
+                                              d_interp->phase_wrapped());
+        if (output_sample_clock())
+            out[oo] = interp_output;
+
+        // Timing Error Detector
+        if (ted_input_clock()) {
+            if (d_ted->needs_derivative())
+                interp_derivative =
+                    d_interp->differentiate(&in[ii], d_interp->phase_wrapped());
+            d_ted->input(interp_output, interp_derivative);
+        }
+        if (symbol_clock() and d_ted->needs_lookahead()) {
+            // N.B. symbol_clock() == true implies ted_input_clock() == true
+            // N.B. symbol_clock() == true implies output_sample_clock() == true
+
+            // We must interpolate ahead to the *next* ted_input_clock, so the
+            // ted will compute the error for *this* symbol.
+            d_interp->next_phase(d_interps_per_ted_input
+                                 * (d_clock->get_inst_period()
+                                    / d_interps_per_symbol),
+                                 look_ahead_phase,
+                                 look_ahead_phase_n,
+                                 look_ahead_phase_wrapped);
+
+            if (ii + look_ahead_phase_n >= ni) {
+                // We can't compute the look ahead interpolated output
+                // because there's not enough input.
+                // Revert the actions we took in the loop so far, and bail out.
+
+                // Symbol Clock Tracking and Estimation
+                // We haven't advanced the clock loop yet; no revert needed.
+
+                // Timing Error Detector
+                d_ted->revert(true); // keep the error value; it's still good
+
+                // Symbol Clock and Interpolator Positioning & Alignment
+                // Nothing to do
+
+                // Block Internal Clocks
+                revert_internal_clocks();
+                break;
+            }
+            // Give the ted the look ahead input that it needs to compute
+            // the error for *this* symbol.
+            interp_output = d_interp->interpolate(&in[ii + look_ahead_phase_n],
+                                                  look_ahead_phase_wrapped);
+            if (d_ted->needs_derivative())
+                interp_derivative =
+                           d_interp->differentiate(&in[ii + look_ahead_phase_n],
+                                                   look_ahead_phase_wrapped);
+            d_ted->input_lookahead(interp_output, interp_derivative);
+        }
+        error = d_ted->error();
+
+        if (symbol_clock()) {
+            // Symbol Clock Tracking and Estimation
+            d_clock->advance_loop(error);
+            d_inst_clock_period = d_clock->get_inst_period();
+            d_avg_clock_period = d_clock->get_avg_period();
+            d_clock->phase_wrap();
+            d_clock->period_limit();
+
+            // Symbol Clock and Interpolator Positioning & Alignment
+            d_inst_interp_period = d_inst_clock_period / d_interps_per_symbol;
+
+            // Tag Propagation
+            d_inst_output_period = d_inst_clock_period / d_osps;
+        }
+
+        // Symbol Clock, Interpolator Positioning & Alignment, and 
+        // Tag Propagation
+        if (symbol_clock()) {
+            // N.B. symbol_clock() == true implies ted_input_clock() == true
+            // N.B. symbol_clock() == true implies output_sample_clock() == true
+
+            // This check and revert is needed either when
+            // a) the samples per symbol to get to the next symbol is greater
+            // than d_interp->ntaps() (normally 8); thus we would consume()
+            // more input than we were given to get there.
+            // b) we can't get to the next output so we can't do tag
+            // propagation properly.
+            d_interp->next_phase(d_inst_clock_period,
+                                 look_ahead_phase,
+                                 look_ahead_phase_n,
+                                 look_ahead_phase_wrapped);
+
+            if (ii + look_ahead_phase_n >= ni) {
+                // We can't move forward because there's not enough input.
+                // Revert the actions we took in the loop so far, and bail out.
+
+                // Symbol Clock Tracking and Estimation
+                d_clock->revert_loop();
+
+                // Timing Error Detector
+                d_ted->revert();
+
+                // Symbol Clock and Interpolator Positioning & Alignment
+                // Nothing to do;
+
+                // Block Internal Clocks
+                revert_internal_clocks();
+                break;
+            }
+        }
+
+        // Symbol Clock and Interpolator Positioning & Alignment
+        d_interp->advance_phase(d_inst_interp_period);
+
+        // Symbol Clock Tracking Reset/Resync to time_est and clock_est tags
+        if (find_sync_tag(nitems_rd, ii, d_interp->phase_n(), sync_tag_offset,
+                          sync_timing_offset, sync_clock_period) == true   ) {
+
+            // Block Internal Clocks
+            sync_reset_internal_clocks();
+
+            // Timing Error Detector
+            d_ted->sync_reset();
+            error = d_ted->error();
+
+            // Symbol Clock Tracking and Estimation
+
+            // NOTE: the + 1 below was determined empirically, but doesn't
+            // seem right on paper (maybe rounding in the computation of
+            // d_filter_delay is the culprit).  Anyway, experiment trumps
+            // theory *every* time; so + 1 it is.
+            d_inst_clock_period = static_cast<float>(
+                  static_cast<int>(sync_tag_offset - nitems_rd - d_filter_delay)
+                  - ii + 1) + sync_timing_offset - d_interp->phase_wrapped();
+
+            d_clock->set_inst_period(d_inst_clock_period);
+            d_clock->set_avg_period(sync_clock_period);
+            d_avg_clock_period = d_clock->get_avg_period();
+            d_clock->set_phase(0.0f);
+
+            // Symbol Clock and Interpolator Positioning & Alignment
+            d_inst_interp_period = d_inst_clock_period;
+            d_interp->revert_phase();
+            d_interp->advance_phase(d_inst_interp_period);
+
+            // Tag Propagation
+            // Only needed if we reverted back to an output_sample_clock()
+            // as this is only used for tag propagation.  Note that the
+            // next output will also be both an output_sample_clock() and a
+            // symbol_clock().
+            d_inst_output_period = d_inst_clock_period;
+        }
+
+        if (output_sample_clock()) {
+            // Diagnostic Output of Symbol Clock Tracking cycle results
+            emit_optional_output(oo, error, d_inst_clock_period,
+                                                            d_avg_clock_period);
+            // Tag Propagation
+            propagate_tags(nitems_rd, ii,
+                           d_interp->prev_phase_wrapped(), d_inst_output_period,
+                           nitems_wr, oo);
+
+            // Symbol Clock and Interpolator Positioning & Alignment
+            oo++;
+        }
+
+        // Symbol Clock and Interpolator Positioning & Alignment
+        ii += d_interp->phase_n();
+      }
+
+      // Deferred Tag Propagation
+      save_expiring_tags(nitems_rd, ii);
+
+      // Symbol Clock and Interpolator Positioning & Alignment
+      consume_each(ii);
+      return oo;
+    }
+
+  } /* namespace digital */
+} /* namespace gr */
-- 
cgit v1.2.3