1 //
2 // Copyright 2019 Ettus Research, a National Instruments Brand
3 //
4 // SPDX-License-Identifier: GPL-3.0-or-later
5 //
6 
7 #include <uhd/exception.hpp>
8 #include <uhd/rfnoc/ddc_block_control.hpp>
9 #include <uhd/rfnoc/defaults.hpp>
10 #include <uhd/rfnoc/multichan_register_iface.hpp>
11 #include <uhd/rfnoc/property.hpp>
12 #include <uhd/rfnoc/registry.hpp>
13 #include <uhd/types/ranges.hpp>
14 #include <uhd/utils/log.hpp>
15 #include <uhd/utils/math.hpp>
16 #include <uhdlib/usrp/cores/dsp_core_utils.hpp>
17 #include <uhdlib/utils/compat_check.hpp>
18 #include <uhdlib/utils/math.hpp>
19 #include <boost/math/special_functions/round.hpp>
20 #include <set>
21 #include <string>
22 
23 namespace {
24 
25 constexpr double DEFAULT_SCALING         = 1.0;
26 constexpr int DEFAULT_DECIM              = 1;
27 constexpr double DEFAULT_FREQ            = 0.0;
28 const uhd::rfnoc::io_type_t DEFAULT_TYPE = uhd::rfnoc::IO_TYPE_SC16;
29 
30 //! Space (in bytes) between register banks per channel
31 constexpr uint32_t REG_CHAN_OFFSET = 2048;
32 
33 } // namespace
34 
35 using namespace uhd::rfnoc;
36 
37 const uint16_t ddc_block_control::MINOR_COMPAT = 1;
38 const uint16_t ddc_block_control::MAJOR_COMPAT = 0;
39 
40 const uint32_t ddc_block_control::RB_COMPAT_NUM    = 0; // read this first
41 const uint32_t ddc_block_control::RB_NUM_HB        = 8;
42 const uint32_t ddc_block_control::RB_CIC_MAX_DECIM = 16;
43 
44 const uint32_t ddc_block_control::SR_N_ADDR         = 128 * 8;
45 const uint32_t ddc_block_control::SR_M_ADDR         = 129 * 8;
46 const uint32_t ddc_block_control::SR_CONFIG_ADDR    = 130 * 8;
47 const uint32_t ddc_block_control::SR_FREQ_ADDR      = 132 * 8;
48 const uint32_t ddc_block_control::SR_SCALE_IQ_ADDR  = 133 * 8;
49 const uint32_t ddc_block_control::SR_DECIM_ADDR     = 134 * 8;
50 const uint32_t ddc_block_control::SR_MUX_ADDR       = 135 * 8;
51 const uint32_t ddc_block_control::SR_COEFFS_ADDR    = 136 * 8;
52 const uint32_t ddc_block_control::SR_TIME_INCR_ADDR = 137 * 8;
53 
54 class ddc_block_control_impl : public ddc_block_control
55 {
56 public:
RFNOC_BLOCK_CONSTRUCTOR(ddc_block_control)57     RFNOC_BLOCK_CONSTRUCTOR(ddc_block_control)
58     , _ddc_reg_iface(*this, 0, REG_CHAN_OFFSET),
59         _fpga_compat(regs().peek32(RB_COMPAT_NUM)),
60         _num_halfbands(regs().peek32(RB_NUM_HB)),
61         _cic_max_decim(regs().peek32(RB_CIC_MAX_DECIM)),
62         _residual_scaling(get_num_input_ports(), DEFAULT_SCALING)
63     {
64         UHD_ASSERT_THROW(get_num_input_ports() == get_num_output_ports());
65         UHD_ASSERT_THROW(_cic_max_decim > 0 && _cic_max_decim <= 0xFF);
66         uhd::assert_fpga_compat(MAJOR_COMPAT,
67             MINOR_COMPAT,
68             _fpga_compat,
69             get_unique_id(),
70             get_unique_id(),
71             false /* Let it slide if minors mismatch */
72         );
73         RFNOC_LOG_DEBUG("Loading DDC with " << _num_halfbands
74                                             << " halfbands and "
75                                                "max CIC decimation "
76                                             << _cic_max_decim);
77         set_mtu_forwarding_policy(forwarding_policy_t::ONE_TO_ONE);
78         // Load list of valid decimation values
79         std::set<size_t> decims{1}; // 1 is always a valid decimation
80         for (size_t hb = 0; hb < _num_halfbands; hb++) {
81             for (size_t cic_decim = 1; cic_decim <= _cic_max_decim; cic_decim++) {
82                 decims.insert((1 << hb) * cic_decim);
83             }
84         }
85         for (size_t decim : decims) {
86             _valid_decims.push_back(uhd::range_t(double(decim)));
87         }
88 
89         // Initialize properties. It is very important to first reserve the
90         // space, because we use push_back() further down, and properties must
91         // not change their base address after registration and resolver
92         // creation.
93         _samp_rate_in.reserve(get_num_ports());
94         _samp_rate_out.reserve(get_num_ports());
95         _scaling_in.reserve(get_num_ports());
96         _scaling_out.reserve(get_num_ports());
97         _decim.reserve(get_num_ports());
98         _freq.reserve(get_num_ports());
99         _type_in.reserve(get_num_ports());
100         _type_out.reserve(get_num_ports());
101         for (size_t chan = 0; chan < get_num_ports(); chan++) {
102             _register_props(chan);
103         }
104         register_issue_stream_cmd();
105     }
106 
set_freq(const double freq,const size_t chan,const boost::optional<uhd::time_spec_t> time)107     double set_freq(const double freq,
108         const size_t chan,
109         const boost::optional<uhd::time_spec_t> time)
110     {
111         // Store the current command time so we can restore it later
112         auto prev_cmd_time = get_command_time(chan);
113         if (time) {
114             set_command_time(time.get(), chan);
115         }
116         // This will trigger property propagation:
117         set_property<double>("freq", freq, chan);
118         set_command_time(prev_cmd_time, chan);
119         return get_freq(chan);
120     }
121 
get_freq(const size_t chan) const122     double get_freq(const size_t chan) const
123     {
124         return _freq.at(chan).get();
125     }
126 
get_frequency_range(const size_t chan) const127     uhd::freq_range_t get_frequency_range(const size_t chan) const
128     {
129         const double input_rate =
130             _samp_rate_in.at(chan).is_valid() ? _samp_rate_in.at(chan).get() : 1.0;
131         // TODO add steps
132         return uhd::freq_range_t(-input_rate / 2, input_rate / 2);
133     }
134 
get_input_rate(const size_t chan) const135     double get_input_rate(const size_t chan) const
136     {
137         return _samp_rate_in.at(chan).is_valid() ? _samp_rate_in.at(chan).get() : 1.0;
138     }
139 
set_input_rate(const double rate,const size_t chan)140     void set_input_rate(const double rate, const size_t chan)
141     {
142         set_property<double>("samp_rate", rate, {res_source_info::INPUT_EDGE, chan});
143     }
144 
get_output_rate(const size_t chan) const145     double get_output_rate(const size_t chan) const
146     {
147         return _samp_rate_out.at(chan).is_valid() ? _samp_rate_out.at(chan).get() : 1.0;
148     }
149 
get_output_rates(const size_t chan) const150     uhd::meta_range_t get_output_rates(const size_t chan) const
151     {
152         uhd::meta_range_t result;
153         if (!_samp_rate_in.at(chan).is_valid()) {
154             result.push_back(uhd::range_t(1.0));
155             return result;
156         }
157         const double input_rate = _samp_rate_in.at(chan).get();
158         // The decimations are stored in order (from smallest to biggest), so
159         // iterate in reverse order so we can add rates from smallest to biggest
160         for (auto it = _valid_decims.rbegin(); it != _valid_decims.rend(); ++it) {
161             result.push_back(uhd::range_t(input_rate / it->start()));
162         }
163         return result;
164     }
165 
set_output_rate(const double rate,const size_t chan)166     double set_output_rate(const double rate, const size_t chan)
167     {
168         if (_samp_rate_in.at(chan).is_valid()) {
169             const int coerced_decim = coerce_decim(get_input_rate(chan) / rate);
170             set_property<int>("decim", coerced_decim, chan);
171         } else {
172             RFNOC_LOG_DEBUG("Property samp_rate@"
173                             << chan << " is not valid, attempting to set output rate "
174                             << (rate / 1e6) << " Msps via the edge property.");
175             set_property<double>("samp_rate", rate, {res_source_info::OUTPUT_EDGE, chan});
176         }
177         return _samp_rate_out.at(chan).get();
178     }
179 
180     // Somewhat counter-intuitively, we post a stream command as a message to
181     // ourselves. That's because it's easier to re-use the message handler than
182     // it is to reuse the issue_stream_cmd() API call, because this API call
183     // will always be forwarded to the upstream block, whereas the message
184     // handler goes both ways.
185     // This way, calling issue_stream_cmd() is the same as posting a message to
186     // our output port.
issue_stream_cmd(const uhd::stream_cmd_t & stream_cmd,const size_t port)187     void issue_stream_cmd(const uhd::stream_cmd_t& stream_cmd, const size_t port)
188     {
189         RFNOC_LOG_TRACE("issue_stream_cmd(stream_mode=" << char(stream_cmd.stream_mode)
190                                                         << ", port=" << port);
191         res_source_info dst_edge{res_source_info::OUTPUT_EDGE, port};
192         auto new_action        = stream_cmd_action_info::make(stream_cmd.stream_mode);
193         new_action->stream_cmd = stream_cmd;
194         issue_stream_cmd_action_handler(dst_edge, new_action);
195     }
196 
197 protected:
198     //! Block-specific register interface
199     multichan_register_iface _ddc_reg_iface;
200 
201 private:
202     //! Shorthand for num ports, since num input ports always equals num output ports
get_num_ports()203     inline size_t get_num_ports()
204     {
205         return get_num_input_ports();
206     }
207 
208     /**************************************************************************
209      * Initialization
210      *************************************************************************/
_register_props(const size_t chan)211     void _register_props(const size_t chan)
212     {
213         // Create actual properties and store them
214         _samp_rate_in.push_back(
215             property_t<double>(PROP_KEY_SAMP_RATE, {res_source_info::INPUT_EDGE, chan}));
216         _samp_rate_out.push_back(
217             property_t<double>(PROP_KEY_SAMP_RATE, {res_source_info::OUTPUT_EDGE, chan}));
218         _scaling_in.push_back(
219             property_t<double>(PROP_KEY_SCALING, {res_source_info::INPUT_EDGE, chan}));
220         _scaling_out.push_back(
221             property_t<double>(PROP_KEY_SCALING, {res_source_info::OUTPUT_EDGE, chan}));
222         _decim.push_back(property_t<int>(
223             PROP_KEY_DECIM, DEFAULT_DECIM, {res_source_info::USER, chan}));
224         _freq.push_back(property_t<double>(
225             PROP_KEY_FREQ, DEFAULT_FREQ, {res_source_info::USER, chan}));
226         _type_in.emplace_back(property_t<std::string>(
227             PROP_KEY_TYPE, IO_TYPE_SC16, {res_source_info::INPUT_EDGE, chan}));
228         _type_out.emplace_back(property_t<std::string>(
229             PROP_KEY_TYPE, IO_TYPE_SC16, {res_source_info::OUTPUT_EDGE, chan}));
230         UHD_ASSERT_THROW(_samp_rate_in.size() == chan + 1);
231         UHD_ASSERT_THROW(_samp_rate_out.size() == chan + 1);
232         UHD_ASSERT_THROW(_scaling_in.size() == chan + 1);
233         UHD_ASSERT_THROW(_scaling_out.size() == chan + 1);
234         UHD_ASSERT_THROW(_decim.size() == chan + 1);
235         UHD_ASSERT_THROW(_freq.size() == chan + 1);
236         UHD_ASSERT_THROW(_type_in.size() == chan + 1);
237         UHD_ASSERT_THROW(_type_out.size() == chan + 1);
238 
239         // give us some shorthands for the rest of this function
240         property_t<double>* samp_rate_in  = &_samp_rate_in.back();
241         property_t<double>* samp_rate_out = &_samp_rate_out.back();
242         property_t<double>* scaling_in    = &_scaling_in.back();
243         property_t<double>* scaling_out   = &_scaling_out.back();
244         property_t<int>* decim            = &_decim.back();
245         property_t<double>* freq          = &_freq.back();
246         property_t<std::string>* type_in  = &_type_in.back();
247         property_t<std::string>* type_out = &_type_out.back();
248 
249         // register them
250         register_property(samp_rate_in);
251         register_property(samp_rate_out);
252         register_property(scaling_in);
253         register_property(scaling_out);
254         register_property(decim);
255         register_property(freq);
256         register_property(type_in);
257         register_property(type_out);
258 
259         /**********************************************************************
260          * Add resolvers
261          *********************************************************************/
262         // Resolver for _decim: this gets executed when the user directly
263         // modifies _decim. the desired behaviour is to coerce it first, then
264         // keep the input rate constant, and re-calculate the output rate.
265         add_property_resolver({decim},
266             {decim, samp_rate_out, samp_rate_in, scaling_in},
267             [this,
268                 chan,
269                 &decim         = *decim,
270                 &samp_rate_out = *samp_rate_out,
271                 &samp_rate_in  = *samp_rate_in,
272                 &scaling_in    = *scaling_in]() {
273                 RFNOC_LOG_TRACE("Calling resolver for `decim'@" << chan);
274                 decim = coerce_decim(double(decim.get()));
275                 if (decim.is_dirty()) {
276                     set_decim(decim.get(), chan);
277                 }
278                 if (samp_rate_in.is_valid()) {
279                     samp_rate_out = samp_rate_in.get() / decim.get();
280                 } else if (samp_rate_out.is_valid()) {
281                     samp_rate_in = samp_rate_out.get() * decim.get();
282                 }
283                 if (scaling_in.is_valid()) {
284                     scaling_in.force_dirty();
285                 }
286             });
287         // Resolver for _freq: this gets executed when the user directly
288         // modifies _freq.
289         add_property_resolver(
290             {freq}, {freq}, [this, chan, &samp_rate_in = *samp_rate_in, &freq = *freq]() {
291                 RFNOC_LOG_TRACE("Calling resolver for `freq'@" << chan);
292                 if (samp_rate_in.is_valid()) {
293                     const double new_freq =
294                         _set_freq(freq.get(), samp_rate_in.get(), chan);
295                     // If the frequency we just set is sufficiently close to the old
296                     // frequency, don't bother updating the property in software
297                     if (!uhd::math::frequencies_are_equal(new_freq, freq.get())) {
298                         freq = new_freq;
299                     }
300                 } else {
301                     RFNOC_LOG_DEBUG("Not setting frequency until sampling rate is set.");
302                 }
303             });
304         // Resolver for the input rate:
305         // If this is called, then most likely, the input sampling rate was set.
306         // In that case, we try and keep the output sampling rate as it was, and
307         // modify decim to match the input/output ratio. If we can't exactly hit
308         // the previous output rate, then we coerce the desired decim to a valid
309         // decim value, and update the output rate.
310         // Note: This means that if the user set decim explicitly, then this
311         // resolver can undo the user's intentions. However, it is the option
312         // that retains the consistency of the graph as much as possible, and
313         // allows the user to call set_output_rate() on this block before the
314         // graph was committed.
315         //
316         // The scaling is modified in the same resolver to avoid circular
317         // dependencies. Note that changing the decimation will change the
318         // scaling ratio (input to output scaling), so we need to write to the
319         // decimation register in this resolver as well as the decim resolver
320         // in order to make sure that _residual_scaling is correctly set.
321         // Otherwise, the decim resolver and this resolver would conflict each
322         // other when writing to scaling_out.
323         //
324         // This resolver may affect the frequency: If the input sampling rate
325         // was changed, then the phase accumulator increment needs to be
326         // recalculated in order to retain the current value of the frequency
327         // offset, which is given in Hz (not in radians).
328         add_property_resolver({samp_rate_in, scaling_in},
329             {decim, samp_rate_out, freq, scaling_out},
330             [this,
331                 chan,
332                 &decim         = *decim,
333                 &freq          = *freq,
334                 &samp_rate_out = *samp_rate_out,
335                 &samp_rate_in  = *samp_rate_in,
336                 &scaling_in    = *scaling_in,
337                 &scaling_out   = *scaling_out]() {
338                 RFNOC_LOG_TRACE(
339                     "Calling resolver for `samp_rate_in/scaling_in'@" << chan);
340                 if (samp_rate_in.is_valid()) {
341                     RFNOC_LOG_TRACE("New samp_rate_in is " << samp_rate_in.get());
342                     if (samp_rate_out.is_valid()) {
343                         decim = coerce_decim(samp_rate_in.get() / samp_rate_out.get());
344                         set_decim(decim.get(), chan);
345                         const double new_samp_rate_out = samp_rate_in.get() / decim.get();
346                         // Only update the samp_rate_out if the new value is not the same
347                         // frequency. However, we still want to call the operator= to make
348                         // sure metadata gets handled
349                         samp_rate_out = (uhd::math::frequencies_are_equal(
350                                             samp_rate_out, new_samp_rate_out))
351                                             ? samp_rate_out.get()
352                                             : new_samp_rate_out;
353                         RFNOC_LOG_TRACE("New samp_rate_out is " << samp_rate_out.get());
354                     } else if (decim.is_valid()) {
355                         samp_rate_out = samp_rate_in.get() / decim.get();
356                     }
357                     // If the input rate changes, we need to update the DDS, too,
358                     // since it works on frequencies normalized by the input rate.
359                     freq.force_dirty();
360                 }
361                 if (scaling_in.is_valid()) {
362                     scaling_out = scaling_in.get() * _residual_scaling.at(chan);
363                 }
364             });
365         // Resolver for the output rate: like the previous one, but flipped.
366         add_property_resolver({samp_rate_out, scaling_out},
367             {decim, samp_rate_in, scaling_out},
368             [this,
369                 chan,
370                 &decim         = *decim,
371                 &samp_rate_out = *samp_rate_out,
372                 &samp_rate_in  = *samp_rate_in,
373                 &scaling_in    = *scaling_in,
374                 &scaling_out   = *scaling_out]() {
375                 RFNOC_LOG_TRACE(
376                     "Calling resolver for `samp_rate_out/scaling_out'@" << chan);
377                 if (samp_rate_out.is_valid()) {
378                     if (samp_rate_in.is_valid()) {
379                         decim = coerce_decim(samp_rate_in.get() / samp_rate_out.get());
380                         set_decim(decim.get(), chan);
381                     }
382                     // If decim is dirty, it will trigger the decim resolver.
383                     // However, the decim resolver will set the output rate based
384                     // on the input rate, so we need to force the input rate first.
385                     if (decim.is_dirty()) {
386                         const double new_samp_rate_in = samp_rate_out.get() * decim.get();
387                         // Only update the samp_rate_in if the new value is not the same
388                         // frequency. However, we still want to call the operator= to make
389                         // sure metadata gets handled
390                         if (samp_rate_in.is_valid()) {
391                             samp_rate_in = (uhd::math::frequencies_are_equal(
392                                                samp_rate_in, new_samp_rate_in))
393                                                ? samp_rate_in.get()
394                                                : new_samp_rate_in;
395                         } else {
396                             samp_rate_in = new_samp_rate_in;
397                         }
398                         RFNOC_LOG_TRACE("New samp_rate_in is " << samp_rate_in.get());
399                     }
400                 }
401                 if (scaling_in.is_valid()) {
402                     scaling_out = scaling_in.get() * _residual_scaling.at(chan);
403                 }
404             });
405         // Resolvers for type: These are constants
406         add_property_resolver({type_in}, {type_in}, [& type_in = *type_in]() {
407             type_in.set(IO_TYPE_SC16);
408         });
409         add_property_resolver({type_out}, {type_out}, [& type_out = *type_out]() {
410             type_out.set(IO_TYPE_SC16);
411         });
412     }
413 
register_issue_stream_cmd()414     void register_issue_stream_cmd()
415     {
416         register_action_handler(ACTION_KEY_STREAM_CMD,
417             [this](const res_source_info& src, action_info::sptr action) {
418                 stream_cmd_action_info::sptr stream_cmd_action =
419                     std::dynamic_pointer_cast<stream_cmd_action_info>(action);
420                 if (!stream_cmd_action) {
421                     throw uhd::runtime_error(
422                         "Received stream_cmd of invalid action type!");
423                 }
424                 issue_stream_cmd_action_handler(src, stream_cmd_action);
425             });
426     }
427 
issue_stream_cmd_action_handler(const res_source_info & src,stream_cmd_action_info::sptr stream_cmd_action)428     void issue_stream_cmd_action_handler(
429         const res_source_info& src, stream_cmd_action_info::sptr stream_cmd_action)
430     {
431         res_source_info dst_edge{res_source_info::invert_edge(src.type), src.instance};
432         const size_t chan = src.instance;
433         uhd::stream_cmd_t::stream_mode_t stream_mode =
434             stream_cmd_action->stream_cmd.stream_mode;
435         RFNOC_LOG_TRACE("Received stream command: " << char(stream_mode) << " to "
436                                                     << src.to_string()
437                                                     << ", id==" << stream_cmd_action->id);
438         auto new_action        = stream_cmd_action_info::make(stream_mode);
439         new_action->stream_cmd = stream_cmd_action->stream_cmd;
440         if (stream_mode == uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE
441             || stream_mode == uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE) {
442             if (src.type == res_source_info::OUTPUT_EDGE) {
443                 new_action->stream_cmd.num_samps *= _decim.at(chan).get();
444             } else {
445                 new_action->stream_cmd.num_samps /= _decim.at(chan).get();
446             }
447             RFNOC_LOG_TRACE("Forwarding num_samps stream command, new value is "
448                             << new_action->stream_cmd.num_samps);
449         } else {
450             RFNOC_LOG_TRACE("Forwarding continuous stream command...")
451         }
452 
453         post_action(dst_edge, new_action);
454     }
455 
456     /**************************************************************************
457      * FPGA communication (register IO)
458      *************************************************************************/
459     /*! Update the decimation value
460      *
461      * \param decim The new decimation value. It must be valid decimation value.
462      * \throws uhd::assertion_error if decim is not valid.
463      */
set_decim(int decim,const size_t chan)464     void set_decim(int decim, const size_t chan)
465     {
466         RFNOC_LOG_TRACE("Set decim to " << decim);
467         // Step 1: Calculate number of halfbands
468         uint32_t hb_enable = 0;
469         uint32_t cic_decim = decim;
470         while ((cic_decim % 2 == 0) and hb_enable < _num_halfbands) {
471             hb_enable++;
472             cic_decim /= 2;
473         }
474         // Step 2: Make sure we can handle the rest with the CIC
475         UHD_ASSERT_THROW(hb_enable <= _num_halfbands);
476         UHD_ASSERT_THROW(cic_decim > 0 and cic_decim <= _cic_max_decim);
477         const uint32_t decim_word = (hb_enable << 8) | cic_decim;
478         _ddc_reg_iface.poke32(SR_DECIM_ADDR, decim_word, chan);
479 
480         // Rate change = M/N
481         _ddc_reg_iface.poke32(SR_N_ADDR, decim, chan);
482         _ddc_reg_iface.poke32(SR_M_ADDR, 1, chan);
483 
484         // Configure time increment in ticks per M output samples
485         _ddc_reg_iface.poke32(SR_TIME_INCR_ADDR,
486             uint32_t(get_tick_rate()/get_output_rate(chan)), chan);
487 
488         if (cic_decim > 1 and hb_enable == 0) {
489             RFNOC_LOG_WARNING(
490                 "The requested decimation is odd; the user should expect passband "
491                 "CIC rolloff.\n"
492                 "Select an even decimation to ensure that a halfband filter is "
493                 "enabled.\n"
494                 "Decimations factorable by 4 will enable 2 halfbands, those "
495                 "factorable by 8 will enable 3 halfbands.\n"
496                 "decimation = dsp_rate/samp_rate -> "
497                 << decim);
498         }
499 
500         constexpr double DDS_GAIN = 2.0;
501         // Calculate algorithmic gain of CIC for a given decimation.
502         // For Ettus CIC R=decim, M=1, N=4. Gain = (R * M) ^ N
503         // The Ettus CIC also tries its best to compensate for the gain by
504         // shifting the CIC output. This reduces the gain by a factor of
505         // 2**ceil(log2(cic_gain))
506         const double cic_gain = std::pow(double(cic_decim * 1), 4);
507         // DDS gain:
508         const double total_gain =
509             DDS_GAIN * cic_gain / std::pow(2, uhd::math::ceil_log2(cic_gain));
510         update_scaling(total_gain, chan);
511     }
512 
513     //! Update scaling based on the current gain
514     //
515     // Calculates the closest fixpoint value that this block can correct for in
516     // hardware (fixpoint). The residual gain is written to _residual_scaling.
update_scaling(const double dsp_gain,const size_t chan)517     void update_scaling(const double dsp_gain, const size_t chan)
518     {
519         constexpr double FIXPOINT_SCALING = 1 << 15;
520         const double compensation_factor  = 1. / dsp_gain;
521         // Convert to fixpoint
522         const double target_factor  = FIXPOINT_SCALING * compensation_factor;
523         const int32_t actual_factor = boost::math::iround(target_factor);
524         // Write DDC with scaling correction for CIC and DDS that maximizes
525         // dynamic range
526         _ddc_reg_iface.poke32(SR_SCALE_IQ_ADDR, actual_factor, chan);
527 
528         // Calculate the error introduced by using fixedpoint representation for
529         // the scaler, can be corrected in host later.
530         _residual_scaling[chan] = dsp_gain * double(actual_factor) / FIXPOINT_SCALING;
531     }
532 
533     /*! Return the closest possible decimation value to the one requested
534      */
coerce_decim(const double requested_decim) const535     int coerce_decim(const double requested_decim) const
536     {
537         UHD_ASSERT_THROW(requested_decim >= 0);
538         return static_cast<int>(_valid_decims.clip(requested_decim, true));
539     }
540 
541     //! Set the DDS frequency shift the signal to \p requested_freq
_set_freq(const double requested_freq,const double input_rate,const size_t chan)542     double _set_freq(
543         const double requested_freq, const double input_rate, const size_t chan)
544     {
545         double actual_freq;
546         int32_t freq_word;
547         std::tie(actual_freq, freq_word) =
548             get_freq_and_freq_word(requested_freq, input_rate);
549         _ddc_reg_iface.poke32(
550             SR_FREQ_ADDR, uint32_t(freq_word), chan, get_command_time(chan));
551         return actual_freq;
552     }
553 
554     /**************************************************************************
555      * Attributes
556      *************************************************************************/
557     //! Block compat number
558     const uint32_t _fpga_compat;
559     //! Number of halfbands
560     const size_t _num_halfbands;
561     //! Max CIC decim
562     const size_t _cic_max_decim;
563 
564     //! List of valid decimation values
565     uhd::meta_range_t _valid_decims;
566 
567     //! Cache the current residual scaling
568     std::vector<double> _residual_scaling;
569 
570     //! Properties for type_in (one per port)
571     std::vector<property_t<std::string>> _type_in;
572     //! Properties for type_out (one per port)
573     std::vector<property_t<std::string>> _type_out;
574     //! Properties for samp_rate_in (one per port)
575     std::vector<property_t<double>> _samp_rate_in;
576     //! Properties for samp_rate_out (one per port)
577     std::vector<property_t<double>> _samp_rate_out;
578     //! Properties for scaling_in (one per port)
579     std::vector<property_t<double>> _scaling_in;
580     //! Properties for scaling_out (one per port)
581     std::vector<property_t<double>> _scaling_out;
582     //! Properties for decim (one per port)
583     std::vector<property_t<int>> _decim;
584     //! Properties for freq (one per port)
585     std::vector<property_t<double>> _freq;
586 };
587 
588 UHD_RFNOC_BLOCK_REGISTER_DIRECT(
589     ddc_block_control, 0xDDC00000, "DDC", CLOCK_KEY_GRAPH, "bus_clk")
590