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