1 //
2 // Copyright 2019 Ettus Research, a National Instruments Brand
3 //
4 // SPDX-License-Identifier: GPL-3.0-or-later
5 //
6 
7 #include "../dboard/db_basic_and_lf.hpp"
8 #include "x300_adc_ctrl.hpp"
9 #include "x300_dac_ctrl.hpp"
10 #include "x300_dboard_iface.hpp"
11 #include "x300_device_args.hpp"
12 #include "x300_mb_controller.hpp"
13 #include "x300_radio_mbc_iface.hpp"
14 #include "x300_regs.hpp"
15 #include <uhd/rfnoc/registry.hpp>
16 #include <uhd/types/direction.hpp>
17 #include <uhd/types/wb_iface.hpp>
18 #include <uhd/usrp/dboard_eeprom.hpp>
19 #include <uhd/usrp/dboard_manager.hpp>
20 #include <uhd/utils/gain_group.hpp>
21 #include <uhd/utils/log.hpp>
22 #include <uhd/utils/math.hpp>
23 #include <uhd/utils/soft_register.hpp>
24 #include <uhdlib/rfnoc/radio_control_impl.hpp>
25 #include <uhdlib/rfnoc/reg_iface_adapter.hpp>
26 #include <uhdlib/usrp/common/apply_corrections.hpp>
27 #include <uhdlib/usrp/cores/gpio_atr_3000.hpp>
28 #include <uhdlib/usrp/cores/rx_frontend_core_3000.hpp>
29 #include <uhdlib/usrp/cores/spi_core_3000.hpp>
30 #include <uhdlib/usrp/cores/tx_frontend_core_200.hpp>
31 #include <boost/algorithm/string.hpp>
32 #include <algorithm>
33 #include <chrono>
34 #include <functional>
35 #include <iostream>
36 #include <memory>
37 #include <thread>
38 
39 using namespace uhd;
40 using namespace uhd::usrp;
41 using namespace uhd::rfnoc;
42 
43 namespace {
44 
str_to_bytes(std::string str)45 std::vector<uint8_t> str_to_bytes(std::string str)
46 {
47     return std::vector<uint8_t>(str.cbegin(), str.cend());
48 }
49 
bytes_to_str(std::vector<uint8_t> str_b)50 std::string bytes_to_str(std::vector<uint8_t> str_b)
51 {
52     return std::string(str_b.cbegin(), str_b.cend());
53 }
54 
make_gain_fcns_from_subtree(property_tree::sptr subtree)55 gain_fcns_t make_gain_fcns_from_subtree(property_tree::sptr subtree)
56 {
57     gain_fcns_t gain_fcns;
58     gain_fcns.get_range = [subtree]() {
59         return subtree->access<meta_range_t>("range").get();
60     };
61     gain_fcns.get_value = [subtree]() { return subtree->access<double>("value").get(); };
62     gain_fcns.set_value = [subtree](const double gain) {
63         subtree->access<double>("value").set(gain);
64     };
65     return gain_fcns;
66 }
67 
68 template <typename map_type>
_get_chan_from_map(std::unordered_map<size_t,map_type> map,const std::string & fe)69 size_t _get_chan_from_map(std::unordered_map<size_t, map_type> map, const std::string& fe)
70 {
71     for (auto it = map.begin(); it != map.end(); ++it) {
72         if (it->second.db_fe_name == fe) {
73             return it->first;
74         }
75     }
76     throw uhd::lookup_error(
77         str(boost::format("Invalid daughterboard frontend name: %s") % fe));
78 }
79 
80 constexpr double DEFAULT_RATE = 200e6;
81 constexpr char HW_GAIN_STAGE[] = "hw";
82 
83 } // namespace
84 
85 namespace x300_regs {
86 
87 static constexpr uint32_t PERIPH_BASE       = 0x80000;
88 static constexpr uint32_t PERIPH_REG_OFFSET = 8;
89 
90 // db_control registers
91 static constexpr uint32_t SR_MISC_OUTS = PERIPH_BASE + 160 * PERIPH_REG_OFFSET;
92 static constexpr uint32_t SR_SPI       = PERIPH_BASE + 168 * PERIPH_REG_OFFSET;
93 static constexpr uint32_t SR_LEDS      = PERIPH_BASE + 176 * PERIPH_REG_OFFSET;
94 static constexpr uint32_t SR_FP_GPIO   = PERIPH_BASE + 184 * PERIPH_REG_OFFSET;
95 static constexpr uint32_t SR_DB_GPIO   = PERIPH_BASE + 192 * PERIPH_REG_OFFSET;
96 
97 static constexpr uint32_t RB_MISC_IO = PERIPH_BASE + 16 * PERIPH_REG_OFFSET;
98 static constexpr uint32_t RB_SPI     = PERIPH_BASE + 17 * PERIPH_REG_OFFSET;
99 static constexpr uint32_t RB_LEDS    = PERIPH_BASE + 18 * PERIPH_REG_OFFSET;
100 static constexpr uint32_t RB_DB_GPIO = PERIPH_BASE + 19 * PERIPH_REG_OFFSET;
101 static constexpr uint32_t RB_FP_GPIO = PERIPH_BASE + 20 * PERIPH_REG_OFFSET;
102 
103 
104 //! Delta between frontend offsets for channel 0 and 1
105 constexpr uint32_t SR_FE_CHAN_OFFSET = 16 * PERIPH_REG_OFFSET;
106 constexpr uint32_t SR_TX_FE_BASE     = PERIPH_BASE + 208 * PERIPH_REG_OFFSET;
107 constexpr uint32_t SR_RX_FE_BASE     = PERIPH_BASE + 224 * PERIPH_REG_OFFSET;
108 
109 } // namespace x300_regs
110 
111 class x300_radio_control_impl : public radio_control_impl,
112                                 public uhd::usrp::x300::x300_radio_mbc_iface
113 {
114 public:
RFNOC_RADIO_CONSTRUCTOR(x300_radio_control)115     RFNOC_RADIO_CONSTRUCTOR(x300_radio_control)
116     , _radio_type(get_block_id().get_block_count() == 0 ? PRIMARY : SECONDARY)
117     {
118         RFNOC_LOG_TRACE("Initializing x300_radio_control, slot "
119                         << x300_radio_control_impl::get_slot_name());
120         UHD_ASSERT_THROW(get_mb_controller());
121         _x300_mb_control =
122             std::dynamic_pointer_cast<x300_mb_controller>(get_mb_controller());
123         UHD_ASSERT_THROW(_x300_mb_control);
124         _x300_mb_control->register_radio(this);
125         // MCR is locked for this session
126         _master_clock_rate = _x300_mb_control->get_clock_ctrl()->get_master_clock_rate();
127         UHD_ASSERT_THROW(get_tick_rate() == _master_clock_rate);
128         radio_control_impl::set_rate(_master_clock_rate);
129 
130         ////////////////////////////////////////////////////////////////
131         // Setup peripherals
132         ////////////////////////////////////////////////////////////////
133         // The X300 only requires a single timed_wb_iface, even for TwinRX
134         _wb_iface = RFNOC_MAKE_WB_IFACE(0, 0);
135 
136         RFNOC_LOG_TRACE("Creating SPI interface...");
137         _spi = spi_core_3000::make(
138             [this](const uint32_t addr, const uint32_t data) {
139                 regs().poke32(addr, data, get_command_time(0));
140             },
141             [this](
142                 const uint32_t addr) { return regs().peek32(addr, get_command_time(0)); },
143             x300_regs::SR_SPI,
144             8,
145             x300_regs::RB_SPI);
146         // DAC/ADC
147         RFNOC_LOG_TRACE("Running init_codec...");
148         // Note: ADC calibration and DAC sync happen in x300_mb_controller
149         _init_codecs();
150         _x300_mb_control->register_reset_codec_cb([this]() { this->reset_codec(); });
151         // FP-GPIO (the gpio_atr_3000 ctor will initialize default values)
152         RFNOC_LOG_TRACE("Creating FP-GPIO interface...");
153         _fp_gpio = gpio_atr::gpio_atr_3000::make(_wb_iface,
154             x300_regs::SR_FP_GPIO,
155             x300_regs::RB_FP_GPIO,
156             x300_regs::PERIPH_REG_OFFSET);
157         // Create the GPIO banks and attributes, and populate them with some default
158         // values
159         // TODO: Do we need this section? Since the _fp_gpio handles state now, we
160         // don't need to stash values here. We only need this if we want to set
161         // anything to a default value.
162         for (const gpio_atr::gpio_attr_map_t::value_type attr : gpio_atr::gpio_attr_map) {
163             // TODO: Default values?
164             if (attr.first == usrp::gpio_atr::GPIO_SRC) {
165                 // Don't set the SRC
166                 // TODO: Remove from the map??
167                 continue;
168             }
169             set_gpio_attr("FP0", usrp::gpio_atr::gpio_attr_map.at(attr.first), 0);
170         }
171         // DB Initialization
172         _init_db(); // This does not init the dboards themselves!
173 
174         // LEDs are technically valid for both RX and TX, but let's put them
175         // here
176         _leds = gpio_atr::gpio_atr_3000::make_write_only(
177             _wb_iface, x300_regs::SR_LEDS, x300_regs::PERIPH_REG_OFFSET);
178         _leds->set_atr_mode(
179             usrp::gpio_atr::MODE_ATR, usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL);
180         // We always want to initialize at least one frontend core for both TX and RX
181         // RX periphs
182         for (size_t i = 0; i < std::max<size_t>(get_num_output_ports(), 1); i++) {
183             _rx_fe_map[i].core = rx_frontend_core_3000::make(_wb_iface,
184                 x300_regs::SR_RX_FE_BASE + i * x300_regs::SR_FE_CHAN_OFFSET,
185                 x300_regs::PERIPH_REG_OFFSET);
186             _rx_fe_map[i].core->set_adc_rate(
187                 _x300_mb_control->get_clock_ctrl()->get_master_clock_rate());
188             _rx_fe_map[i].core->set_dc_offset(
189                 rx_frontend_core_3000::DEFAULT_DC_OFFSET_VALUE);
190             _rx_fe_map[i].core->set_dc_offset_auto(
191                 rx_frontend_core_3000::DEFAULT_DC_OFFSET_ENABLE);
192             _rx_fe_map[i].core->populate_subtree(
193                 get_tree()->subtree(FE_PATH / "rx_fe_corrections" / i));
194         }
195         // TX Periphs
196         for (size_t i = 0; i < std::max<size_t>(get_num_input_ports(), 1); i++) {
197             _tx_fe_map[i].core = tx_frontend_core_200::make(_wb_iface,
198                 x300_regs::SR_TX_FE_BASE + i * x300_regs::SR_FE_CHAN_OFFSET,
199                 x300_regs::PERIPH_REG_OFFSET);
200             _tx_fe_map[i].core->set_dc_offset(
201                 tx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE);
202             _tx_fe_map[i].core->set_iq_balance(
203                 tx_frontend_core_200::DEFAULT_IQ_BALANCE_VALUE);
204             _tx_fe_map[i].core->populate_subtree(
205                 get_tree()->subtree(FE_PATH / "tx_fe_corrections" / i));
206         }
207 
208         // Dboards
209         _init_dboards();
210 
211         // Properties
212         for (auto& samp_rate_prop : _samp_rate_in) {
213             set_property(
214                 samp_rate_prop.get_id(), get_rate(), samp_rate_prop.get_src_info());
215         }
216         for (auto& samp_rate_prop : _samp_rate_out) {
217             set_property(
218                 samp_rate_prop.get_id(), get_rate(), samp_rate_prop.get_src_info());
219         }
220     } /* ctor */
221 
~x300_radio_control_impl()222     ~x300_radio_control_impl()
223     {
224         // nop
225     }
226 
227     /**************************************************************************
228      * Radio API calls
229      *************************************************************************/
set_rate(double rate)230     double set_rate(double rate)
231     {
232         // On X3x0, tick rate can't actually be changed at runtime
233         const double actual_rate = get_rate();
234         if (not uhd::math::frequencies_are_equal(rate, actual_rate)) {
235             RFNOC_LOG_WARNING("Requesting invalid sampling rate from device: "
236                               << (rate / 1e6) << " MHz. Actual rate is: "
237                               << (actual_rate / 1e6) << " MHz.");
238         }
239         return actual_rate;
240     }
241 
set_tx_antenna(const std::string & ant,const size_t chan)242     void set_tx_antenna(const std::string& ant, const size_t chan)
243     {
244         // Antenna changes may result in a change of streaming mode for LF/Basic dboards
245         if (_basic_lf_tx) {
246             if (not uhd::usrp::dboard::basic_and_lf::antenna_mode_to_conn.has_key(ant)) {
247                 throw uhd::lookup_error(
248                     str(boost::format("Invalid antenna mode: %s") % ant));
249             }
250             const std::string connection =
251                 uhd::usrp::dboard::basic_and_lf::antenna_mode_to_conn[ant];
252             _tx_fe_map[chan].core->set_mux(connection);
253         }
254         get_tree()
255             ->access<std::string>(get_db_path("tx", chan) / "antenna" / "value")
256             .set(ant);
257     }
258 
get_tx_antenna(const size_t chan) const259     std::string get_tx_antenna(const size_t chan) const
260     {
261         return get_tree()
262             ->access<std::string>(get_db_path("tx", chan) / "antenna" / "value")
263             .get();
264     }
265 
get_tx_antennas(size_t chan) const266     std::vector<std::string> get_tx_antennas(size_t chan) const
267     {
268         return get_tree()
269             ->access<std::vector<std::string>>(
270                 get_db_path("tx", chan) / "antenna" / "options")
271             .get();
272     }
273 
set_rx_antenna(const std::string & ant,const size_t chan)274     void set_rx_antenna(const std::string& ant, const size_t chan)
275     {
276         // Antenna changes may result in a change of streaming mode for LF/Basic dboards
277         if (_basic_lf_rx) {
278             if (not uhd::usrp::dboard::basic_and_lf::antenna_mode_to_conn.has_key(ant)) {
279                 throw uhd::lookup_error(
280                     str(boost::format("Invalid antenna mode: %s") % ant));
281             }
282             const std::string connection =
283                 uhd::usrp::dboard::basic_and_lf::antenna_mode_to_conn[ant];
284             const double if_freq = 0.0;
285             _rx_fe_map[chan].core->set_fe_connection(
286                 usrp::fe_connection_t(connection, if_freq));
287         }
288         get_tree()
289             ->access<std::string>(get_db_path("rx", chan) / "antenna" / "value")
290             .set(ant);
291     }
292 
get_rx_antenna(const size_t chan) const293     std::string get_rx_antenna(const size_t chan) const
294     {
295         return get_tree()
296             ->access<std::string>(get_db_path("rx", chan) / "antenna" / "value")
297             .get();
298     }
299 
get_rx_antennas(size_t chan) const300     std::vector<std::string> get_rx_antennas(size_t chan) const
301     {
302         return get_tree()
303             ->access<std::vector<std::string>>(
304                 get_db_path("rx", chan) / "antenna" / "options")
305             .get();
306     }
307 
set_tx_frequency(const double freq,const size_t chan)308     double set_tx_frequency(const double freq, const size_t chan)
309     {
310         return get_tree()
311             ->access<double>(get_db_path("tx", chan) / "freq" / "value")
312             .set(freq)
313             .get();
314     }
315 
set_tx_tune_args(const uhd::device_addr_t & tune_args,const size_t chan)316     void set_tx_tune_args(const uhd::device_addr_t& tune_args, const size_t chan)
317     {
318         if (get_tree()->exists(get_db_path("tx", chan) / "tune_args")) {
319             get_tree()
320                 ->access<uhd::device_addr_t>(get_db_path("tx", chan) / "tune_args")
321                 .set(tune_args);
322         }
323     }
324 
get_tx_frequency(const size_t chan)325     double get_tx_frequency(const size_t chan)
326     {
327         return get_tree()
328             ->access<double>(get_db_path("tx", chan) / "freq" / "value")
329             .get();
330     }
331 
set_rx_frequency(const double freq,const size_t chan)332     double set_rx_frequency(const double freq, const size_t chan)
333     {
334         RFNOC_LOG_TRACE(
335             "set_rx_frequency(freq=" << (freq / 1e6) << " MHz, chan=" << chan << ")");
336         return get_tree()
337             ->access<double>(get_db_path("rx", chan) / "freq" / "value")
338             .set(freq)
339             .get();
340     }
341 
set_rx_tune_args(const uhd::device_addr_t & tune_args,const size_t chan)342     void set_rx_tune_args(const uhd::device_addr_t& tune_args, const size_t chan)
343     {
344         if (get_tree()->exists(get_db_path("rx", chan) / "tune_args")) {
345             get_tree()
346                 ->access<uhd::device_addr_t>(get_db_path("rx", chan) / "tune_args")
347                 .set(tune_args);
348         }
349     }
350 
get_rx_frequency(const size_t chan)351     double get_rx_frequency(const size_t chan)
352     {
353         return get_tree()
354             ->access<double>(get_db_path("rx", chan) / "freq" / "value")
355             .get();
356     }
357 
get_tx_frequency_range(const size_t chan) const358     uhd::freq_range_t get_tx_frequency_range(const size_t chan) const
359     {
360         return get_tree()
361             ->access<uhd::freq_range_t>(get_db_path("tx", chan) / "freq" / "range")
362             .get();
363     }
364 
get_rx_frequency_range(const size_t chan) const365     uhd::freq_range_t get_rx_frequency_range(const size_t chan) const
366     {
367         return get_tree()
368             ->access<uhd::meta_range_t>(get_db_path("rx", chan) / "freq" / "range")
369             .get();
370     }
371 
372     /*** Bandwidth-Related APIs************************************************/
set_rx_bandwidth(const double bandwidth,const size_t chan)373     double set_rx_bandwidth(const double bandwidth, const size_t chan)
374     {
375         return get_tree()
376             ->access<double>(get_db_path("rx", chan) / "bandwidth" / "value")
377             .set(bandwidth)
378             .get();
379     }
380 
get_rx_bandwidth(const size_t chan)381     double get_rx_bandwidth(const size_t chan)
382     {
383         return get_tree()
384             ->access<double>(get_db_path("rx", chan) / "bandwidth" / "value")
385             .get();
386     }
387 
get_rx_bandwidth_range(size_t chan) const388     uhd::meta_range_t get_rx_bandwidth_range(size_t chan) const
389     {
390         return get_tree()
391             ->access<uhd::meta_range_t>(get_db_path("rx", chan) / "bandwidth" / "range")
392             .get();
393     }
394 
set_tx_bandwidth(const double bandwidth,const size_t chan)395     double set_tx_bandwidth(const double bandwidth, const size_t chan)
396     {
397         return get_tree()
398             ->access<double>(get_db_path("tx", chan) / "bandwidth" / "value")
399             .set(bandwidth)
400             .get();
401     }
402 
get_tx_bandwidth(const size_t chan)403     double get_tx_bandwidth(const size_t chan)
404     {
405         return get_tree()
406             ->access<double>(get_db_path("tx", chan) / "bandwidth" / "value")
407             .get();
408     }
409 
get_tx_bandwidth_range(size_t chan) const410     uhd::meta_range_t get_tx_bandwidth_range(size_t chan) const
411     {
412         return get_tree()
413             ->access<uhd::meta_range_t>(get_db_path("tx", chan) / "bandwidth" / "range")
414             .get();
415     }
416 
417     /*** Gain-Related APIs ***************************************************/
set_tx_gain(const double gain,const size_t chan)418     double set_tx_gain(const double gain, const size_t chan)
419     {
420         return set_tx_gain(gain, ALL_GAINS, chan);
421     }
422 
set_tx_gain(const double gain,const std::string & name,const size_t chan)423     double set_tx_gain(const double gain, const std::string& name, const size_t chan)
424     {
425         _tx_pwr_mgr.at(chan)->set_tracking_mode(pwr_cal_mgr::tracking_mode::TRACK_GAIN);
426         if (_tx_gain_groups.count(chan)) {
427             auto& gg = _tx_gain_groups.at(chan);
428             gg->set_value(gain, name);
429             return radio_control_impl::set_tx_gain(gg->get_value(name), chan);
430         }
431         return radio_control_impl::set_tx_gain(0.0, chan);
432     }
433 
set_rx_gain(const double gain,const size_t chan)434     double set_rx_gain(const double gain, const size_t chan)
435     {
436         return set_rx_gain(gain, ALL_GAINS, chan);
437     }
438 
set_rx_gain(const double gain,const std::string & name,const size_t chan)439     double set_rx_gain(const double gain, const std::string& name, const size_t chan)
440     {
441         _rx_pwr_mgr.at(chan)->set_tracking_mode(pwr_cal_mgr::tracking_mode::TRACK_GAIN);
442         auto& gg = _rx_gain_groups.at(chan);
443         gg->set_value(gain, name);
444         return radio_control_impl::set_rx_gain(gg->get_value(name), chan);
445     }
446 
get_rx_gain(const size_t chan)447     double get_rx_gain(const size_t chan)
448     {
449         return get_rx_gain(ALL_GAINS, chan);
450     }
451 
get_rx_gain(const std::string & name,const size_t chan)452     double get_rx_gain(const std::string& name, const size_t chan)
453     {
454         return _rx_gain_groups.at(chan)->get_value(name);
455     }
456 
get_tx_gain(const size_t chan)457     double get_tx_gain(const size_t chan)
458     {
459         return get_tx_gain(ALL_GAINS, chan);
460     }
461 
get_tx_gain(const std::string & name,const size_t chan)462     double get_tx_gain(const std::string& name, const size_t chan)
463     {
464         return _tx_gain_groups.at(chan)->get_value(name);
465     }
466 
get_tx_gain_names(const size_t chan) const467     std::vector<std::string> get_tx_gain_names(const size_t chan) const
468     {
469         return _tx_gain_groups.at(chan)->get_names();
470     }
471 
get_rx_gain_names(const size_t chan) const472     std::vector<std::string> get_rx_gain_names(const size_t chan) const
473     {
474         return _rx_gain_groups.at(chan)->get_names();
475     }
476 
get_tx_gain_range(const size_t chan) const477     uhd::gain_range_t get_tx_gain_range(const size_t chan) const
478     {
479         return get_tx_gain_range(ALL_GAINS, chan);
480     }
481 
get_tx_gain_range(const std::string & name,const size_t chan) const482     uhd::gain_range_t get_tx_gain_range(const std::string& name, const size_t chan) const
483     {
484         if (!_tx_gain_groups.count(chan)) {
485             throw uhd::index_error(
486                 "Trying to access invalid TX gain group: " + std::to_string(chan));
487         }
488         return _tx_gain_groups.at(chan)->get_range(name);
489     }
490 
get_rx_gain_range(const size_t chan) const491     uhd::gain_range_t get_rx_gain_range(const size_t chan) const
492     {
493         return get_rx_gain_range(ALL_GAINS, chan);
494     }
495 
get_rx_gain_range(const std::string & name,const size_t chan) const496     uhd::gain_range_t get_rx_gain_range(const std::string& name, const size_t chan) const
497     {
498         if (!_rx_gain_groups.count(chan)) {
499             throw uhd::index_error(
500                 "Trying to access invalid RX gain group: " + std::to_string(chan));
501         }
502         return _rx_gain_groups.at(chan)->get_range(name);
503     }
504 
get_tx_gain_profile_names(const size_t chan) const505     std::vector<std::string> get_tx_gain_profile_names(const size_t chan) const
506     {
507         return get_tree()
508             ->access<std::vector<std::string>>(
509                 get_db_path("tx", chan) / "gains/all/profile/options")
510             .get();
511     }
512 
get_rx_gain_profile_names(const size_t chan) const513     std::vector<std::string> get_rx_gain_profile_names(const size_t chan) const
514     {
515         return get_tree()
516             ->access<std::vector<std::string>>(
517                 get_db_path("rx", chan) / "gains/all/profile/options")
518             .get();
519     }
520 
521 
set_tx_gain_profile(const std::string & profile,const size_t chan)522     void set_tx_gain_profile(const std::string& profile, const size_t chan)
523     {
524         get_tree()
525             ->access<std::string>(get_db_path("tx", chan) / "gains/all/profile/value")
526             .set(profile);
527     }
528 
set_rx_gain_profile(const std::string & profile,const size_t chan)529     void set_rx_gain_profile(const std::string& profile, const size_t chan)
530     {
531         get_tree()
532             ->access<std::string>(get_db_path("rx", chan) / "gains/all/profile/value")
533             .set(profile);
534     }
535 
536 
get_tx_gain_profile(const size_t chan) const537     std::string get_tx_gain_profile(const size_t chan) const
538     {
539         return get_tree()
540             ->access<std::string>(get_db_path("tx", chan) / "gains/all/profile/value")
541             .get();
542     }
543 
get_rx_gain_profile(const size_t chan) const544     std::string get_rx_gain_profile(const size_t chan) const
545     {
546         return get_tree()
547             ->access<std::string>(get_db_path("rx", chan) / "gains/all/profile/value")
548             .get();
549     }
550 
551     /**************************************************************************
552      * LO controls
553      *************************************************************************/
get_rx_lo_names(const size_t chan) const554     std::vector<std::string> get_rx_lo_names(const size_t chan) const
555     {
556         fs_path rx_fe_fe_root = get_db_path("rx", chan);
557         std::vector<std::string> lo_names;
558         if (get_tree()->exists(rx_fe_fe_root / "los")) {
559             for (const std::string& name : get_tree()->list(rx_fe_fe_root / "los")) {
560                 lo_names.push_back(name);
561             }
562         }
563         return lo_names;
564     }
565 
get_rx_lo_sources(const std::string & name,const size_t chan) const566     std::vector<std::string> get_rx_lo_sources(
567         const std::string& name, const size_t chan) const
568     {
569         fs_path rx_fe_fe_root = get_db_path("rx", chan);
570 
571         if (get_tree()->exists(rx_fe_fe_root / "los")) {
572             if (name == ALL_LOS) {
573                 if (get_tree()->exists(rx_fe_fe_root / "los" / ALL_LOS)) {
574                     // Special value ALL_LOS support atomically sets the source for all
575                     // LOs
576                     return get_tree()
577                         ->access<std::vector<std::string>>(
578                             rx_fe_fe_root / "los" / ALL_LOS / "source" / "options")
579                         .get();
580                 } else {
581                     return std::vector<std::string>();
582                 }
583             } else {
584                 if (get_tree()->exists(rx_fe_fe_root / "los")) {
585                     return get_tree()
586                         ->access<std::vector<std::string>>(
587                             rx_fe_fe_root / "los" / name / "source" / "options")
588                         .get();
589                 } else {
590                     throw uhd::runtime_error("Could not find LO stage " + name);
591                 }
592             }
593         } else {
594             // If the daughterboard doesn't expose it's LO(s) then it can only be internal
595             return std::vector<std::string>(1, "internal");
596         }
597     }
598 
set_rx_lo_source(const std::string & src,const std::string & name,const size_t chan)599     void set_rx_lo_source(
600         const std::string& src, const std::string& name, const size_t chan)
601     {
602         fs_path rx_fe_fe_root = get_db_path("rx", chan);
603 
604         if (get_tree()->exists(rx_fe_fe_root / "los")) {
605             if (name == ALL_LOS) {
606                 if (get_tree()->exists(rx_fe_fe_root / "los" / ALL_LOS)) {
607                     // Special value ALL_LOS support atomically sets the source for all
608                     // LOs
609                     get_tree()
610                         ->access<std::string>(
611                             rx_fe_fe_root / "los" / ALL_LOS / "source" / "value")
612                         .set(src);
613                 } else {
614                     for (const std::string& n : get_tree()->list(rx_fe_fe_root / "los")) {
615                         this->set_rx_lo_source(src, n, chan);
616                     }
617                 }
618             } else {
619                 if (get_tree()->exists(rx_fe_fe_root / "los")) {
620                     get_tree()
621                         ->access<std::string>(
622                             rx_fe_fe_root / "los" / name / "source" / "value")
623                         .set(src);
624                 } else {
625                     throw uhd::runtime_error("Could not find LO stage " + name);
626                 }
627             }
628         } else {
629             throw uhd::runtime_error(
630                 "This device does not support manual configuration of LOs");
631         }
632     }
633 
get_rx_lo_source(const std::string & name,const size_t chan)634     const std::string get_rx_lo_source(const std::string& name, const size_t chan)
635     {
636         fs_path rx_fe_fe_root = get_db_path("rx", chan);
637 
638         if (get_tree()->exists(rx_fe_fe_root / "los")) {
639             if (name == ALL_LOS) {
640                 // Special value ALL_LOS support atomically sets the source for all LOs
641                 return get_tree()
642                     ->access<std::string>(
643                         rx_fe_fe_root / "los" / ALL_LOS / "source" / "value")
644                     .get();
645             } else {
646                 if (get_tree()->exists(rx_fe_fe_root / "los")) {
647                     return get_tree()
648                         ->access<std::string>(
649                             rx_fe_fe_root / "los" / name / "source" / "value")
650                         .get();
651                 } else {
652                     throw uhd::runtime_error("Could not find LO stage " + name);
653                 }
654             }
655         } else {
656             // If the daughterboard doesn't expose it's LO(s) then it can only be internal
657             return "internal";
658         }
659     }
660 
set_rx_lo_export_enabled(bool enabled,const std::string & name,const size_t chan)661     void set_rx_lo_export_enabled(
662         bool enabled, const std::string& name, const size_t chan)
663     {
664         fs_path rx_fe_fe_root = get_db_path("rx", chan);
665 
666         if (get_tree()->exists(rx_fe_fe_root / "los")) {
667             if (name == ALL_LOS) {
668                 if (get_tree()->exists(rx_fe_fe_root / "los" / ALL_LOS)) {
669                     // Special value ALL_LOS support atomically sets the source for all
670                     // LOs
671                     get_tree()
672                         ->access<bool>(rx_fe_fe_root / "los" / ALL_LOS / "export")
673                         .set(enabled);
674                 } else {
675                     for (const std::string& n : get_tree()->list(rx_fe_fe_root / "los")) {
676                         this->set_rx_lo_export_enabled(enabled, n, chan);
677                     }
678                 }
679             } else {
680                 if (get_tree()->exists(rx_fe_fe_root / "los")) {
681                     get_tree()
682                         ->access<bool>(rx_fe_fe_root / "los" / name / "export")
683                         .set(enabled);
684                 } else {
685                     throw uhd::runtime_error("Could not find LO stage " + name);
686                 }
687             }
688         } else {
689             throw uhd::runtime_error(
690                 "This device does not support manual configuration of LOs");
691         }
692     }
693 
get_rx_lo_export_enabled(const std::string & name,const size_t chan) const694     bool get_rx_lo_export_enabled(const std::string& name, const size_t chan) const
695     {
696         fs_path rx_fe_fe_root = get_db_path("rx", chan);
697 
698         if (get_tree()->exists(rx_fe_fe_root / "los")) {
699             if (name == ALL_LOS) {
700                 // Special value ALL_LOS support atomically sets the source for all LOs
701                 return get_tree()
702                     ->access<bool>(rx_fe_fe_root / "los" / ALL_LOS / "export")
703                     .get();
704             } else {
705                 if (get_tree()->exists(rx_fe_fe_root / "los")) {
706                     return get_tree()
707                         ->access<bool>(rx_fe_fe_root / "los" / name / "export")
708                         .get();
709                 } else {
710                     throw uhd::runtime_error("Could not find LO stage " + name);
711                 }
712             }
713         } else {
714             // If the daughterboard doesn't expose it's LO(s), assume it cannot export
715             return false;
716         }
717     }
718 
set_rx_lo_freq(double freq,const std::string & name,const size_t chan)719     double set_rx_lo_freq(double freq, const std::string& name, const size_t chan)
720     {
721         fs_path rx_fe_fe_root = get_db_path("rx", chan);
722 
723         if (get_tree()->exists(rx_fe_fe_root / "los")) {
724             if (name == ALL_LOS) {
725                 throw uhd::runtime_error(
726                     "LO frequency must be set for each stage individually");
727             } else {
728                 if (get_tree()->exists(rx_fe_fe_root / "los")) {
729                     get_tree()
730                         ->access<double>(rx_fe_fe_root / "los" / name / "freq" / "value")
731                         .set(freq);
732                     return get_tree()
733                         ->access<double>(rx_fe_fe_root / "los" / name / "freq" / "value")
734                         .get();
735                 } else {
736                     throw uhd::runtime_error("Could not find LO stage " + name);
737                 }
738             }
739         } else {
740             throw uhd::runtime_error(
741                 "This device does not support manual configuration of LOs");
742         }
743     }
744 
get_rx_lo_freq(const std::string & name,const size_t chan)745     double get_rx_lo_freq(const std::string& name, const size_t chan)
746     {
747         fs_path rx_fe_fe_root = get_db_path("rx", chan);
748 
749         if (get_tree()->exists(rx_fe_fe_root / "los")) {
750             if (name == ALL_LOS) {
751                 throw uhd::runtime_error(
752                     "LO frequency must be retrieved for each stage individually");
753             } else {
754                 if (get_tree()->exists(rx_fe_fe_root / "los")) {
755                     return get_tree()
756                         ->access<double>(rx_fe_fe_root / "los" / name / "freq" / "value")
757                         .get();
758                 } else {
759                     throw uhd::runtime_error("Could not find LO stage " + name);
760                 }
761             }
762         } else {
763             // Return actual RF frequency if the daughterboard doesn't expose its LO(s)
764             return get_tree()->access<double>(rx_fe_fe_root / "freq" / " value").get();
765         }
766     }
767 
get_rx_lo_freq_range(const std::string & name,const size_t chan) const768     freq_range_t get_rx_lo_freq_range(const std::string& name, const size_t chan) const
769     {
770         fs_path rx_fe_fe_root = get_db_path("rx", chan);
771 
772         if (get_tree()->exists(rx_fe_fe_root / "los")) {
773             if (name == ALL_LOS) {
774                 throw uhd::runtime_error(
775                     "LO frequency range must be retrieved for each stage individually");
776             } else {
777                 if (get_tree()->exists(rx_fe_fe_root / "los")) {
778                     return get_tree()
779                         ->access<freq_range_t>(
780                             rx_fe_fe_root / "los" / name / "freq" / "range")
781                         .get();
782                 } else {
783                     throw uhd::runtime_error("Could not find LO stage " + name);
784                 }
785             }
786         } else {
787             // Return the actual RF range if the daughterboard doesn't expose its LO(s)
788             return get_tree()
789                 ->access<meta_range_t>(rx_fe_fe_root / "freq" / "range")
790                 .get();
791         }
792     }
793 
794     /*** Calibration API *****************************************************/
set_tx_dc_offset(const std::complex<double> & offset,size_t chan)795     void set_tx_dc_offset(const std::complex<double>& offset, size_t chan)
796     {
797         const fs_path dc_offset_path = get_fe_path("tx", chan) / "dc_offset" / "value";
798         if (get_tree()->exists(dc_offset_path)) {
799             get_tree()->access<std::complex<double>>(dc_offset_path).set(offset);
800         } else {
801             RFNOC_LOG_WARNING("Setting TX DC offset is not possible on this device.");
802         }
803     }
804 
get_tx_dc_offset_range(size_t chan) const805     meta_range_t get_tx_dc_offset_range(size_t chan) const
806     {
807         const fs_path range_path = get_fe_path("tx", chan) / "dc_offset" / "range";
808         if (get_tree()->exists(range_path)) {
809             return get_tree()->access<uhd::meta_range_t>(range_path).get();
810         } else {
811             RFNOC_LOG_WARNING(
812                 "This device does not support querying the TX DC offset range.");
813             return meta_range_t(0, 0);
814         }
815     }
816 
set_tx_iq_balance(const std::complex<double> & correction,size_t chan)817     void set_tx_iq_balance(const std::complex<double>& correction, size_t chan)
818     {
819         const fs_path iq_balance_path = get_fe_path("tx", chan) / "iq_balance" / "value";
820         if (get_tree()->exists(iq_balance_path)) {
821             get_tree()->access<std::complex<double>>(iq_balance_path).set(correction);
822         } else {
823             RFNOC_LOG_WARNING("Setting TX IQ Balance is not possible on this device.");
824         }
825     }
826 
set_rx_dc_offset(const bool enb,size_t chan)827     void set_rx_dc_offset(const bool enb, size_t chan)
828     {
829         const fs_path dc_offset_path = get_fe_path("rx", chan) / "dc_offset" / "enable";
830         if (get_tree()->exists(dc_offset_path)) {
831             get_tree()->access<bool>(dc_offset_path).set(enb);
832         } else {
833             RFNOC_LOG_WARNING(
834                 "Setting DC offset compensation is not possible on this device.");
835         }
836     }
837 
set_rx_dc_offset(const std::complex<double> & offset,size_t chan)838     void set_rx_dc_offset(const std::complex<double>& offset, size_t chan)
839     {
840         const fs_path dc_offset_path = get_fe_path("rx", chan) / "dc_offset" / "value";
841         if (get_tree()->exists(dc_offset_path)) {
842             get_tree()->access<std::complex<double>>(dc_offset_path).set(offset);
843         } else {
844             RFNOC_LOG_WARNING("Setting RX DC offset is not possible on this device.");
845         }
846     }
847 
get_rx_dc_offset_range(size_t chan) const848     meta_range_t get_rx_dc_offset_range(size_t chan) const
849     {
850         const fs_path range_path = get_fe_path("rx", chan) / "dc_offset" / "range";
851         if (get_tree()->exists(range_path)) {
852             return get_tree()->access<uhd::meta_range_t>(range_path).get();
853         } else {
854             RFNOC_LOG_WARNING(
855                 "This device does not support querying the rx DC offset range.");
856             return meta_range_t(0, 0);
857         }
858     }
859 
set_rx_iq_balance(const bool enb,size_t chan)860     void set_rx_iq_balance(const bool enb, size_t chan)
861     {
862         const fs_path iq_balance_path = get_fe_path("rx", chan) / "iq_balance" / "enable";
863         if (get_tree()->exists(iq_balance_path)) {
864             get_tree()->access<bool>(iq_balance_path).set(enb);
865         } else {
866             RFNOC_LOG_WARNING("Setting RX IQ Balance is not possible on this device.");
867         }
868     }
869 
set_rx_iq_balance(const std::complex<double> & correction,size_t chan)870     void set_rx_iq_balance(const std::complex<double>& correction, size_t chan)
871     {
872         const fs_path iq_balance_path = get_fe_path("rx", chan) / "iq_balance" / "value";
873         if (get_tree()->exists(iq_balance_path)) {
874             get_tree()->access<std::complex<double>>(iq_balance_path).set(correction);
875         } else {
876             RFNOC_LOG_WARNING("Setting RX IQ Balance is not possible on this device.");
877         }
878     }
879 
880     /*** GPIO API ************************************************************/
get_gpio_banks() const881     std::vector<std::string> get_gpio_banks() const
882     {
883         return {"RX", "TX", "FP0"};
884     }
885 
set_gpio_attr(const std::string & bank,const std::string & attr,const uint32_t value)886     void set_gpio_attr(
887         const std::string& bank, const std::string& attr, const uint32_t value)
888     {
889         if (bank == "FP0") {
890             _fp_gpio->set_gpio_attr(usrp::gpio_atr::gpio_attr_rev_map.at(attr), value);
891             return;
892         }
893         if (bank.size() >= 2 and bank[1] == 'X') {
894             const std::string name          = bank.substr(2);
895             const dboard_iface::unit_t unit = (bank[0] == 'R') ? dboard_iface::UNIT_RX
896                                                                : dboard_iface::UNIT_TX;
897             constexpr uint16_t mask = 0xFFFF;
898             if (attr == "CTRL") {
899                 _db_iface->set_pin_ctrl(unit, value, mask);
900             } else if (attr == "DDR") {
901                 _db_iface->set_gpio_ddr(unit, value, mask);
902             } else if (attr == "OUT") {
903                 _db_iface->set_gpio_out(unit, value, mask);
904             } else if (attr == "ATR_0X") {
905                 _db_iface->set_atr_reg(unit, gpio_atr::ATR_REG_IDLE, value, mask);
906             } else if (attr == "ATR_RX") {
907                 _db_iface->set_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY, value, mask);
908             } else if (attr == "ATR_TX") {
909                 _db_iface->set_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY, value, mask);
910             } else if (attr == "ATR_XX") {
911                 _db_iface->set_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX, value, mask);
912             } else {
913                 RFNOC_LOG_ERROR("Invalid GPIO attribute name: " << attr);
914                 throw uhd::key_error(std::string("Invalid GPIO attribute name: ") + attr);
915             }
916             return;
917         }
918         RFNOC_LOG_WARNING(
919             "Invalid GPIO bank name: `"
920             << bank
921             << "'. Ignoring call to set_gpio_attr() to retain backward compatibility.");
922     }
923 
get_gpio_attr(const std::string & bank,const std::string & attr)924     uint32_t get_gpio_attr(const std::string& bank, const std::string& attr)
925     {
926         if (bank == "FP0") {
927             return _fp_gpio->get_attr_reg(usrp::gpio_atr::gpio_attr_rev_map.at(attr));
928         }
929         if (bank.size() >= 2 and bank[1] == 'X') {
930             const std::string name          = bank.substr(2);
931             const dboard_iface::unit_t unit = (bank[0] == 'R') ? dboard_iface::UNIT_RX
932                                                                : dboard_iface::UNIT_TX;
933             if (attr == "CTRL")
934                 return _db_iface->get_pin_ctrl(unit);
935             if (attr == "DDR")
936                 return _db_iface->get_gpio_ddr(unit);
937             if (attr == "OUT")
938                 return _db_iface->get_gpio_out(unit);
939             if (attr == "ATR_0X")
940                 return _db_iface->get_atr_reg(unit, gpio_atr::ATR_REG_IDLE);
941             if (attr == "ATR_RX")
942                 return _db_iface->get_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY);
943             if (attr == "ATR_TX")
944                 return _db_iface->get_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY);
945             if (attr == "ATR_XX")
946                 return _db_iface->get_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX);
947             if (attr == "READBACK")
948                 return _db_iface->read_gpio(unit);
949             RFNOC_LOG_ERROR("Invalid GPIO attribute name: " << attr);
950             throw uhd::key_error(std::string("Invalid GPIO attribute name: ") + attr);
951         }
952         RFNOC_LOG_WARNING(
953             "Invalid GPIO bank name: `"
954             << bank
955             << "'. get_gpio_attr() will return 0 to retain backward compatibility.");
956         return 0;
957     }
958 
959     /**************************************************************************
960      * Sensor API
961      *************************************************************************/
get_rx_sensor_names(size_t chan) const962     std::vector<std::string> get_rx_sensor_names(size_t chan) const
963     {
964         const fs_path sensor_path = get_db_path("rx", chan) / "sensors";
965         if (get_tree()->exists(sensor_path)) {
966             return get_tree()->list(sensor_path);
967         }
968         return {};
969     }
970 
get_rx_sensor(const std::string & name,size_t chan)971     uhd::sensor_value_t get_rx_sensor(const std::string& name, size_t chan)
972     {
973         return get_tree()
974             ->access<uhd::sensor_value_t>(get_db_path("rx", chan) / "sensors" / name)
975             .get();
976     }
977 
get_tx_sensor_names(size_t chan) const978     std::vector<std::string> get_tx_sensor_names(size_t chan) const
979     {
980         const fs_path sensor_path = get_db_path("tx", chan) / "sensors";
981         if (get_tree()->exists(sensor_path)) {
982             return get_tree()->list(sensor_path);
983         }
984         return {};
985     }
986 
get_tx_sensor(const std::string & name,size_t chan)987     uhd::sensor_value_t get_tx_sensor(const std::string& name, size_t chan)
988     {
989         return get_tree()
990             ->access<uhd::sensor_value_t>(get_db_path("tx", chan) / "sensors" / name)
991             .get();
992     }
993 
994     /**************************************************************************
995      * EEPROM API
996      *************************************************************************/
set_db_eeprom(const uhd::eeprom_map_t & db_eeprom)997     void set_db_eeprom(const uhd::eeprom_map_t& db_eeprom)
998     {
999         const std::string key_prefix = db_eeprom.count("rx_id") ? "rx_" : "tx_";
1000         const std::string id_key     = key_prefix + "id";
1001         const std::string serial_key = key_prefix + "serial";
1002         const std::string rev_key    = key_prefix + "rev";
1003         if (!(db_eeprom.count(id_key) && db_eeprom.count(serial_key)
1004                 && db_eeprom.count(rev_key))) {
1005             RFNOC_LOG_ERROR("set_db_eeprom() requires id, serial, and rev keys!");
1006             throw uhd::key_error(
1007                 "[X300] set_db_eeprom() requires id, serial, and rev keys!");
1008         }
1009 
1010         dboard_eeprom_t eeprom;
1011         eeprom.id.from_string(bytes_to_str(db_eeprom.at(id_key)));
1012         eeprom.serial   = bytes_to_str(db_eeprom.at(serial_key));
1013         eeprom.revision = bytes_to_str(db_eeprom.at(rev_key));
1014         if (get_tree()->exists(DB_PATH / (key_prefix + "eeprom"))) {
1015             get_tree()
1016                 ->access<dboard_eeprom_t>(DB_PATH / (key_prefix + "eeprom"))
1017                 .set(eeprom);
1018         } else {
1019             RFNOC_LOG_WARNING("Cannot set EEPROM, tree path does not exist.");
1020         }
1021     }
1022 
1023 
get_db_eeprom()1024     uhd::eeprom_map_t get_db_eeprom()
1025     {
1026         uhd::eeprom_map_t result;
1027         if (get_tree()->exists(DB_PATH / "rx_eeprom")) {
1028             const auto rx_eeprom =
1029                 get_tree()->access<dboard_eeprom_t>(DB_PATH / "rx_eeprom").get();
1030             result["rx_id"]     = str_to_bytes(rx_eeprom.id.to_pp_string());
1031             result["rx_serial"] = str_to_bytes(rx_eeprom.serial);
1032             result["rx_rev"]    = str_to_bytes(rx_eeprom.revision);
1033         }
1034         if (get_tree()->exists(DB_PATH / "tx_eeprom")) {
1035             const auto tx_eeprom =
1036                 get_tree()->access<dboard_eeprom_t>(DB_PATH / "tx_eeprom").get();
1037             result["tx_id"]     = str_to_bytes(tx_eeprom.id.to_pp_string());
1038             result["tx_serial"] = str_to_bytes(tx_eeprom.serial);
1039             result["tx_rev"]    = str_to_bytes(tx_eeprom.revision);
1040         }
1041         return result;
1042     }
1043 
1044     /**************************************************************************
1045      * Radio Identification API Calls
1046      *************************************************************************/
get_slot_name() const1047     std::string get_slot_name() const
1048     {
1049         return _radio_type == PRIMARY ? "A" : "B";
1050     }
1051 
get_chan_from_dboard_fe(const std::string & fe,const uhd::direction_t direction) const1052     size_t get_chan_from_dboard_fe(
1053         const std::string& fe, const uhd::direction_t direction) const
1054     {
1055         switch (direction) {
1056             case uhd::TX_DIRECTION:
1057                 return _get_chan_from_map(_tx_fe_map, fe);
1058             case uhd::RX_DIRECTION:
1059                 return _get_chan_from_map(_rx_fe_map, fe);
1060             default:
1061                 UHD_THROW_INVALID_CODE_PATH();
1062         }
1063     }
1064 
get_dboard_fe_from_chan(const size_t chan,const uhd::direction_t direction) const1065     std::string get_dboard_fe_from_chan(
1066         const size_t chan, const uhd::direction_t direction) const
1067     {
1068         switch (direction) {
1069             case uhd::TX_DIRECTION:
1070                 return _tx_fe_map.at(chan).db_fe_name;
1071             case uhd::RX_DIRECTION:
1072                 return _rx_fe_map.at(chan).db_fe_name;
1073             default:
1074                 UHD_THROW_INVALID_CODE_PATH();
1075         }
1076     }
1077 
get_fe_name(const size_t chan,const uhd::direction_t direction) const1078     std::string get_fe_name(const size_t chan, const uhd::direction_t direction) const
1079     {
1080         fs_path name_path =
1081             get_db_path(direction == uhd::RX_DIRECTION ? "rx" : "tx", chan) / "name";
1082         if (!get_tree()->exists(name_path)) {
1083             return get_dboard_fe_from_chan(chan, direction);
1084         }
1085 
1086         return get_tree()->access<std::string>(name_path).get();
1087     }
1088 
1089 
set_command_time(uhd::time_spec_t time,const size_t chan)1090     virtual void set_command_time(uhd::time_spec_t time, const size_t chan)
1091     {
1092         node_t::set_command_time(time, chan);
1093         // This is for TwinRX only:
1094         fs_path cmd_time_path = get_db_path("rx", chan) / "time" / "cmd";
1095         if (get_tree()->exists(cmd_time_path)) {
1096             get_tree()->access<time_spec_t>(cmd_time_path).set(time);
1097         }
1098     }
1099 
1100     /**************************************************************************
1101      * MB Interface API Calls
1102      *************************************************************************/
get_adc_rx_word()1103     uint32_t get_adc_rx_word()
1104     {
1105         return regs().peek32(regmap::RADIO_BASE_ADDR + regmap::REG_RX_DATA);
1106     }
1107 
set_adc_test_word(const std::string & patterna,const std::string & patternb)1108     void set_adc_test_word(const std::string& patterna, const std::string& patternb)
1109     {
1110         _adc->set_test_word(patterna, patternb);
1111     }
1112 
set_adc_checker_enabled(const bool enb)1113     void set_adc_checker_enabled(const bool enb)
1114     {
1115         _regs->misc_outs_reg.write(
1116             radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, enb ? 1 : 0);
1117     }
1118 
get_adc_checker_locked(const bool I)1119     bool get_adc_checker_locked(const bool I)
1120     {
1121         return bool(_regs->misc_ins_reg.read(
1122             I ? radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_LOCKED
1123               : radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_LOCKED));
1124     }
1125 
get_adc_checker_error_code(const bool I)1126     uint32_t get_adc_checker_error_code(const bool I)
1127     {
1128         return _regs->misc_ins_reg.get(
1129             I ? radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_ERROR
1130               : radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_ERROR);
1131     }
1132 
1133     // Documented in x300_radio_mbc_iface.hpp
self_test_adc(const uint32_t ramp_time_ms)1134     void self_test_adc(const uint32_t ramp_time_ms)
1135     {
1136         RFNOC_LOG_DEBUG("Running ADC self-cal...");
1137         // Bypass all front-end corrections
1138         for (size_t i = 0; i < get_num_output_ports(); i++) {
1139             _rx_fe_map[i].core->bypass_all(true);
1140         }
1141 
1142         // Test basic patterns
1143         _adc->set_test_word("ones", "ones");
1144         _check_adc(0xfffcfffc);
1145         _adc->set_test_word("zeros", "zeros");
1146         _check_adc(0x00000000);
1147         _adc->set_test_word("ones", "zeros");
1148         _check_adc(0xfffc0000);
1149         _adc->set_test_word("zeros", "ones");
1150         _check_adc(0x0000fffc);
1151         for (size_t k = 0; k < 14; k++) {
1152             _adc->set_test_word("zeros", "custom", 1 << k);
1153             _check_adc(1 << (k + 2));
1154         }
1155         for (size_t k = 0; k < 14; k++) {
1156             _adc->set_test_word("custom", "zeros", 1 << k);
1157             _check_adc(1 << (k + 18));
1158         }
1159 
1160         // Turn on ramp pattern test
1161         _adc->set_test_word("ramp", "ramp");
1162         _regs->misc_outs_reg.write(
1163             radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0);
1164         // Sleep added for SPI transactions to finish and ramp to start before checker is
1165         // enabled.
1166         std::this_thread::sleep_for(std::chrono::microseconds(1000));
1167         _regs->misc_outs_reg.write(
1168             radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1);
1169 
1170         std::this_thread::sleep_for(std::chrono::milliseconds(ramp_time_ms));
1171         _regs->misc_ins_reg.refresh();
1172 
1173         std::string i_status, q_status;
1174         if (_regs->misc_ins_reg.get(
1175                 radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_LOCKED))
1176             if (_regs->misc_ins_reg.get(
1177                     radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_ERROR))
1178                 i_status = "Bit Errors!";
1179             else
1180                 i_status = "Good";
1181         else
1182             i_status = "Not Locked!";
1183 
1184         if (_regs->misc_ins_reg.get(
1185                 radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_LOCKED))
1186             if (_regs->misc_ins_reg.get(
1187                     radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_ERROR))
1188                 q_status = "Bit Errors!";
1189             else
1190                 q_status = "Good";
1191         else
1192             q_status = "Not Locked!";
1193 
1194         // Return to normal mode
1195         _adc->set_test_word("normal", "normal");
1196 
1197         if ((i_status != "Good") or (q_status != "Good")) {
1198             throw uhd::runtime_error(
1199                 (boost::format("ADC self-test failed for %s. Ramp checker status: "
1200                                "{ADC_A=%s, ADC_B=%s}")
1201                     % get_unique_id() % i_status % q_status)
1202                     .str());
1203         }
1204 
1205         // Restore front-end corrections
1206         for (size_t i = 0; i < get_num_output_ports(); i++) {
1207             _rx_fe_map[i].core->bypass_all(false);
1208         }
1209     }
1210 
sync_dac()1211     void sync_dac()
1212     {
1213         _dac->sync();
1214     }
1215 
set_dac_sync(const bool enb,const uhd::time_spec_t & time)1216     void set_dac_sync(const bool enb, const uhd::time_spec_t& time)
1217     {
1218         if (time != uhd::time_spec_t(0.0)) {
1219             set_command_time(time, 0);
1220         }
1221         _regs->misc_outs_reg.write(
1222             radio_regmap_t::misc_outs_reg_t::DAC_SYNC, enb ? 1 : 0);
1223         if (!enb && time != uhd::time_spec_t(0.0)) {
1224             set_command_time(uhd::time_spec_t(0.0), 0);
1225         }
1226     }
1227 
dac_verify_sync()1228     void dac_verify_sync()
1229     {
1230         _dac->verify_sync();
1231     }
1232 
1233 private:
1234     /**************************************************************************
1235      * ADC Control
1236      *************************************************************************/
1237     //! Create the ADC/DAC objects, reset them, run ADC cal
_init_codecs()1238     void _init_codecs()
1239     {
1240         _regs = std::make_unique<radio_regmap_t>(get_block_id().get_block_count());
1241         _regs->initialize(*_wb_iface, true);
1242         // Only Radio0 has the ADC/DAC reset bits
1243         if (_radio_type == PRIMARY) {
1244             RFNOC_LOG_TRACE("Resetting DAC and ADCs...");
1245             _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1);
1246             _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0);
1247             _regs->misc_outs_reg.flush();
1248             _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 0);
1249             _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 1);
1250             _regs->misc_outs_reg.flush();
1251         }
1252         _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 1);
1253 
1254         RFNOC_LOG_TRACE("Creating ADC interface...");
1255         _adc = x300_adc_ctrl::make(_spi, DB_ADC_SEN);
1256         RFNOC_LOG_TRACE("Creating DAC interface...");
1257         _dac = x300_dac_ctrl::make(_spi, DB_DAC_SEN, _master_clock_rate);
1258         _self_cal_adc_capture_delay();
1259 
1260         ////////////////////////////////////////////////////////////////
1261         // create legacy codec control objects
1262         ////////////////////////////////////////////////////////////////
1263         // DAC has no gains
1264         get_tree()->create<int>("tx_codec/gains");
1265         get_tree()->create<std::string>("tx_codec/name").set("ad9146");
1266         get_tree()->create<std::string>("rx_codec/name").set("ads62p48");
1267         get_tree()
1268             ->create<meta_range_t>("rx_codec/gains/digital/range")
1269             .set(meta_range_t(0, 6.0, 0.5));
1270         get_tree()
1271             ->create<double>("rx_codec/gains/digital/value")
1272             .add_coerced_subscriber([this](const double gain) { _adc->set_gain(gain); })
1273             .set(0);
1274     }
1275 
1276     //! Calibrate delays on the ADC. This needs to happen before every session.
_self_cal_adc_capture_delay()1277     void _self_cal_adc_capture_delay()
1278     {
1279         RFNOC_LOG_TRACE("Running ADC capture delay self-cal...");
1280         constexpr uint32_t NUM_DELAY_STEPS = 32; // The IDELAYE2 element has 32 steps
1281         // Retry self-cal if it fails in warmup situations
1282         constexpr uint32_t NUM_RETRIES   = 2;
1283         constexpr int32_t MIN_WINDOW_LEN = 4;
1284 
1285         int32_t win_start = -1, win_stop = -1;
1286         uint32_t iter = 0;
1287         while (iter++ < NUM_RETRIES) {
1288             for (uint32_t dly_tap = 0; dly_tap < NUM_DELAY_STEPS; dly_tap++) {
1289                 // Apply delay
1290                 _regs->misc_outs_reg.write(
1291                     radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_VAL, dly_tap);
1292                 _regs->misc_outs_reg.write(
1293                     radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_STB, 1);
1294                 _regs->misc_outs_reg.write(
1295                     radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_STB, 0);
1296 
1297                 uint32_t err_code = 0;
1298 
1299                 // -- Test I Channel --
1300                 // Put ADC in ramp test mode. Tie the other channel to all ones.
1301                 _adc->set_test_word("ramp", "ones");
1302                 // Turn on the pattern checker in the FPGA. It will lock when it sees a
1303                 // zero and count deviations from the expected value
1304                 _regs->misc_outs_reg.write(
1305                     radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0);
1306                 _regs->misc_outs_reg.write(
1307                     radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1);
1308                 // 5ms @ 200MHz = 1 million samples
1309                 std::this_thread::sleep_for(std::chrono::milliseconds(5));
1310                 if (_regs->misc_ins_reg.read(
1311                         radio_regmap_t::misc_ins_reg_t::ADC_CHECKER0_I_LOCKED)) {
1312                     err_code += _regs->misc_ins_reg.get(
1313                         radio_regmap_t::misc_ins_reg_t::ADC_CHECKER0_I_ERROR);
1314                 } else {
1315                     err_code += 100; // Increment error code by 100 to indicate no lock
1316                 }
1317 
1318                 // -- Test Q Channel --
1319                 // Put ADC in ramp test mode. Tie the other channel to all ones.
1320                 _adc->set_test_word("ones", "ramp");
1321                 // Turn on the pattern checker in the FPGA. It will lock when it sees a
1322                 // zero and count deviations from the expected value
1323                 _regs->misc_outs_reg.write(
1324                     radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0);
1325                 _regs->misc_outs_reg.write(
1326                     radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1);
1327                 // 5ms @ 200MHz = 1 million samples
1328                 std::this_thread::sleep_for(std::chrono::milliseconds(5));
1329                 if (_regs->misc_ins_reg.read(
1330                         radio_regmap_t::misc_ins_reg_t::ADC_CHECKER0_Q_LOCKED)) {
1331                     err_code += _regs->misc_ins_reg.get(
1332                         radio_regmap_t::misc_ins_reg_t::ADC_CHECKER0_Q_ERROR);
1333                 } else {
1334                     err_code += 100; // Increment error code by 100 to indicate no lock
1335                 }
1336 
1337                 if (err_code == 0) {
1338                     if (win_start == -1) { // This is the first window
1339                         win_start = dly_tap;
1340                         win_stop  = dly_tap;
1341                     } else { // We are extending the window
1342                         win_stop = dly_tap;
1343                     }
1344                 } else {
1345                     if (win_start != -1) { // A valid window turned invalid
1346                         if (win_stop - win_start >= MIN_WINDOW_LEN) {
1347                             break; // Valid window found
1348                         } else {
1349                             win_start = -1; // Reset window
1350                         }
1351                     }
1352                 }
1353                 // UHD_LOGGER_INFO("X300 RADIO") << (boost::format("CapTap=%d, Error=%d")
1354                 // % dly_tap % err_code);
1355             }
1356 
1357             // Retry the self-cal if it fails
1358             if ((win_start == -1 || (win_stop - win_start) < MIN_WINDOW_LEN)
1359                 && iter < NUM_RETRIES /*not last iteration*/) {
1360                 win_start = -1;
1361                 win_stop  = -1;
1362                 std::this_thread::sleep_for(std::chrono::milliseconds(2000));
1363             } else {
1364                 break;
1365             }
1366         }
1367         _adc->set_test_word("normal", "normal");
1368         _regs->misc_outs_reg.write(
1369             radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0);
1370 
1371         if (win_start == -1) {
1372             throw uhd::runtime_error("self_cal_adc_capture_delay: Self calibration "
1373                                      "failed. Convergence error.");
1374         }
1375 
1376         if (win_stop - win_start < MIN_WINDOW_LEN) {
1377             throw uhd::runtime_error(
1378                 "self_cal_adc_capture_delay: Self calibration failed. "
1379                 "Valid window too narrow.");
1380         }
1381 
1382         uint32_t ideal_tap = (win_stop + win_start) / 2;
1383         _regs->misc_outs_reg.write(
1384             radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_VAL, ideal_tap);
1385         _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_STB, 1);
1386         _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_STB, 0);
1387 
1388         double tap_delay = (1.0e12 / 200e6) / (2 * 32); // in ps
1389         RFNOC_LOG_DEBUG(
1390             boost::format("ADC capture delay self-cal done (Tap=%d, Window=%d, "
1391                           "TapDelay=%.3fps, Iter=%d)")
1392             % ideal_tap % (win_stop - win_start) % tap_delay % iter);
1393     }
1394 
1395     //! Verify that the output of the ADC matches an expected \p val
_check_adc(const uint32_t val)1396     void _check_adc(const uint32_t val)
1397     {
1398         // Wait for previous control transaction to flush
1399         get_adc_rx_word();
1400         // Wait for ADC test pattern to propagate
1401         std::this_thread::sleep_for(std::chrono::microseconds(5));
1402         // Read value of RX readback register and verify, adapt for I inversion
1403         // in FPGA
1404         const uint32_t adc_rb = get_adc_rx_word() ^ 0xfffc0000;
1405         if (val != adc_rb) {
1406             RFNOC_LOG_ERROR(boost::format("ADC self-test failed! (Exp=0x%x, Got=0x%x)")
1407                             % val % adc_rb);
1408             throw uhd::runtime_error("ADC self-test failed!");
1409         }
1410     }
1411 
reset_codec()1412     void reset_codec()
1413     {
1414         RFNOC_LOG_TRACE("Start reset_codec");
1415         if (_radio_type == PRIMARY) { // ADC/DAC reset lines only exist in Radio0
1416             _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1);
1417             _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0);
1418             _regs->misc_outs_reg.flush();
1419             _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 0);
1420             _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 1);
1421             _regs->misc_outs_reg.flush();
1422         }
1423         _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 1);
1424         UHD_ASSERT_THROW(bool(_adc));
1425         UHD_ASSERT_THROW(bool(_dac));
1426         _adc->reset();
1427         _dac->reset();
1428         RFNOC_LOG_TRACE("Done reset_codec");
1429     }
1430 
1431     /**************************************************************************
1432      * DBoard
1433      *************************************************************************/
get_db_path(const std::string & dir,const size_t chan) const1434     fs_path get_db_path(const std::string& dir, const size_t chan) const
1435     {
1436         UHD_ASSERT_THROW(dir == "rx" || dir == "tx");
1437         if (dir == "rx" && chan >= get_num_output_ports()) {
1438             throw uhd::key_error("Invalid RX channel: " + std::to_string(chan));
1439         }
1440         if (dir == "tx" && chan >= get_num_input_ports()) {
1441             throw uhd::key_error("Invalid TX channel: " + std::to_string(chan));
1442         }
1443         return DB_PATH / (dir + "_frontends")
1444                / ((dir == "rx") ? _rx_fe_map.at(chan).db_fe_name
1445                                 : _tx_fe_map.at(chan).db_fe_name);
1446     }
1447 
get_fe_path(const std::string & dir,const size_t chan) const1448     fs_path get_fe_path(const std::string& dir, const size_t chan) const
1449     {
1450         UHD_ASSERT_THROW(dir == "rx" || dir == "tx");
1451         if (dir == "rx" && chan >= get_num_output_ports()) {
1452             throw uhd::key_error("Invalid RX channel: " + std::to_string(chan));
1453         }
1454         if (dir == "tx" && chan >= get_num_input_ports()) {
1455             throw uhd::key_error("Invalid TX channel: " + std::to_string(chan));
1456         }
1457         return FE_PATH / (dir + "_fe_corrections")
1458                / ((dir == "rx") ? _rx_fe_map.at(chan).db_fe_name
1459                                 : _tx_fe_map.at(chan).db_fe_name);
1460     }
1461 
_init_db()1462     void _init_db()
1463     {
1464         constexpr size_t BASE_ADDR       = 0x50;
1465         constexpr size_t RX_EEPROM_ADDR  = 0x5;
1466         constexpr size_t TX_EEPROM_ADDR  = 0x4;
1467         constexpr size_t GDB_EEPROM_ADDR = 0x1;
1468         static const std::vector<size_t> EEPROM_ADDRS{
1469             RX_EEPROM_ADDR, TX_EEPROM_ADDR, GDB_EEPROM_ADDR};
1470         static const std::vector<std::string> EEPROM_PATHS{
1471             "rx_eeprom", "tx_eeprom", "gdb_eeprom"};
1472         const size_t DB_OFFSET = (_radio_type == PRIMARY) ? 0x0 : 0x2;
1473         auto zpu_i2c           = _x300_mb_control->get_zpu_i2c();
1474         auto clock             = _x300_mb_control->get_clock_ctrl();
1475         for (size_t i = 0; i < EEPROM_ADDRS.size(); i++) {
1476             const size_t addr = EEPROM_ADDRS[i] + DB_OFFSET;
1477             // Load EEPROM
1478             _db_eeproms[addr].load(*zpu_i2c, BASE_ADDR | addr);
1479             // Use the RFNoC implementation for Basic/LF dboards
1480             uint16_t dboard_pid = _db_eeproms[addr].id.to_uint16();
1481             switch (dboard_pid) {
1482                 case uhd::usrp::dboard::basic_and_lf::BASIC_RX_PID:
1483                 case uhd::usrp::dboard::basic_and_lf::LF_RX_PID:
1484                     dboard_pid |= uhd::usrp::dboard::basic_and_lf::RFNOC_PID_FLAG;
1485                     _db_eeproms[addr].id = dboard_pid;
1486                     _basic_lf_rx         = true;
1487                     break;
1488                 case uhd::usrp::dboard::basic_and_lf::BASIC_TX_PID:
1489                 case uhd::usrp::dboard::basic_and_lf::LF_TX_PID:
1490                     dboard_pid |= uhd::usrp::dboard::basic_and_lf::RFNOC_PID_FLAG;
1491                     _db_eeproms[addr].id = dboard_pid;
1492                     _basic_lf_tx         = true;
1493                     break;
1494             }
1495             // Add to tree
1496             get_tree()
1497                 ->create<dboard_eeprom_t>(DB_PATH / EEPROM_PATHS[i])
1498                 .set(_db_eeproms[addr])
1499                 .add_coerced_subscriber([this, zpu_i2c, BASE_ADDR, addr](
1500                                             const uhd::usrp::dboard_eeprom_t& db_eeprom) {
1501                     _set_db_eeprom(zpu_i2c, BASE_ADDR | addr, db_eeprom);
1502                 });
1503         }
1504 
1505         // create a new dboard interface
1506         x300_dboard_iface_config_t db_config;
1507         db_config.gpio           = gpio_atr::db_gpio_atr_3000::make(_wb_iface,
1508             x300_regs::SR_DB_GPIO,
1509             x300_regs::RB_DB_GPIO,
1510             x300_regs::PERIPH_REG_OFFSET);
1511         db_config.spi            = _spi;
1512         db_config.rx_spi_slaveno = DB_RX_SEN;
1513         db_config.tx_spi_slaveno = DB_TX_SEN;
1514         db_config.i2c            = zpu_i2c;
1515         db_config.clock          = clock;
1516         db_config.which_rx_clk   = (_radio_type == PRIMARY) ? X300_CLOCK_WHICH_DB0_RX
1517                                                           : X300_CLOCK_WHICH_DB1_RX;
1518         db_config.which_tx_clk = (_radio_type == PRIMARY) ? X300_CLOCK_WHICH_DB0_TX
1519                                                           : X300_CLOCK_WHICH_DB1_TX;
1520         db_config.dboard_slot   = (_radio_type == PRIMARY) ? 0 : 1;
1521         db_config.cmd_time_ctrl = _wb_iface;
1522 
1523         // create a new dboard manager
1524         RFNOC_LOG_TRACE("Creating DB interface...");
1525         _db_iface = std::make_shared<x300_dboard_iface>(db_config);
1526         RFNOC_LOG_TRACE("Creating DB manager...");
1527         _db_manager = dboard_manager::make(_db_eeproms[RX_EEPROM_ADDR + DB_OFFSET],
1528             _db_eeproms[TX_EEPROM_ADDR + DB_OFFSET],
1529             _db_eeproms[GDB_EEPROM_ADDR + DB_OFFSET],
1530             _db_iface,
1531             get_tree()->subtree(DB_PATH),
1532             true // defer daughterboard initialization
1533         );
1534         RFNOC_LOG_TRACE("DB Manager Initialization complete.");
1535 
1536         // The X3x0 radio block defaults to a maximum of two ports, but
1537         // many daughterboards have fewer possible frontends. So we now
1538         // reduce the number of actual ports based on what is connected.
1539         // Note: The Basic and LF boards have two possible frontends on
1540         // the rx side, and one on the tx side. TwinRX boards have two
1541         // possible rx frontends, and require up to two ports on the rx side.
1542         // For all other cases, the number of possible frontends is one for
1543         // rx and tx.
1544         const size_t num_tx_frontends = _db_manager->get_tx_frontends().size();
1545         const size_t num_rx_frontends = _db_manager->get_rx_frontends().size();
1546         if (num_tx_frontends == 2 || num_tx_frontends == 1) {
1547             set_num_input_ports(num_tx_frontends);
1548         } else {
1549             throw uhd::runtime_error("Unexpected number of TX frontends!");
1550         }
1551         if (num_rx_frontends == 2 || num_rx_frontends == 1) {
1552             set_num_output_ports(num_rx_frontends);
1553         } else {
1554             throw uhd::runtime_error("Unexpected number of RX frontends!");
1555         }
1556         // This is specific to TwinRX. Due to driver legacy, we think we have a
1557         // Tx frontend even though we don't. We thus hard-code that knowledge
1558         // here.
1559         if (num_rx_frontends == 2
1560             && boost::starts_with(
1561                    get_tree()->access<std::string>(DB_PATH / "rx_frontends/0/name").get(),
1562                    "TwinRX")) {
1563             _twinrx = true;
1564             set_num_input_ports(0);
1565         }
1566         RFNOC_LOG_TRACE("Num Active Frontends: RX: " << get_num_output_ports()
1567                                                      << " TX: " << get_num_input_ports());
1568     } // _init_db()
1569 
_init_dboards()1570     void _init_dboards()
1571     {
1572         size_t rx_chan = 0;
1573         size_t tx_chan = 0;
1574         for (const std::string& fe : _db_manager->get_rx_frontends()) {
1575             if (rx_chan >= get_num_output_ports()) {
1576                 break;
1577             }
1578             _set_rx_fe(fe, rx_chan);
1579             rx_chan++;
1580         }
1581         for (const std::string& fe : _db_manager->get_tx_frontends()) {
1582             if (tx_chan >= get_num_input_ports()) {
1583                 break;
1584             }
1585             _set_tx_fe(fe, tx_chan);
1586             tx_chan++;
1587         }
1588         UHD_ASSERT_THROW(rx_chan or tx_chan);
1589         RFNOC_LOG_DEBUG("Actual sample rate: " << (get_rate() / 1e6) << " Msps.");
1590 
1591         // Initialize the daughterboards now that frontend cores and connections exist
1592         _db_manager->initialize_dboards();
1593 
1594         // now that dboard is created -- register into rx antenna event
1595         if (not _rx_fe_map.empty()) {
1596             for (size_t i = 0; i < get_num_output_ports(); i++) {
1597                 if (get_tree()->exists(get_db_path("rx", i) / "antenna" / "value")) {
1598                     // We need a desired subscriber for antenna/value because the experts
1599                     // don't coerce that property.
1600                     get_tree()
1601                         ->access<std::string>(get_db_path("rx", i) / "antenna" / "value")
1602                         .add_desired_subscriber([this, i](const std::string& led) {
1603                             _update_atr_leds(led, i);
1604                         })
1605                         .update();
1606                 } else {
1607                     _update_atr_leds("", i); // init anyway, even if never called
1608                 }
1609             }
1610         }
1611 
1612         // bind frontend corrections to the dboard freq props
1613         if (not _tx_fe_map.empty()) {
1614             for (size_t i = 0; i < get_num_input_ports(); i++) {
1615                 if (get_tree()->exists(get_db_path("tx", i) / "freq" / "value")) {
1616                     get_tree()
1617                         ->access<double>(get_db_path("tx", i) / "freq" / "value")
1618                         .add_coerced_subscriber([this, i](const double freq) {
1619                             set_tx_fe_corrections(freq, i);
1620                         });
1621                 }
1622             }
1623         }
1624         if (not _rx_fe_map.empty()) {
1625             for (size_t i = 0; i < get_num_output_ports(); i++) {
1626                 if (get_tree()->exists(get_db_path("rx", i) / "freq" / "value")) {
1627                     get_tree()
1628                         ->access<double>(get_db_path("rx", i) / "freq" / "value")
1629                         .add_coerced_subscriber([this, i](const double freq) {
1630                             set_rx_fe_corrections(freq, i);
1631                         });
1632                 }
1633             }
1634         }
1635 
1636         ////////////////////////////////////////////////////////////////
1637         // Set gain groups
1638         // Note: The actual gain control comes from the daughterboard drivers, thus,
1639         // we need to call into the prop tree at the appropriate location in order
1640         // to modify the gains.
1641         ////////////////////////////////////////////////////////////////
1642         // TX
1643         for (size_t chan = 0; chan < get_num_input_ports(); chan++) {
1644             fs_path rf_gains_path(get_db_path("tx", chan) / "gains");
1645             if (!get_tree()->exists(rf_gains_path)) {
1646                 _tx_gain_groups[chan] = gain_group::make_zero();
1647                 continue;
1648             }
1649 
1650             std::vector<std::string> gain_stages = get_tree()->list(rf_gains_path);
1651             if (gain_stages.empty()) {
1652                 _tx_gain_groups[chan] = gain_group::make_zero();
1653                 continue;
1654             }
1655 
1656             // DAC does not have a gain path
1657             auto gg = gain_group::make();
1658             for (const auto& name : gain_stages) {
1659                 gg->register_fcns(name,
1660                     make_gain_fcns_from_subtree(
1661                         get_tree()->subtree(rf_gains_path / name)),
1662                     1 /* high prio */);
1663             }
1664             _tx_gain_groups[chan] = gg;
1665         }
1666         // RX
1667         for (size_t chan = 0; chan < get_num_output_ports(); chan++) {
1668             fs_path rf_gains_path(get_db_path("rx", chan) / "gains");
1669             fs_path adc_gains_path("rx_codec/gains");
1670 
1671             auto gg = gain_group::make();
1672             // ADC also has a gain path
1673             for (const auto& name : get_tree()->list(adc_gains_path)) {
1674                 gg->register_fcns("ADC-" + name,
1675                     make_gain_fcns_from_subtree(
1676                         get_tree()->subtree(adc_gains_path / name)),
1677                     0 /* low prio */);
1678             }
1679             if (get_tree()->exists(rf_gains_path)) {
1680                 for (const auto& name : get_tree()->list(rf_gains_path)) {
1681                     gg->register_fcns(name,
1682                         make_gain_fcns_from_subtree(
1683                             get_tree()->subtree(rf_gains_path / name)),
1684                         1 /* high prio */);
1685                 }
1686             }
1687             _rx_gain_groups[chan] = gg;
1688         }
1689 
1690         ////////////////////////////////////////////////////////////////
1691         // Load calibration data
1692         ////////////////////////////////////////////////////////////////
1693         RFNOC_LOG_TRACE("Initializing power calibration data...");
1694         // RX and TX are symmetric, so we use a macro to avoid some duplication
1695 #define INIT_POWER_CAL(dir)                                                             \
1696     {                                                                                   \
1697         const std::string DIR  = (#dir == std::string("tx")) ? "TX" : "RX";             \
1698         const size_t num_ports = (#dir == std::string("tx")) ? get_num_input_ports()    \
1699                                                              : get_num_output_ports();  \
1700         _##dir##_pwr_mgr.resize(num_ports);                                             \
1701         for (size_t chan = 0; chan < num_ports; chan++) {                               \
1702             const auto eeprom =                                                         \
1703                 get_tree()->access<dboard_eeprom_t>(DB_PATH / (#dir "_eeprom")).get();  \
1704             /* The cal serial is the daughterboard serial plus the FE name */           \
1705             const std::string cal_serial =                                              \
1706                 eeprom.serial + "#" + _##dir##_fe_map.at(chan).db_fe_name;              \
1707             /* Now create a gain group for this. _?x_gain_groups won't work, */         \
1708             /* unfortunately, because it doesn't group the gains we want them to */     \
1709             /* be grouped. */                                                           \
1710             auto ggroup = uhd::gain_group::make();                                      \
1711             ggroup->register_fcns(HW_GAIN_STAGE,                                        \
1712                 {[this, chan]() { return get_##dir##_gain_range(chan); },               \
1713                     [this, chan]() { return get_##dir##_gain(chan); },                  \
1714                     [this, chan](const double gain) { set_##dir##_gain(gain, chan); }}, \
1715                 10 /* High priority */);                                                \
1716             /* If we had a digital (baseband) gain, we would register it here, so */    \
1717             /* that the power manager would know to use it as a backup gain stage. */   \
1718             _##dir##_pwr_mgr.at(chan) = pwr_cal_mgr::make(                              \
1719                 cal_serial,                                                             \
1720                 "X300-CAL-" + DIR,                                                      \
1721                 [this, chan]() { return get_##dir##_frequency(chan); },                 \
1722                 [this, chan]() -> std::string {                                         \
1723                     const auto id_path = get_db_path(#dir, chan) / "id";                \
1724                     const std::string db_suffix =                                       \
1725                         get_tree()->exists(id_path)                                     \
1726                             ? get_tree()->access<std::string>(id_path).get()            \
1727                             : "generic";                                                \
1728                     const std::string ant = get_##dir##_antenna(chan);                  \
1729                     return "x3xx_pwr_" + db_suffix + "_" + #dir + "_"                   \
1730                            + pwr_cal_mgr::sanitize_antenna_name(ant);                   \
1731                 },                                                                      \
1732                 ggroup);                                                                \
1733             /* Every time we retune, we need to re-set the power level, if */           \
1734             /* we're in power tracking mode */                                          \
1735             get_tree()                                                                  \
1736                 ->access<double>(get_db_path(#dir, chan) / "freq" / "value")            \
1737                 .add_coerced_subscriber([this, chan](const double) {                    \
1738                     _##dir##_pwr_mgr.at(chan)->update_power();                          \
1739                 });                                                                     \
1740         }                                                                               \
1741     } // end macro
1742 
1743         INIT_POWER_CAL(tx);
1744         INIT_POWER_CAL(rx);
1745     } /* _init_dboards */
1746 
_set_db_eeprom(i2c_iface::sptr i2c,const size_t addr,const uhd::usrp::dboard_eeprom_t & db_eeprom)1747     void _set_db_eeprom(i2c_iface::sptr i2c,
1748         const size_t addr,
1749         const uhd::usrp::dboard_eeprom_t& db_eeprom)
1750     {
1751         db_eeprom.store(*i2c, addr);
1752         _db_eeproms[addr] = db_eeprom;
1753     }
1754 
_update_atr_leds(const std::string & rx_ant,const size_t)1755     void _update_atr_leds(const std::string& rx_ant, const size_t /*chan*/)
1756     {
1757         // The "RX1" port is used by TwinRX and the "TX/RX" port is used by all
1758         // other full-duplex dboards. We need to handle both here.
1759         const bool is_txrx = (rx_ant == "TX/RX" or rx_ant == "RX1");
1760         const int TXRX_RX  = (1 << 0);
1761         const int TXRX_TX  = (1 << 1);
1762         const int RX2_RX   = (1 << 2);
1763         _leds->set_atr_reg(gpio_atr::ATR_REG_IDLE, 0);
1764         _leds->set_atr_reg(gpio_atr::ATR_REG_RX_ONLY, is_txrx ? TXRX_RX : RX2_RX);
1765         _leds->set_atr_reg(gpio_atr::ATR_REG_TX_ONLY, TXRX_TX);
1766         _leds->set_atr_reg(gpio_atr::ATR_REG_FULL_DUPLEX, RX2_RX | TXRX_TX);
1767     }
1768 
_set_rx_fe(const std::string & fe,const size_t chan)1769     void _set_rx_fe(const std::string& fe, const size_t chan)
1770     {
1771         _rx_fe_map[chan].db_fe_name = fe;
1772         _db_iface->add_rx_fe(fe, _rx_fe_map[chan].core);
1773         const std::string connection =
1774             get_tree()->access<std::string>(get_db_path("rx", chan) / "connection").get();
1775         const double if_freq =
1776             (get_tree()->exists(get_db_path("rx", chan) / "if_freq" / "value"))
1777                 ? get_tree()
1778                       ->access<double>(get_db_path("rx", chan) / "if_freq" / "value")
1779                       .get()
1780                 : 0.0;
1781         _rx_fe_map[chan].core->set_fe_connection(
1782             usrp::fe_connection_t(connection, if_freq));
1783     }
1784 
_set_tx_fe(const std::string & fe,const size_t chan)1785     void _set_tx_fe(const std::string& fe, const size_t chan)
1786     {
1787         _tx_fe_map[chan].db_fe_name = fe;
1788         const std::string connection =
1789             get_tree()->access<std::string>(get_db_path("tx", chan) / "connection").get();
1790         _tx_fe_map[chan].core->set_mux(connection);
1791     }
1792 
set_rx_fe_corrections(const double lo_freq,const size_t chan)1793     void set_rx_fe_corrections(const double lo_freq, const size_t chan)
1794     {
1795         if (not _ignore_cal_file) {
1796             apply_rx_fe_corrections(get_tree(),
1797                 get_tree()->access<dboard_eeprom_t>(DB_PATH / "rx_eeprom").get().serial,
1798                 get_fe_path("rx", chan),
1799                 lo_freq);
1800         }
1801     }
1802 
set_tx_fe_corrections(const double lo_freq,const size_t chan)1803     void set_tx_fe_corrections(const double lo_freq, const size_t chan)
1804     {
1805         if (not _ignore_cal_file) {
1806             apply_tx_fe_corrections(get_tree(),
1807                 get_tree()->access<dboard_eeprom_t>(DB_PATH / "tx_eeprom").get().serial,
1808                 get_fe_path("tx", chan),
1809                 lo_freq);
1810         }
1811     }
1812 
1813     /**************************************************************************
1814      * noc_block_base API
1815      *************************************************************************/
1816     //! Safely shut down all peripherals
1817     //
1818     // Reminder: After this is called, no peeks and pokes are allowed!
deinit()1819     void deinit()
1820     {
1821         RFNOC_LOG_TRACE("deinit()");
1822         // Reset daughterboard
1823         _db_manager.reset();
1824         _db_iface.reset();
1825         // Reset codecs
1826         if (_radio_type == PRIMARY) {
1827             _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1);
1828             _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0);
1829         }
1830         _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 0);
1831         _regs->misc_outs_reg.flush();
1832         _adc.reset();
1833         _dac.reset();
1834         // Destroy all other periph controls
1835         _spi.reset();
1836         _fp_gpio.reset();
1837         _leds.reset();
1838         _rx_fe_map.clear();
1839         _tx_fe_map.clear();
1840     }
1841 
check_topology(const std::vector<size_t> & connected_inputs,const std::vector<size_t> & connected_outputs)1842     bool check_topology(const std::vector<size_t>& connected_inputs,
1843         const std::vector<size_t>& connected_outputs)
1844     {
1845         RFNOC_LOG_TRACE("check_topology()");
1846         if (!node_t::check_topology(connected_inputs, connected_outputs)) {
1847             return false;
1848         }
1849 
1850         for (size_t chan = 0; chan < get_num_input_ports(); chan++) {
1851             const auto fe_enable_path = get_db_path("tx", chan) / "enabled";
1852             if (get_tree()->exists(fe_enable_path)) {
1853                 const bool chan_active = std::any_of(connected_inputs.cbegin(),
1854                     connected_inputs.cend(),
1855                     [chan](const size_t input) { return input == chan; });
1856                 RFNOC_LOG_TRACE(
1857                     "Enabling TX chan " << chan << ": " << (chan_active ? "Yes" : "No"));
1858                 get_tree()->access<bool>(fe_enable_path).set(chan_active);
1859             }
1860         }
1861 
1862         for (size_t chan = 0; chan < get_num_output_ports(); chan++) {
1863             const auto fe_enable_path = get_db_path("rx", chan) / "enabled";
1864             if (get_tree()->exists(fe_enable_path)) {
1865                 const bool chan_active = std::any_of(connected_outputs.cbegin(),
1866                     connected_outputs.cend(),
1867                     [chan](const size_t output) { return output == chan; });
1868                 RFNOC_LOG_TRACE(
1869                     "Enabling RX chan " << chan << ": " << (chan_active ? "Yes" : "No"));
1870                 get_tree()->access<bool>(fe_enable_path).set(chan_active);
1871             }
1872         }
1873 
1874         return true;
1875     }
1876 
1877 
1878     /**************************************************************************
1879      * Attributes
1880      *************************************************************************/
1881     //! Register space for the ADC and DAC
1882     class radio_regmap_t : public uhd::soft_regmap_t
1883     {
1884     public:
1885         class misc_outs_reg_t : public uhd::soft_reg32_wo_t
1886         {
1887         public:
1888             UHD_DEFINE_SOFT_REG_FIELD(DAC_ENABLED, /*width*/ 1, /*shift*/ 0); //[0]
1889             UHD_DEFINE_SOFT_REG_FIELD(DAC_RESET_N, /*width*/ 1, /*shift*/ 1); //[1]
1890             UHD_DEFINE_SOFT_REG_FIELD(ADC_RESET, /*width*/ 1, /*shift*/ 2); //[2]
1891             UHD_DEFINE_SOFT_REG_FIELD(ADC_DATA_DLY_STB, /*width*/ 1, /*shift*/ 3); //[3]
1892             UHD_DEFINE_SOFT_REG_FIELD(ADC_DATA_DLY_VAL, /*width*/ 5, /*shift*/ 4); //[8:4]
1893             UHD_DEFINE_SOFT_REG_FIELD(
1894                 ADC_CHECKER_ENABLED, /*width*/ 1, /*shift*/ 9); //[9]
1895             UHD_DEFINE_SOFT_REG_FIELD(DAC_SYNC, /*width*/ 1, /*shift*/ 10); //[10]
1896 
misc_outs_reg_t()1897             misc_outs_reg_t() : uhd::soft_reg32_wo_t(x300_regs::SR_MISC_OUTS)
1898             {
1899                 // Initial values
1900                 set(DAC_ENABLED, 0);
1901                 set(DAC_RESET_N, 0);
1902                 set(ADC_RESET, 0);
1903                 set(ADC_DATA_DLY_STB, 0);
1904                 set(ADC_DATA_DLY_VAL, 16);
1905                 set(ADC_CHECKER_ENABLED, 0);
1906                 set(DAC_SYNC, 0);
1907             }
1908         } misc_outs_reg;
1909 
1910         class misc_ins_reg_t : public uhd::soft_reg64_ro_t
1911         {
1912         public:
1913             UHD_DEFINE_SOFT_REG_FIELD(
1914                 ADC_CHECKER0_Q_LOCKED, /*width*/ 1, /*shift*/ 32); //[0]
1915             UHD_DEFINE_SOFT_REG_FIELD(
1916                 ADC_CHECKER0_I_LOCKED, /*width*/ 1, /*shift*/ 33); //[1]
1917             UHD_DEFINE_SOFT_REG_FIELD(
1918                 ADC_CHECKER1_Q_LOCKED, /*width*/ 1, /*shift*/ 34); //[2]
1919             UHD_DEFINE_SOFT_REG_FIELD(
1920                 ADC_CHECKER1_I_LOCKED, /*width*/ 1, /*shift*/ 35); //[3]
1921             UHD_DEFINE_SOFT_REG_FIELD(
1922                 ADC_CHECKER0_Q_ERROR, /*width*/ 1, /*shift*/ 36); //[4]
1923             UHD_DEFINE_SOFT_REG_FIELD(
1924                 ADC_CHECKER0_I_ERROR, /*width*/ 1, /*shift*/ 37); //[5]
1925             UHD_DEFINE_SOFT_REG_FIELD(
1926                 ADC_CHECKER1_Q_ERROR, /*width*/ 1, /*shift*/ 38); //[6]
1927             UHD_DEFINE_SOFT_REG_FIELD(
1928                 ADC_CHECKER1_I_ERROR, /*width*/ 1, /*shift*/ 39); //[7]
1929 
misc_ins_reg_t()1930             misc_ins_reg_t() : uhd::soft_reg64_ro_t(x300_regs::RB_MISC_IO) {}
1931         } misc_ins_reg;
1932 
radio_regmap_t(int radio_num)1933         radio_regmap_t(int radio_num)
1934             : soft_regmap_t("radio" + std::to_string(radio_num) + "_regmap")
1935         {
1936             add_to_map(misc_outs_reg, "misc_outs_reg", PRIVATE);
1937             add_to_map(misc_ins_reg, "misc_ins_reg", PRIVATE);
1938         }
1939     }; /* class radio_regmap_t */
1940     //! wb_iface Instance for _regs
1941     uhd::timed_wb_iface::sptr _wb_iface;
1942     //! Instantiation of regs object for ADC and DAC (MISC_OUT register)
1943     std::unique_ptr<radio_regmap_t> _regs;
1944     //! Reference to the MB controller, typecast
1945     std::shared_ptr<x300_mb_controller> _x300_mb_control;
1946 
1947     //! Reference to the DBoard SPI core (also controls ADC/DAC)
1948     spi_core_3000::sptr _spi;
1949     //! Reference to the ADC controller
1950     x300_adc_ctrl::sptr _adc;
1951     //! Reference to the DAC controller
1952     x300_dac_ctrl::sptr _dac;
1953     //! Front-panel GPIO
1954     usrp::gpio_atr::gpio_atr_3000::sptr _fp_gpio;
1955     //! LEDs
1956     usrp::gpio_atr::gpio_atr_3000::sptr _leds;
1957 
1958     struct rx_fe_perif
1959     {
1960         std::string name;
1961         std::string db_fe_name;
1962         rx_frontend_core_3000::sptr core;
1963     };
1964     struct tx_fe_perif
1965     {
1966         std::string name;
1967         std::string db_fe_name;
1968         tx_frontend_core_200::sptr core;
1969     };
1970 
1971     bool _basic_lf_rx = false;
1972     bool _basic_lf_tx = false;
1973     bool _twinrx = false;
1974 
1975     std::unordered_map<size_t, rx_fe_perif> _rx_fe_map;
1976     std::unordered_map<size_t, tx_fe_perif> _tx_fe_map;
1977 
1978     //! Cache of EEPROM info (one per channel)
1979     std::unordered_map<size_t, usrp::dboard_eeprom_t> _db_eeproms;
1980     //! Reference to DB manager
1981     usrp::dboard_manager::sptr _db_manager;
1982     //! Reference to DB iface
1983     std::shared_ptr<x300_dboard_iface> _db_iface;
1984 
1985     enum radio_connection_t { PRIMARY, SECONDARY };
1986     radio_connection_t _radio_type;
1987 
1988     bool _ignore_cal_file = false;
1989 
1990     std::unordered_map<size_t, uhd::gain_group::sptr> _tx_gain_groups;
1991     std::unordered_map<size_t, uhd::gain_group::sptr> _rx_gain_groups;
1992 
1993     double _master_clock_rate = DEFAULT_RATE;
1994 };
1995 
1996 UHD_RFNOC_BLOCK_REGISTER_FOR_DEVICE_DIRECT(
1997     x300_radio_control, RADIO_BLOCK, X300, "Radio", true, "radio_clk", "radio_clk")
1998