1 //
2 // Copyright 2019 Ettus Research, a National Instruments Brand
3 //
4 // SPDX-License-Identifier: GPL-3.0-or-later
5 //
6
7 #include <uhd/exception.hpp>
8 #include <uhd/rfnoc/ddc_block_control.hpp>
9 #include <uhd/rfnoc/duc_block_control.hpp>
10 #include <uhd/rfnoc/filter_node.hpp>
11 #include <uhd/rfnoc/graph_edge.hpp>
12 #include <uhd/rfnoc/radio_control.hpp>
13 #include <uhd/rfnoc_graph.hpp>
14 #include <uhd/types/device_addr.hpp>
15 #include <uhd/usrp/multi_usrp.hpp>
16 #include <uhd/utils/graph_utils.hpp>
17 #include <uhdlib/rfnoc/rfnoc_device.hpp>
18 #include <uhdlib/rfnoc/rfnoc_rx_streamer.hpp>
19 #include <uhdlib/rfnoc/rfnoc_tx_streamer.hpp>
20 #include <uhdlib/usrp/gpio_defs.hpp>
21 #include <uhdlib/utils/narrow.hpp>
22 #include <unordered_set>
23 #include <boost/format.hpp>
24 #include <algorithm>
25 #include <chrono>
26 #include <memory>
27 #include <mutex>
28 #include <thread>
29 #include <vector>
30
31 using namespace std::chrono_literals;
32 using namespace uhd;
33 using namespace uhd::usrp;
34 using namespace uhd::rfnoc;
35
36 //! Fan out (mux) an API call that is for all channels or all motherboards
37 #define MUX_API_CALL(max_index, api_call, mux_var, mux_cond, ...) \
38 if (mux_var == mux_cond) { \
39 for (size_t __index = 0; __index < max_index; ++__index) { \
40 api_call(__VA_ARGS__, __index); \
41 } \
42 return; \
43 }
44
45 //! Fan out (mux) an RX-specific API call that is for all channels
46 #define MUX_RX_API_CALL(api_call, ...) \
47 MUX_API_CALL(get_rx_num_channels(), api_call, chan, ALL_CHANS, __VA_ARGS__)
48 //! Fan out (mux) an TX-specific API call that is for all channels
49 #define MUX_TX_API_CALL(api_call, ...) \
50 MUX_API_CALL(get_tx_num_channels(), api_call, chan, ALL_CHANS, __VA_ARGS__)
51 //! Fan out (mux) a motherboard-specific API call that is for all boards
52 #define MUX_MB_API_CALL(api_call, ...) \
53 MUX_API_CALL(get_num_mboards(), api_call, mboard, ALL_MBOARDS, __VA_ARGS__)
54
55
56 namespace {
57 constexpr char DEFAULT_CPU_FORMAT[] = "fc32";
58 constexpr char DEFAULT_OTW_FORMAT[] = "sc16";
59 constexpr double RX_SIGN = +1.0;
60 constexpr double TX_SIGN = -1.0;
61 constexpr char LOG_ID[] = "MULTI_USRP";
62
63 //! A faux container for a UHD device
64 //
65 // Note that multi_usrp_rfnoc no longer gives access to the underlying device
66 // class. Legacy code might use multi_usrp->get_device()->get_tree() or
67 // similar functionalities; these can be faked with this redirector class.
68 //
69 // The only exception is recv_async_msg(), which depends on the streamer. It
70 // will print a warning once, and will attempt to access a Tx streamer if it
71 // has access to a Tx streamer. If there is only ever one Tx streamer, this will
72 // work as expected. For multiple streamers, only the last streamer's async
73 // messages will make it through.
74 class redirector_device : public uhd::device
75 {
76 public:
redirector_device(multi_usrp * musrp_ptr)77 redirector_device(multi_usrp* musrp_ptr) : _musrp(musrp_ptr) {}
78
get_rx_stream(const stream_args_t & args)79 rx_streamer::sptr get_rx_stream(const stream_args_t& args)
80 {
81 return _musrp->get_rx_stream(args);
82 }
83
get_tx_stream(const stream_args_t & args)84 tx_streamer::sptr get_tx_stream(const stream_args_t& args)
85 {
86 auto streamer = _musrp->get_tx_stream(args);
87 _last_tx_streamer = streamer;
88 return streamer;
89 }
90
recv_async_msg(async_metadata_t & md,double timeout)91 bool recv_async_msg(async_metadata_t& md, double timeout)
92 {
93 std::call_once(_async_warning_flag, []() {
94 UHD_LOG_WARNING(LOG_ID,
95 "Calling multi_usrp::recv_async_msg() is deprecated and can lead to "
96 "unexpected behaviour. Prefer calling tx_stream::recv_async_msg().");
97 });
98 auto streamer = _last_tx_streamer.lock();
99 if (streamer) {
100 return streamer->recv_async_msg(md, timeout);
101 }
102 return false;
103 }
104
get_tree(void) const105 uhd::property_tree::sptr get_tree(void) const
106 {
107 return _musrp->get_tree();
108 }
109
get_device_type() const110 device_filter_t get_device_type() const
111 {
112 return USRP;
113 }
114
set_tx_stream(tx_streamer::sptr streamer)115 void set_tx_stream(tx_streamer::sptr streamer)
116 {
117 _last_tx_streamer = streamer;
118 }
119
120 private:
121 std::once_flag _async_warning_flag;
122 std::weak_ptr<tx_streamer> _last_tx_streamer;
123 multi_usrp* _musrp;
124 };
125
126 /*! Make sure the stream args are valid and can be used by get_tx_stream()
127 * and get_rx_stream().
128 *
129 */
sanitize_stream_args(const stream_args_t args_)130 stream_args_t sanitize_stream_args(const stream_args_t args_)
131 {
132 stream_args_t args = args_;
133 if (args.cpu_format.empty()) {
134 UHD_LOG_DEBUG("MULTI_USRP",
135 "get_xx_stream(): cpu_format not specified, defaulting to "
136 << DEFAULT_CPU_FORMAT);
137 args.cpu_format = DEFAULT_CPU_FORMAT;
138 }
139 if (args.otw_format.empty()) {
140 UHD_LOG_DEBUG("MULTI_USRP",
141 "get_xx_stream(): otw_format not specified, defaulting to "
142 << DEFAULT_OTW_FORMAT);
143 args.otw_format = DEFAULT_OTW_FORMAT;
144 }
145 if (args.channels.empty()) {
146 UHD_LOG_DEBUG(
147 "MULTI_USRP", "get_xx_stream(): channels not specified, defaulting to [0]");
148 args.channels = {0};
149 }
150 return args;
151 }
152
bytes_to_str(std::vector<uint8_t> str_b)153 std::string bytes_to_str(std::vector<uint8_t> str_b)
154 {
155 return std::string(str_b.cbegin(), str_b.cend());
156 }
157
158 } // namespace
159
160 class multi_usrp_rfnoc : public multi_usrp,
161 public std::enable_shared_from_this<multi_usrp_rfnoc>
162 {
163 public:
164 struct rx_chan_t
165 {
166 radio_control::sptr radio;
167 ddc_block_control::sptr ddc; // can be nullptr
168 size_t block_chan;
169 std::vector<graph_edge_t> edge_list;
170 };
171
172 struct tx_chan_t
173 {
174 radio_control::sptr radio;
175 duc_block_control::sptr duc; // can be nullptr
176 size_t block_chan;
177 std::vector<graph_edge_t> edge_list;
178 };
179
180 /**************************************************************************
181 * Structors
182 *************************************************************************/
multi_usrp_rfnoc(rfnoc_graph::sptr graph,const device_addr_t & addr)183 multi_usrp_rfnoc(rfnoc_graph::sptr graph, const device_addr_t& addr)
184 : _args(addr)
185 , _graph(graph)
186 , _tree(_graph->get_tree())
187 , _device(std::make_shared<redirector_device>(this))
188 {
189 // Discover all of the radios on our devices and create a mapping between
190 // radio chains and channel numbers. The result is sorted.
191 auto radio_blk_ids = _graph->find_blocks("Radio");
192 // If we don't find any radios, we don't have a multi_usrp object
193 if (radio_blk_ids.empty()) {
194 throw uhd::runtime_error(
195 "[multi_usrp] No radios found in connected devices.");
196 }
197 // Next, we assign block controllers to RX channels
198 // Note that we don't want to connect blocks now; we will wait until we create and
199 // connect a streamer. This gives us a little more time to figure out the desired
200 // values of our properties (such as master clock)
201 size_t musrp_rx_channel = 0;
202 size_t musrp_tx_channel = 0;
203 for (auto radio_id : radio_blk_ids) {
204 auto radio_blk = _graph->get_block<uhd::rfnoc::radio_control>(radio_id);
205 for (size_t block_chan = 0; block_chan < radio_blk->get_num_output_ports();
206 ++block_chan) {
207 // Create the RX chan
208 uhd::usrp::subdev_spec_t rx_radio_subdev;
209 rx_radio_subdev.push_back(uhd::usrp::subdev_spec_pair_t(
210 radio_blk->get_slot_name(),
211 radio_blk->get_dboard_fe_from_chan(block_chan, uhd::RX_DIRECTION)));
212 auto rx_chans =
213 _generate_mboard_rx_chans(rx_radio_subdev, radio_id.get_device_no());
214 // TODO: we're passing the same info around here; there has to be a
215 // cleaner way
216 for (auto rx_chan : rx_chans) {
217 _rx_chans.emplace(musrp_rx_channel, rx_chan);
218 ++musrp_rx_channel; // Increment after logging so we print the correct
219 // value
220 }
221 }
222 for (size_t block_chan = 0; block_chan < radio_blk->get_num_input_ports();
223 ++block_chan) {
224 // Create the TX chan
225 uhd::usrp::subdev_spec_t tx_radio_subdev;
226 tx_radio_subdev.push_back(uhd::usrp::subdev_spec_pair_t(
227 radio_blk->get_slot_name(),
228 radio_blk->get_dboard_fe_from_chan(block_chan, uhd::TX_DIRECTION)));
229 auto tx_chans =
230 _generate_mboard_tx_chans(tx_radio_subdev, radio_id.get_device_no());
231 // TODO: we're passing the same info around here; there has to be a
232 // cleaner way
233 for (auto tx_chan : tx_chans) {
234 _tx_chans.emplace(musrp_tx_channel, tx_chan);
235 ++musrp_tx_channel; // Increment after logging so we print the correct
236 // value
237 }
238 }
239 }
240 // Manually propagate radio block sample rates to DDC/DUC blocks in order to allow
241 // DDC/DUC blocks to have valid internal state before graph is (later) connected
242 for (size_t rx_chan = 0; rx_chan < get_rx_num_channels(); ++rx_chan) {
243 auto& rx_chain = _get_rx_chan(rx_chan);
244 if (rx_chain.ddc) {
245 rx_chain.ddc->set_input_rate(
246 rx_chain.radio->get_rate(), rx_chain.block_chan);
247 }
248 }
249 for (size_t tx_chan = 0; tx_chan < get_tx_num_channels(); ++tx_chan) {
250 auto& tx_chain = _get_tx_chan(tx_chan);
251 if (tx_chain.duc) {
252 tx_chain.duc->set_output_rate(
253 tx_chain.radio->get_rate(), tx_chain.block_chan);
254 }
255 }
256 _graph->commit();
257 }
258
~multi_usrp_rfnoc()259 ~multi_usrp_rfnoc()
260 {
261 // nop
262 }
263
get_device(void)264 device::sptr get_device(void)
265 {
266 return _device;
267 }
268
get_tree() const269 uhd::property_tree::sptr get_tree() const
270 {
271 return _tree;
272 }
273
get_rx_stream(const stream_args_t & args_)274 rx_streamer::sptr get_rx_stream(const stream_args_t& args_)
275 {
276 std::lock_guard<std::recursive_mutex> l(_graph_mutex);
277 stream_args_t args = sanitize_stream_args(args_);
278 double rate = 1.0;
279
280 // Note that we don't release the graph, which means that property
281 // propagation is possible. This is necessary so we don't disrupt
282 // existing streamers. We use the _graph_mutex to try and avoid any
283 // property propagation where possible.
284
285 // Connect the chains
286 _connect_rx_chains(args.channels);
287
288 // Create the streamer
289 // The disconnect callback must disconnect the entire chain because the radio
290 // relies on the connections to determine what is enabled.
291 auto this_multi_usrp = shared_from_this();
292 auto rx_streamer = std::make_shared<rfnoc_rx_streamer>(args.channels.size(),
293 args,
294 [channels = args.channels, this_multi_usrp](const std::string& id) {
295 this_multi_usrp->_graph->disconnect(id);
296 this_multi_usrp->_disconnect_rx_chains(channels);
297 });
298
299 // Connect the streamer
300 for (size_t strm_port = 0; strm_port < args.channels.size(); ++strm_port) {
301 auto rx_channel = args.channels.at(strm_port);
302 auto rx_chain = _get_rx_chan(rx_channel);
303 UHD_LOG_TRACE("MULTI_USRP",
304 "Connecting " << rx_chain.edge_list.back().src_blockid << ":"
305 << rx_chain.edge_list.back().src_port
306 << " -> RxStreamer:" << strm_port);
307 _graph->connect(rx_chain.edge_list.back().src_blockid,
308 rx_chain.edge_list.back().src_port,
309 rx_streamer,
310 strm_port);
311 const double chan_rate =
312 _rx_rates.count(rx_channel) ? _rx_rates.at(rx_channel) : 1.0;
313 if (chan_rate > 1.0 && rate != chan_rate) {
314 if (rate > 1.0) {
315 UHD_LOG_DEBUG("MULTI_USRP",
316 "Inconsistent RX rates when creating streamer! "
317 "Harmonizing to "
318 << chan_rate);
319 }
320 rate = chan_rate;
321 }
322 }
323
324 // Now everything is connected, commit() again so we can have stream
325 // commands go through the graph
326 _graph->commit();
327
328 // Before we return the streamer, we may need to reapply the rate. This
329 // is necessary whenever the blocks were configured before the streamer
330 // was created, because we don't know what state the graph is in after
331 // commit() was called in that case..
332 if (rate > 1.0) {
333 UHD_LOG_TRACE("MULTI_USRP",
334 "Now reapplying RX rate " << (rate / 1e6)
335 << " MHz to all streamer channels");
336 for (auto rx_channel : args.channels) {
337 auto rx_chain = _get_rx_chan(rx_channel);
338 if (rx_chain.ddc) {
339 rx_chain.ddc->set_output_rate(rate, rx_chain.block_chan);
340 } else {
341 rx_chain.radio->set_rate(rate);
342 }
343 }
344 }
345
346 return rx_streamer;
347 }
348
get_tx_stream(const stream_args_t & args_)349 tx_streamer::sptr get_tx_stream(const stream_args_t& args_)
350 {
351 std::lock_guard<std::recursive_mutex> l(_graph_mutex);
352 stream_args_t args = sanitize_stream_args(args_);
353 double rate = 1.0;
354
355 // Note that we don't release the graph, which means that property
356 // propagation is possible. This is necessary so we don't disrupt
357 // existing streamers. We use the _graph_mutex to try and avoid any
358 // property propagation where possible.
359
360 // Connect the chains
361 _connect_tx_chains(args.channels);
362
363 // Create a streamer
364 // The disconnect callback must disconnect the entire chain because the radio
365 // relies on the connections to determine what is enabled.
366 auto this_multi_usrp = shared_from_this();
367 auto tx_streamer = std::make_shared<rfnoc_tx_streamer>(args.channels.size(),
368 args,
369 [channels = args.channels, this_multi_usrp](const std::string& id) {
370 this_multi_usrp->_graph->disconnect(id);
371 this_multi_usrp->_disconnect_tx_chains(channels);
372 });
373
374 // Connect the streamer
375 for (size_t strm_port = 0; strm_port < args.channels.size(); ++strm_port) {
376 auto tx_channel = args.channels.at(strm_port);
377 auto tx_chain = _get_tx_chan(tx_channel);
378 UHD_LOG_TRACE("MULTI_USRP",
379 "Connecting TxStreamer:" << strm_port << " -> "
380 << tx_chain.edge_list.back().dst_blockid << ":"
381 << tx_chain.edge_list.back().dst_port);
382 _graph->connect(tx_streamer,
383 strm_port,
384 tx_chain.edge_list.back().dst_blockid,
385 tx_chain.edge_list.back().dst_port);
386 const double chan_rate =
387 _tx_rates.count(tx_channel) ? _tx_rates.at(tx_channel) : 1.0;
388 if (chan_rate > 1.0 && rate != chan_rate) {
389 UHD_LOG_DEBUG("MULTI_USRP",
390 "Inconsistent TX rates when creating streamer! Harmonizing "
391 "to "
392 << chan_rate);
393 rate = chan_rate;
394 }
395 }
396 // Now everything is connected, commit() again so we can have stream
397 // commands go through the graph
398 _graph->commit();
399
400 // Before we return the streamer, we may need to reapply the rate. This
401 // is necessary whenever the blocks were configured before the streamer
402 // was created, because we don't know what state the graph is in after
403 // commit() was called in that case, or we could have configured blocks
404 // to run at different rates (see the warning above).
405 if (rate > 1.0) {
406 UHD_LOG_TRACE("MULTI_USRP",
407 "Now reapplying TX rate " << (rate / 1e6)
408 << " MHz to all streamer channels");
409 for (auto tx_channel : args.channels) {
410 auto tx_chain = _get_tx_chan(tx_channel);
411 if (tx_chain.duc) {
412 tx_chain.duc->set_input_rate(rate, tx_chain.block_chan);
413 } else {
414 tx_chain.radio->set_rate(rate);
415 }
416 }
417 }
418
419 // For legacy purposes: This enables recv_async_msg(), which is considered
420 // deprecated, but as long as it's there, we need this to approximate
421 // previous behaviour.
422 _device->set_tx_stream(tx_streamer);
423
424 return tx_streamer;
425 }
426
427
428 /***********************************************************************
429 * Helper methods
430 **********************************************************************/
431 /*! The CORDIC can be used to shift the baseband below / past the tunable
432 * limits of the actual RF front-end. The baseband filter, located on the
433 * daughterboard, however, limits the useful instantaneous bandwidth. We
434 * allow the user to tune to the edge of the filter, where the roll-off
435 * begins. This prevents the user from tuning past the point where less
436 * than half of the spectrum would be useful.
437 */
make_overall_tune_range(const meta_range_t & fe_range,const meta_range_t & dsp_range,const double bw)438 static meta_range_t make_overall_tune_range(
439 const meta_range_t& fe_range, const meta_range_t& dsp_range, const double bw)
440 {
441 meta_range_t range;
442 for (const range_t& sub_range : fe_range) {
443 range.push_back(
444 range_t(sub_range.start() + std::max(dsp_range.start(), -bw / 2),
445 sub_range.stop() + std::min(dsp_range.stop(), bw / 2),
446 dsp_range.step()));
447 }
448 return range;
449 }
450
get_usrp_rx_info(size_t chan)451 dict<std::string, std::string> get_usrp_rx_info(size_t chan)
452 {
453 auto& rx_chain = _get_rx_chan(chan);
454 const size_t mb_idx = rx_chain.radio->get_block_id().get_device_no();
455 auto mbc = get_mbc(mb_idx);
456 auto mb_eeprom = mbc->get_eeprom();
457
458 dict<std::string, std::string> usrp_info;
459 usrp_info["mboard_id"] = mbc->get_mboard_name();
460 usrp_info["mboard_name"] = mb_eeprom.get("name", "n/a");
461 usrp_info["mboard_serial"] = mb_eeprom.get("serial", "n/a");
462 usrp_info["rx_subdev_name"] = get_rx_subdev_name(chan);
463 usrp_info["rx_subdev_spec"] = get_rx_subdev_spec(mb_idx).to_string();
464 usrp_info["rx_antenna"] = get_rx_antenna(chan);
465
466 const auto db_eeprom = rx_chain.radio->get_db_eeprom();
467 usrp_info["rx_serial"] =
468 db_eeprom.count("rx_serial")
469 ? bytes_to_str(db_eeprom.at("rx_serial"))
470 : db_eeprom.count("serial") ? bytes_to_str(db_eeprom.at("serial")) : "";
471 usrp_info["rx_id"] =
472 db_eeprom.count("rx_id")
473 ? bytes_to_str(db_eeprom.at("rx_id"))
474 : db_eeprom.count("pid") ? bytes_to_str(db_eeprom.at("pid")) : "";
475
476 const auto rx_power_ref_keys = rx_chain.radio->get_rx_power_ref_keys();
477 if (!rx_power_ref_keys.empty() && rx_power_ref_keys.size() == 2) {
478 usrp_info["rx_ref_power_key"] = rx_power_ref_keys.at(0);
479 usrp_info["rx_ref_power_serial"] = rx_power_ref_keys.at(1);
480 }
481
482 return usrp_info;
483 }
484
get_usrp_tx_info(size_t chan)485 dict<std::string, std::string> get_usrp_tx_info(size_t chan)
486 {
487 auto& tx_chain = _get_tx_chan(chan);
488 const size_t mb_idx = tx_chain.radio->get_block_id().get_device_no();
489 auto mbc = get_mbc(mb_idx);
490 auto mb_eeprom = mbc->get_eeprom();
491
492 dict<std::string, std::string> usrp_info;
493 usrp_info["mboard_id"] = mbc->get_mboard_name();
494 usrp_info["mboard_name"] = mb_eeprom.get("name", "n/a");
495 usrp_info["mboard_serial"] = mb_eeprom.get("serial", "n/a");
496 usrp_info["tx_subdev_name"] = get_tx_subdev_name(chan);
497 usrp_info["tx_subdev_spec"] = get_tx_subdev_spec(mb_idx).to_string();
498 usrp_info["tx_antenna"] = get_tx_antenna(chan);
499
500 const auto db_eeprom = tx_chain.radio->get_db_eeprom();
501 usrp_info["tx_serial"] =
502 db_eeprom.count("tx_serial")
503 ? bytes_to_str(db_eeprom.at("tx_serial"))
504 : db_eeprom.count("serial") ? bytes_to_str(db_eeprom.at("serial")) : "";
505 usrp_info["tx_id"] =
506 db_eeprom.count("tx_id")
507 ? bytes_to_str(db_eeprom.at("tx_id"))
508 : db_eeprom.count("pid") ? bytes_to_str(db_eeprom.at("pid")) : "";
509
510 const auto tx_power_ref_keys = tx_chain.radio->get_tx_power_ref_keys();
511 if (!tx_power_ref_keys.empty() && tx_power_ref_keys.size() == 2) {
512 usrp_info["tx_ref_power_key"] = tx_power_ref_keys.at(0);
513 usrp_info["tx_ref_power_serial"] = tx_power_ref_keys.at(1);
514 }
515
516 return usrp_info;
517 }
518
519 /*! Tune the appropriate radio chain to the requested frequency.
520 * The general algorithm is the same for RX and TX, so we can pass in lambdas to do
521 * the setting/getting for us.
522 */
tune_xx_subdev_and_dsp(const double xx_sign,freq_range_t tune_range,freq_range_t rf_freq_range,freq_range_t dsp_freq_range,std::function<void (double)> set_rf_freq,std::function<double ()> get_rf_freq,std::function<void (double)> set_dsp_freq,std::function<double ()> get_dsp_freq,const tune_request_t & tune_request)523 tune_result_t tune_xx_subdev_and_dsp(const double xx_sign,
524 freq_range_t tune_range,
525 freq_range_t rf_freq_range,
526 freq_range_t dsp_freq_range,
527 std::function<void(double)> set_rf_freq,
528 std::function<double()> get_rf_freq,
529 std::function<void(double)> set_dsp_freq,
530 std::function<double()> get_dsp_freq,
531 const tune_request_t& tune_request)
532 {
533 double clipped_requested_freq = tune_range.clip(tune_request.target_freq);
534 UHD_LOGGER_TRACE("MULTI_USRP")
535 << boost::format("Frequency Range %.3fMHz->%.3fMHz")
536 % (tune_range.start() / 1e6) % (tune_range.stop() / 1e6);
537 UHD_LOGGER_TRACE("MULTI_USRP")
538 << "Clipped RX frequency requested: "
539 + std::to_string(clipped_requested_freq / 1e6) + "MHz";
540
541 //------------------------------------------------------------------
542 //-- set the RF frequency depending upon the policy
543 //------------------------------------------------------------------
544 double target_rf_freq = 0.0;
545 switch (tune_request.rf_freq_policy) {
546 case tune_request_t::POLICY_AUTO:
547 target_rf_freq = clipped_requested_freq;
548 break;
549
550 case tune_request_t::POLICY_MANUAL:
551 target_rf_freq = rf_freq_range.clip(tune_request.rf_freq);
552 break;
553
554 case tune_request_t::POLICY_NONE:
555 break; // does not set
556 }
557 UHD_LOGGER_TRACE("MULTI_USRP")
558 << "Target RF Freq: " + std::to_string(target_rf_freq / 1e6) + "MHz";
559
560 //------------------------------------------------------------------
561 //-- Tune the RF frontend
562 //------------------------------------------------------------------
563 if (tune_request.rf_freq_policy != tune_request_t::POLICY_NONE) {
564 set_rf_freq(target_rf_freq);
565 }
566 const double actual_rf_freq = get_rf_freq();
567
568 //------------------------------------------------------------------
569 //-- Set the DSP frequency depending upon the DSP frequency policy.
570 //------------------------------------------------------------------
571 double target_dsp_freq = 0.0;
572 switch (tune_request.dsp_freq_policy) {
573 case tune_request_t::POLICY_AUTO:
574 /* If we are using the AUTO tuning policy, then we prevent the
575 * CORDIC from spinning us outside of the range of the baseband
576 * filter, regardless of what the user requested. This could happen
577 * if the user requested a center frequency so far outside of the
578 * tunable range of the FE that the CORDIC would spin outside the
579 * filtered baseband. */
580 target_dsp_freq = actual_rf_freq - clipped_requested_freq;
581
582 // invert the sign on the dsp freq for transmit (spinning up vs down)
583 target_dsp_freq *= xx_sign;
584
585 break;
586
587 case tune_request_t::POLICY_MANUAL:
588 /* If the user has specified a manual tune policy, we will allow
589 * tuning outside of the baseband filter, but will still clip the
590 * target DSP frequency to within the bounds of the CORDIC to
591 * prevent undefined behavior (likely an overflow). */
592 target_dsp_freq = dsp_freq_range.clip(tune_request.dsp_freq);
593 break;
594
595 case tune_request_t::POLICY_NONE:
596 break; // does not set
597 }
598 UHD_LOGGER_TRACE("MULTI_USRP")
599 << "Target DSP Freq: " + std::to_string(target_dsp_freq / 1e6) + "MHz";
600
601 //------------------------------------------------------------------
602 //-- Tune the DSP
603 //------------------------------------------------------------------
604 if (tune_request.dsp_freq_policy != tune_request_t::POLICY_NONE) {
605 set_dsp_freq(target_dsp_freq);
606 }
607 const double actual_dsp_freq = get_dsp_freq();
608
609 //------------------------------------------------------------------
610 //-- Load and return the tune result
611 //------------------------------------------------------------------
612 tune_result_t tune_result;
613 tune_result.clipped_rf_freq = clipped_requested_freq;
614 tune_result.target_rf_freq = target_rf_freq;
615 tune_result.actual_rf_freq = actual_rf_freq;
616 tune_result.target_dsp_freq = target_dsp_freq;
617 tune_result.actual_dsp_freq = actual_dsp_freq;
618 return tune_result;
619 }
620
621 /*******************************************************************
622 * Mboard methods
623 ******************************************************************/
set_master_clock_rate(double rate,size_t mboard)624 void set_master_clock_rate(double rate, size_t mboard)
625 {
626 for (auto& chain : _rx_chans) {
627 auto radio = chain.second.radio;
628 if (radio->get_block_id().get_device_no() == mboard
629 || mboard == ALL_MBOARDS) {
630 radio->set_rate(rate);
631 }
632 }
633 for (auto& chain : _tx_chans) {
634 auto radio = chain.second.radio;
635 if (radio->get_block_id().get_device_no() == mboard
636 || mboard == ALL_MBOARDS) {
637 radio->set_rate(rate);
638 }
639 }
640 }
641
get_master_clock_rate(size_t mboard)642 double get_master_clock_rate(size_t mboard)
643 {
644 // We pick the first radio we can find on this mboard, and hope that all
645 // radios have the same range.
646 for (auto& chain : _rx_chans) {
647 auto radio = chain.second.radio;
648 if (radio->get_block_id().get_device_no() == mboard) {
649 return radio->get_tick_rate();
650 }
651 }
652 for (auto& chain : _tx_chans) {
653 auto radio = chain.second.radio;
654 if (radio->get_block_id().get_device_no() == mboard) {
655 return radio->get_tick_rate();
656 }
657 }
658 throw uhd::key_error("Invalid mboard index!");
659 }
660
get_master_clock_rate_range(const size_t mboard)661 meta_range_t get_master_clock_rate_range(const size_t mboard)
662 {
663 // We pick the first radio we can find on this mboard, and hope that all
664 // radios have the same range.
665 for (auto& chain : _rx_chans) {
666 auto radio = chain.second.radio;
667 if (radio->get_block_id().get_device_no() == mboard) {
668 return radio->get_rate_range();
669 }
670 }
671 for (auto& chain : _tx_chans) {
672 auto radio = chain.second.radio;
673 if (radio->get_block_id().get_device_no() == mboard) {
674 return radio->get_rate_range();
675 }
676 }
677 throw uhd::key_error("Invalid mboard index!");
678 }
679
get_pp_string(void)680 std::string get_pp_string(void)
681 {
682 std::string buff = str(boost::format("%s USRP:\n"
683 " Device: %s\n")
684 % ((get_num_mboards() > 1) ? "Multi" : "Single")
685 % (_tree->access<std::string>("/name").get()));
686 for (size_t m = 0; m < get_num_mboards(); m++) {
687 buff += str(
688 boost::format(" Mboard %d: %s\n") % m % get_mbc(m)->get_mboard_name());
689 }
690
691
692 //----------- rx side of life ----------------------------------
693 for (size_t rx_chan = 0; rx_chan < get_rx_num_channels(); rx_chan++) {
694 buff += str(boost::format(" RX Channel: %u\n"
695 " RX DSP: %s\n"
696 " RX Dboard: %s\n"
697 " RX Subdev: %s\n")
698 % rx_chan
699 % (_rx_chans.at(rx_chan).ddc ? std::to_string(rx_chan) : "n/a")
700 % _rx_chans.at(rx_chan).radio->get_slot_name()
701 % get_rx_subdev_name(rx_chan));
702 }
703
704 //----------- tx side of life ----------------------------------
705 for (size_t tx_chan = 0; tx_chan < get_tx_num_channels(); tx_chan++) {
706 buff += str(boost::format(" TX Channel: %u\n"
707 " TX DSP: %s\n"
708 " TX Dboard: %s\n"
709 " TX Subdev: %s\n")
710 % tx_chan
711 % (_tx_chans.at(tx_chan).duc ? std::to_string(tx_chan) : "n/a")
712 % _tx_chans.at(tx_chan).radio->get_slot_name()
713 % get_tx_subdev_name(tx_chan));
714 }
715
716 return buff;
717 }
718
get_mboard_name(size_t mboard=0)719 std::string get_mboard_name(size_t mboard = 0)
720 {
721 return get_mbc(mboard)->get_mboard_name();
722 }
723
get_time_now(size_t mboard=0)724 time_spec_t get_time_now(size_t mboard = 0)
725 {
726 return get_mbc(mboard)->get_timekeeper(0)->get_time_now();
727 }
728
get_time_last_pps(size_t mboard=0)729 time_spec_t get_time_last_pps(size_t mboard = 0)
730 {
731 return get_mbc(mboard)->get_timekeeper(0)->get_time_last_pps();
732 }
733
set_time_now(const time_spec_t & time_spec,size_t mboard=ALL_MBOARDS)734 void set_time_now(const time_spec_t& time_spec, size_t mboard = ALL_MBOARDS)
735 {
736 MUX_MB_API_CALL(set_time_now, time_spec);
737 get_mbc(mboard)->get_timekeeper(0)->set_time_now(time_spec);
738 }
739
set_time_next_pps(const time_spec_t & time_spec,size_t mboard=ALL_MBOARDS)740 void set_time_next_pps(const time_spec_t& time_spec, size_t mboard = ALL_MBOARDS)
741 {
742 MUX_MB_API_CALL(set_time_next_pps, time_spec);
743 get_mbc(mboard)->get_timekeeper(0)->set_time_next_pps(time_spec);
744 }
745
set_time_unknown_pps(const time_spec_t & time_spec)746 void set_time_unknown_pps(const time_spec_t& time_spec)
747 {
748 UHD_LOGGER_INFO("MULTI_USRP") << " 1) catch time transition at pps edge";
749 auto end_time = std::chrono::steady_clock::now() + 1100ms;
750 time_spec_t time_start_last_pps = get_time_last_pps();
751 while (time_start_last_pps == get_time_last_pps()) {
752 if (std::chrono::steady_clock::now() > end_time) {
753 throw uhd::runtime_error("Board 0 may not be getting a PPS signal!\n"
754 "No PPS detected within the time interval.\n"
755 "See the application notes for your device.\n");
756 }
757 std::this_thread::sleep_for(1ms);
758 }
759
760 UHD_LOGGER_INFO("MULTI_USRP") << " 2) set times next pps (synchronously)";
761 set_time_next_pps(time_spec, ALL_MBOARDS);
762 std::this_thread::sleep_for(1s);
763
764 // verify that the time registers are read to be within a few RTT
765 for (size_t m = 1; m < get_num_mboards(); m++) {
766 time_spec_t time_0 = this->get_time_now(0);
767 time_spec_t time_i = this->get_time_now(m);
768 // 10 ms: greater than RTT but not too big
769 if (time_i < time_0 or (time_i - time_0) > time_spec_t(0.01)) {
770 UHD_LOGGER_WARNING("MULTI_USRP")
771 << boost::format(
772 "Detected time deviation between board %d and board 0.\n"
773 "Board 0 time is %f seconds.\n"
774 "Board %d time is %f seconds.\n")
775 % m % time_0.get_real_secs() % m % time_i.get_real_secs();
776 }
777 }
778 }
779
get_time_synchronized(void)780 bool get_time_synchronized(void)
781 {
782 for (size_t m = 1; m < get_num_mboards(); m++) {
783 time_spec_t time_0 = this->get_time_now(0);
784 time_spec_t time_i = this->get_time_now(m);
785 if (time_i < time_0 or (time_i - time_0) > time_spec_t(0.01)) {
786 return false;
787 }
788 }
789 return true;
790 }
791
set_command_time(const uhd::time_spec_t & time_spec,size_t mboard=ALL_MBOARDS)792 void set_command_time(const uhd::time_spec_t& time_spec, size_t mboard = ALL_MBOARDS)
793 {
794 MUX_MB_API_CALL(set_command_time, time_spec);
795
796 // Set command time on all the blocks that are connected
797 for (auto& chain : _rx_chans) {
798 if (chain.second.radio->get_block_id().get_device_no() != mboard) {
799 continue;
800 }
801 chain.second.radio->set_command_time(time_spec, chain.second.block_chan);
802 if (chain.second.ddc) {
803 chain.second.ddc->set_command_time(time_spec, chain.second.block_chan);
804 }
805 }
806 for (auto& chain : _tx_chans) {
807 if (chain.second.radio->get_block_id().get_device_no() != mboard) {
808 continue;
809 }
810 chain.second.radio->set_command_time(time_spec, chain.second.block_chan);
811 if (chain.second.duc) {
812 chain.second.duc->set_command_time(time_spec, chain.second.block_chan);
813 }
814 }
815 }
816
clear_command_time(size_t mboard=ALL_MBOARDS)817 void clear_command_time(size_t mboard = ALL_MBOARDS)
818 {
819 if (mboard == ALL_MBOARDS) {
820 for (size_t i = 0; i < get_num_mboards(); ++i) {
821 clear_command_time(i);
822 }
823 return;
824 }
825
826 // Clear command time on all the blocks that are connected
827 for (auto& chain : _rx_chans) {
828 if (chain.second.radio->get_block_id().get_device_no() != mboard) {
829 continue;
830 }
831 chain.second.radio->clear_command_time(chain.second.block_chan);
832 if (chain.second.ddc) {
833 chain.second.ddc->clear_command_time(chain.second.block_chan);
834 }
835 }
836 for (auto& chain : _tx_chans) {
837 if (chain.second.radio->get_block_id().get_device_no() != mboard) {
838 continue;
839 }
840 chain.second.radio->clear_command_time(chain.second.block_chan);
841 if (chain.second.duc) {
842 chain.second.duc->clear_command_time(chain.second.block_chan);
843 }
844 }
845 }
846
issue_stream_cmd(const stream_cmd_t & stream_cmd,size_t chan=ALL_CHANS)847 void issue_stream_cmd(const stream_cmd_t& stream_cmd, size_t chan = ALL_CHANS)
848 {
849 MUX_RX_API_CALL(issue_stream_cmd, stream_cmd);
850 auto& rx_chain = _get_rx_chan(chan);
851 if (rx_chain.ddc) {
852 rx_chain.ddc->issue_stream_cmd(stream_cmd, rx_chain.block_chan);
853 } else {
854 rx_chain.radio->issue_stream_cmd(stream_cmd, rx_chain.block_chan);
855 }
856 }
857
set_time_source(const std::string & source,const size_t mboard=ALL_MBOARDS)858 void set_time_source(const std::string& source, const size_t mboard = ALL_MBOARDS)
859 {
860 MUX_MB_API_CALL(set_time_source, source);
861 get_mbc(mboard)->set_time_source(source);
862 }
863
get_time_source(const size_t mboard)864 std::string get_time_source(const size_t mboard)
865 {
866 return get_mbc(mboard)->get_time_source();
867 }
868
get_time_sources(const size_t mboard)869 std::vector<std::string> get_time_sources(const size_t mboard)
870 {
871 return get_mbc(mboard)->get_time_sources();
872 }
873
set_clock_source(const std::string & source,const size_t mboard=ALL_MBOARDS)874 void set_clock_source(const std::string& source, const size_t mboard = ALL_MBOARDS)
875 {
876 MUX_MB_API_CALL(set_clock_source, source);
877 get_mbc(mboard)->set_clock_source(source);
878 }
879
get_clock_source(const size_t mboard)880 std::string get_clock_source(const size_t mboard)
881 {
882 return get_mbc(mboard)->get_clock_source();
883 }
884
get_clock_sources(const size_t mboard)885 std::vector<std::string> get_clock_sources(const size_t mboard)
886 {
887 return get_mbc(mboard)->get_clock_sources();
888 }
889
set_sync_source(const std::string & clock_source,const std::string & time_source,const size_t mboard=ALL_MBOARDS)890 void set_sync_source(const std::string& clock_source,
891 const std::string& time_source,
892 const size_t mboard = ALL_MBOARDS)
893 {
894 MUX_MB_API_CALL(set_sync_source, clock_source, time_source);
895 get_mbc(mboard)->set_sync_source(clock_source, time_source);
896 }
897
set_sync_source(const device_addr_t & sync_source,const size_t mboard=ALL_MBOARDS)898 void set_sync_source(
899 const device_addr_t& sync_source, const size_t mboard = ALL_MBOARDS)
900 {
901 MUX_MB_API_CALL(set_sync_source, sync_source);
902 get_mbc(mboard)->set_sync_source(sync_source);
903 }
904
get_sync_source(const size_t mboard)905 device_addr_t get_sync_source(const size_t mboard)
906 {
907 return get_mbc(mboard)->get_sync_source();
908 }
909
get_sync_sources(const size_t mboard)910 std::vector<device_addr_t> get_sync_sources(const size_t mboard)
911 {
912 return get_mbc(mboard)->get_sync_sources();
913 }
914
set_clock_source_out(const bool enb,const size_t mboard=ALL_MBOARDS)915 void set_clock_source_out(const bool enb, const size_t mboard = ALL_MBOARDS)
916 {
917 MUX_MB_API_CALL(set_clock_source_out, enb);
918 get_mbc(mboard)->set_clock_source_out(enb);
919 }
920
set_time_source_out(const bool enb,const size_t mboard=ALL_MBOARDS)921 void set_time_source_out(const bool enb, const size_t mboard = ALL_MBOARDS)
922 {
923 MUX_MB_API_CALL(set_time_source_out, enb);
924 get_mbc(mboard)->set_time_source_out(enb);
925 }
926
get_num_mboards(void)927 size_t get_num_mboards(void)
928 {
929 return _graph->get_num_mboards();
930 }
931
get_mboard_sensor(const std::string & name,size_t mboard=0)932 sensor_value_t get_mboard_sensor(const std::string& name, size_t mboard = 0)
933 {
934 return get_mbc(mboard)->get_sensor(name);
935 }
936
get_mboard_sensor_names(size_t mboard=0)937 std::vector<std::string> get_mboard_sensor_names(size_t mboard = 0)
938 {
939 return get_mbc(mboard)->get_sensor_names();
940 }
941
942 // This only works on the USRP2 and B100, both of which are not rfnoc_device
set_user_register(const uint8_t,const uint32_t,size_t)943 void set_user_register(const uint8_t, const uint32_t, size_t)
944 {
945 throw uhd::not_implemented_error(
946 "set_user_register(): Not implemented on this device!");
947 }
948
949 // This only works on the B200, which is not an rfnoc_device
get_user_settings_iface(const size_t)950 uhd::wb_iface::sptr get_user_settings_iface(const size_t)
951 {
952 return nullptr;
953 }
954
get_radio_control(const size_t chan=0)955 uhd::rfnoc::radio_control& get_radio_control(const size_t chan = 0)
956 {
957 return *_get_rx_chan(chan).radio;
958 }
959
960 /*******************************************************************
961 * RX methods
962 ******************************************************************/
_generate_rx_radio_chan(block_id_t radio_id,size_t block_chan)963 rx_chan_t _generate_rx_radio_chan(block_id_t radio_id, size_t block_chan)
964 {
965 auto radio_blk = _graph->get_block<uhd::rfnoc::radio_control>(radio_id);
966 auto radio_source_chain = get_block_chain(_graph, radio_id, block_chan, true);
967
968 // Find out if we have a DDC in the radio block chain
969 auto ddc_port_def = [this, radio_source_chain, radio_id, block_chan]() {
970 try {
971 for (auto edge : radio_source_chain) {
972 if (block_id_t(edge.dst_blockid).match("DDC")) {
973 if (edge.dst_port != block_chan) {
974 /* We don't expect this to happen very often. But in
975 * the case that port numbers don't match, we need to
976 * disable DDC control to ensure we're not controlling
977 * another channel's DDC
978 */
979 UHD_LOGGER_WARNING("MULTI_USRP")
980 << "DDC in radio chain " << radio_id << ":"
981 << std::to_string(block_chan)
982 << " not connected to the same port number! "
983 "Disabling DDC control.";
984 break;
985 }
986 auto ddc_blk = _graph->get_block<uhd::rfnoc::ddc_block_control>(
987 edge.dst_blockid);
988 return std::tuple<uhd::rfnoc::ddc_block_control::sptr, size_t>(
989 ddc_blk, block_chan);
990 }
991 }
992 } catch (const uhd::exception&) {
993 UHD_LOGGER_DEBUG("MULTI_USRP")
994 << "No DDC found for radio block " << radio_id << ":"
995 << std::to_string(block_chan);
996 // Then just return a nullptr
997 }
998 return std::tuple<uhd::rfnoc::ddc_block_control::sptr, size_t>(nullptr, 0);
999 }();
1000
1001 // Create the RX chan
1002 return rx_chan_t(
1003 {radio_blk, std::get<0>(ddc_port_def), block_chan, radio_source_chain});
1004 }
1005
_generate_mboard_rx_chans(const uhd::usrp::subdev_spec_t & spec,size_t mboard)1006 std::vector<rx_chan_t> _generate_mboard_rx_chans(
1007 const uhd::usrp::subdev_spec_t& spec, size_t mboard)
1008 {
1009 // Discover all of the radios on our devices and create a mapping between radio
1010 // chains and channel numbers
1011 auto radio_blk_ids = _graph->find_blocks(std::to_string(mboard) + "/Radio");
1012 // If we don't find any radios, we don't have a multi_usrp object
1013 if (radio_blk_ids.empty()) {
1014 throw uhd::runtime_error(
1015 "[multi_usrp] No radios found in the requested mboard: "
1016 + std::to_string(mboard));
1017 }
1018
1019 // Iterate through the subdev pairs, and try to find a radio that matches
1020 std::vector<rx_chan_t> new_chans;
1021 for (auto chan_subdev_pair : spec) {
1022 bool subdev_found = false;
1023 for (auto radio_id : radio_blk_ids) {
1024 auto radio_blk = _graph->get_block<uhd::rfnoc::radio_control>(radio_id);
1025 size_t block_chan;
1026 try {
1027 block_chan = radio_blk->get_chan_from_dboard_fe(
1028 chan_subdev_pair.sd_name, RX_DIRECTION);
1029 } catch (const uhd::lookup_error&) {
1030 // This is OK, since we're probing all radios, this
1031 // particular radio may not have the requested frontend name
1032 // so it's not one that we want in this list.
1033 continue;
1034 }
1035 subdev_spec_pair_t radio_subdev(radio_blk->get_slot_name(),
1036 radio_blk->get_dboard_fe_from_chan(block_chan, uhd::RX_DIRECTION));
1037 if (chan_subdev_pair == radio_subdev) {
1038 new_chans.push_back(_generate_rx_radio_chan(radio_id, block_chan));
1039 subdev_found = true;
1040 }
1041 }
1042 if (!subdev_found) {
1043 std::string err_msg("Could not find radio on mboard "
1044 + std::to_string(mboard) + " that matches subdev "
1045 + chan_subdev_pair.db_name + ":"
1046 + chan_subdev_pair.sd_name);
1047 UHD_LOG_ERROR("MULTI_USRP", err_msg);
1048 throw uhd::lookup_error(err_msg);
1049 }
1050 }
1051 UHD_LOG_TRACE("MULTI_USRP",
1052 std::string("Using RX subdev " + spec.to_string() + ", found ")
1053 + std::to_string(new_chans.size()) + " channels for mboard "
1054 + std::to_string(mboard));
1055 return new_chans;
1056 }
1057
set_rx_subdev_spec(const uhd::usrp::subdev_spec_t & spec,size_t mboard=ALL_MBOARDS)1058 void set_rx_subdev_spec(
1059 const uhd::usrp::subdev_spec_t& spec, size_t mboard = ALL_MBOARDS)
1060 {
1061 // First, generate a vector of the RX channels that we need to register
1062 auto new_rx_chans = [this, spec, mboard]() {
1063 /* When setting the subdev spec in multiple mboard scenarios, there are two
1064 * cases we need to handle:
1065 * 1. Setting all mboard to the same subdev spec. This is the easy case.
1066 * 2. Setting a single mboard's subdev spec. In this case, we need to update
1067 * the requested mboard's subdev spec, and keep the old subdev spec for the
1068 * other mboards.
1069 */
1070 std::vector<rx_chan_t> new_rx_chans;
1071 for (size_t current_mboard = 0; current_mboard < get_num_mboards();
1072 ++current_mboard) {
1073 auto current_spec = [this, spec, mboard, current_mboard]() {
1074 if (mboard == ALL_MBOARDS || mboard == current_mboard) {
1075 // Update all mboards to the same subdev spec OR
1076 // only update this mboard to the new subdev spec
1077 return spec;
1078 } else {
1079 // Keep the old subdev spec for this mboard
1080 return get_rx_subdev_spec(current_mboard);
1081 }
1082 }();
1083 auto new_mboard_chans =
1084 _generate_mboard_rx_chans(current_spec, current_mboard);
1085 new_rx_chans.insert(
1086 new_rx_chans.end(), new_mboard_chans.begin(), new_mboard_chans.end());
1087 }
1088 return new_rx_chans;
1089 }();
1090
1091 // Disconnect and clear the existing chains
1092 for (size_t i = 0; i < _rx_chans.size(); i++) {
1093 _disconnect_rx_chain(i);
1094 }
1095 _rx_chans.clear();
1096
1097 // Register the new chains
1098 size_t musrp_rx_channel = 0;
1099 for (auto rx_chan : new_rx_chans) {
1100 _rx_chans.emplace(musrp_rx_channel++, rx_chan);
1101 }
1102 }
1103
get_rx_subdev_spec(size_t mboard=0)1104 uhd::usrp::subdev_spec_t get_rx_subdev_spec(size_t mboard = 0)
1105 {
1106 uhd::usrp::subdev_spec_t result;
1107 for (size_t rx_chan = 0; rx_chan < get_rx_num_channels(); rx_chan++) {
1108 auto& rx_chain = _rx_chans.at(rx_chan);
1109 if (rx_chain.radio->get_block_id().get_device_no() == mboard) {
1110 result.push_back(
1111 uhd::usrp::subdev_spec_pair_t(rx_chain.radio->get_slot_name(),
1112 rx_chain.radio->get_dboard_fe_from_chan(
1113 rx_chain.block_chan, uhd::RX_DIRECTION)));
1114 }
1115 }
1116
1117 return result;
1118 }
1119
get_rx_num_channels(void)1120 size_t get_rx_num_channels(void)
1121 {
1122 return _rx_chans.size();
1123 }
1124
get_rx_subdev_name(size_t chan=0)1125 std::string get_rx_subdev_name(size_t chan = 0)
1126 {
1127 auto rx_chain = _get_rx_chan(chan);
1128 return rx_chain.radio->get_fe_name(rx_chain.block_chan, uhd::RX_DIRECTION);
1129 }
1130
set_rx_rate(double rate,size_t chan=ALL_CHANS)1131 void set_rx_rate(double rate, size_t chan = ALL_CHANS)
1132 {
1133 std::lock_guard<std::recursive_mutex> l(_graph_mutex);
1134 MUX_RX_API_CALL(set_rx_rate, rate);
1135 const double actual_rate = [&]() {
1136 auto rx_chain = _get_rx_chan(chan);
1137 if (rx_chain.ddc) {
1138 return rx_chain.ddc->set_output_rate(rate, rx_chain.block_chan);
1139 } else {
1140 return rx_chain.radio->set_rate(rate);
1141 }
1142 }();
1143 if (actual_rate != rate) {
1144 UHD_LOGGER_WARNING("MULTI_USRP")
1145 << boost::format(
1146 "Could not set RX rate to %.3f MHz. Actual rate is %.3f MHz")
1147 % (rate / 1.0e6) % (actual_rate / 1.0e6);
1148 }
1149 _rx_rates[chan] = actual_rate;
1150 }
1151
set_rx_spp(const size_t spp,const size_t chan=ALL_CHANS)1152 void set_rx_spp(const size_t spp, const size_t chan = ALL_CHANS)
1153 {
1154 std::lock_guard<std::recursive_mutex> l(_graph_mutex);
1155 MUX_RX_API_CALL(set_rx_spp, spp);
1156 auto rx_chain = _get_rx_chan(chan);
1157 rx_chain.radio->set_property<int>(
1158 "spp", narrow_cast<int>(spp), rx_chain.block_chan);
1159 }
1160
get_rx_rate(size_t chan=0)1161 double get_rx_rate(size_t chan = 0)
1162 {
1163 std::lock_guard<std::recursive_mutex> l(_graph_mutex);
1164 auto& rx_chain = _get_rx_chan(chan);
1165 if (rx_chain.ddc) {
1166 return rx_chain.ddc->get_output_rate(rx_chain.block_chan);
1167 }
1168 return rx_chain.radio->get_rate();
1169 }
1170
get_rx_rates(size_t chan=0)1171 meta_range_t get_rx_rates(size_t chan = 0)
1172 {
1173 std::lock_guard<std::recursive_mutex> l(_graph_mutex);
1174 auto rx_chain = _get_rx_chan(chan);
1175 if (rx_chain.ddc) {
1176 return rx_chain.ddc->get_output_rates(rx_chain.block_chan);
1177 }
1178 return rx_chain.radio->get_rate_range();
1179 }
1180
set_rx_freq(const tune_request_t & tune_request,size_t chan=0)1181 tune_result_t set_rx_freq(const tune_request_t& tune_request, size_t chan = 0)
1182 {
1183 std::lock_guard<std::recursive_mutex> l(_graph_mutex);
1184
1185 // TODO: Add external LO warning
1186
1187 auto rx_chain = _get_rx_chan(chan);
1188
1189 rx_chain.radio->set_rx_tune_args(tune_request.args, rx_chain.block_chan);
1190 //------------------------------------------------------------------
1191 //-- calculate the tunable frequency ranges of the system
1192 //------------------------------------------------------------------
1193 freq_range_t tune_range =
1194 (rx_chain.ddc)
1195 ? make_overall_tune_range(
1196 rx_chain.radio->get_rx_frequency_range(rx_chain.block_chan),
1197 rx_chain.ddc->get_frequency_range(rx_chain.block_chan),
1198 rx_chain.radio->get_rx_bandwidth(rx_chain.block_chan))
1199 : rx_chain.radio->get_rx_frequency_range(rx_chain.block_chan);
1200
1201 freq_range_t rf_range =
1202 rx_chain.radio->get_rx_frequency_range(rx_chain.block_chan);
1203 freq_range_t dsp_range =
1204 (rx_chain.ddc) ? rx_chain.ddc->get_frequency_range(rx_chain.block_chan)
1205 : meta_range_t(0, 0);
1206 // Create lambdas to feed to tune_xx_subdev_and_dsp()
1207 // Note: If there is no DDC present, register empty lambdas for the DSP functions
1208 auto set_rf_freq = [rx_chain](double freq) {
1209 rx_chain.radio->set_rx_frequency(freq, rx_chain.block_chan);
1210 };
1211 auto get_rf_freq = [rx_chain](void) {
1212 return rx_chain.radio->get_rx_frequency(rx_chain.block_chan);
1213 };
1214 auto set_dsp_freq = [rx_chain](double freq) {
1215 (rx_chain.ddc) ? rx_chain.ddc->set_freq(freq, rx_chain.block_chan) : 0;
1216 };
1217 auto get_dsp_freq = [rx_chain](void) {
1218 return (rx_chain.ddc) ? rx_chain.ddc->get_freq(rx_chain.block_chan) : 0.0;
1219 };
1220 return tune_xx_subdev_and_dsp(RX_SIGN,
1221 tune_range,
1222 rf_range,
1223 dsp_range,
1224 set_rf_freq,
1225 get_rf_freq,
1226 set_dsp_freq,
1227 get_dsp_freq,
1228 tune_request);
1229 }
1230
get_rx_freq(size_t chan=0)1231 double get_rx_freq(size_t chan = 0)
1232 {
1233 auto& rx_chain = _get_rx_chan(chan);
1234
1235 // extract actual dsp and IF frequencies
1236 const double actual_rf_freq =
1237 rx_chain.radio->get_rx_frequency(rx_chain.block_chan);
1238 const double actual_dsp_freq =
1239 (rx_chain.ddc) ? rx_chain.ddc->get_freq(rx_chain.block_chan) : 0.0;
1240
1241 return actual_rf_freq - actual_dsp_freq * RX_SIGN;
1242 }
1243
get_rx_freq_range(size_t chan=0)1244 freq_range_t get_rx_freq_range(size_t chan = 0)
1245 {
1246 auto fe_freq_range = get_fe_rx_freq_range(chan);
1247
1248 auto rx_chain = _get_rx_chan(chan);
1249 uhd::freq_range_t dsp_freq_range =
1250 (rx_chain.ddc) ? make_overall_tune_range(get_fe_rx_freq_range(chan),
1251 rx_chain.ddc->get_frequency_range(rx_chain.block_chan),
1252 rx_chain.radio->get_rx_bandwidth(rx_chain.block_chan))
1253 : get_fe_rx_freq_range(chan);
1254 return dsp_freq_range;
1255 }
1256
get_fe_rx_freq_range(size_t chan=0)1257 freq_range_t get_fe_rx_freq_range(size_t chan = 0)
1258 {
1259 auto rx_chain = _get_rx_chan(chan);
1260 return rx_chain.radio->get_rx_frequency_range(rx_chain.block_chan);
1261 }
1262
1263 /**************************************************************************
1264 * LO controls
1265 *************************************************************************/
get_rx_lo_names(size_t chan=0)1266 std::vector<std::string> get_rx_lo_names(size_t chan = 0)
1267 {
1268 auto rx_chain = _get_rx_chan(chan);
1269 return rx_chain.radio->get_rx_lo_names(rx_chain.block_chan);
1270 }
1271
set_rx_lo_source(const std::string & src,const std::string & name=ALL_LOS,size_t chan=0)1272 void set_rx_lo_source(
1273 const std::string& src, const std::string& name = ALL_LOS, size_t chan = 0)
1274 {
1275 MUX_RX_API_CALL(set_rx_lo_source, src, name);
1276 auto rx_chain = _get_rx_chan(chan);
1277 rx_chain.radio->set_rx_lo_source(src, name, rx_chain.block_chan);
1278 }
1279
get_rx_lo_source(const std::string & name=ALL_LOS,size_t chan=0)1280 const std::string get_rx_lo_source(const std::string& name = ALL_LOS, size_t chan = 0)
1281 {
1282 auto rx_chain = _get_rx_chan(chan);
1283 return rx_chain.radio->get_rx_lo_source(name, rx_chain.block_chan);
1284 }
1285
get_rx_lo_sources(const std::string & name=ALL_LOS,size_t chan=0)1286 std::vector<std::string> get_rx_lo_sources(
1287 const std::string& name = ALL_LOS, size_t chan = 0)
1288 {
1289 auto rx_chain = _get_rx_chan(chan);
1290 return rx_chain.radio->get_rx_lo_sources(name, rx_chain.block_chan);
1291 }
1292
set_rx_lo_export_enabled(bool enabled,const std::string & name=ALL_LOS,size_t chan=0)1293 void set_rx_lo_export_enabled(
1294 bool enabled, const std::string& name = ALL_LOS, size_t chan = 0)
1295 {
1296 MUX_RX_API_CALL(set_rx_lo_export_enabled, enabled, name);
1297 auto rx_chain = _get_rx_chan(chan);
1298 rx_chain.radio->set_rx_lo_export_enabled(enabled, name, rx_chain.block_chan);
1299 }
1300
get_rx_lo_export_enabled(const std::string & name=ALL_LOS,size_t chan=0)1301 bool get_rx_lo_export_enabled(const std::string& name = ALL_LOS, size_t chan = 0)
1302 {
1303 auto rx_chain = _get_rx_chan(chan);
1304 return rx_chain.radio->get_rx_lo_export_enabled(name, rx_chain.block_chan);
1305 }
1306
set_rx_lo_freq(double freq,const std::string & name,size_t chan=0)1307 double set_rx_lo_freq(double freq, const std::string& name, size_t chan = 0)
1308 {
1309 auto rx_chain = _get_rx_chan(chan);
1310 return rx_chain.radio->set_rx_lo_freq(freq, name, rx_chain.block_chan);
1311 }
1312
get_rx_lo_freq(const std::string & name,size_t chan=0)1313 double get_rx_lo_freq(const std::string& name, size_t chan = 0)
1314 {
1315 auto rx_chain = _get_rx_chan(chan);
1316 return rx_chain.radio->get_rx_lo_freq(name, rx_chain.block_chan);
1317 }
1318
get_rx_lo_freq_range(const std::string & name,size_t chan=0)1319 freq_range_t get_rx_lo_freq_range(const std::string& name, size_t chan = 0)
1320 {
1321 auto rx_chain = _get_rx_chan(chan);
1322 return rx_chain.radio->get_rx_lo_freq_range(name, rx_chain.block_chan);
1323 }
1324
1325 /*** TX LO API ***/
get_tx_lo_names(size_t chan=0)1326 std::vector<std::string> get_tx_lo_names(size_t chan = 0)
1327 {
1328 auto tx_chain = _get_tx_chan(chan);
1329 return tx_chain.radio->get_tx_lo_names(tx_chain.block_chan);
1330 }
1331
set_tx_lo_source(const std::string & src,const std::string & name=ALL_LOS,const size_t chan=0)1332 void set_tx_lo_source(
1333 const std::string& src, const std::string& name = ALL_LOS, const size_t chan = 0)
1334 {
1335 MUX_TX_API_CALL(set_tx_lo_source, src, name);
1336 auto tx_chain = _get_tx_chan(chan);
1337 tx_chain.radio->set_tx_lo_source(src, name, tx_chain.block_chan);
1338 }
1339
get_tx_lo_source(const std::string & name=ALL_LOS,const size_t chan=0)1340 const std::string get_tx_lo_source(
1341 const std::string& name = ALL_LOS, const size_t chan = 0)
1342 {
1343 auto tx_chain = _get_tx_chan(chan);
1344 return tx_chain.radio->get_tx_lo_source(name, tx_chain.block_chan);
1345 }
1346
get_tx_lo_sources(const std::string & name=ALL_LOS,const size_t chan=0)1347 std::vector<std::string> get_tx_lo_sources(
1348 const std::string& name = ALL_LOS, const size_t chan = 0)
1349 {
1350 auto tx_chain = _get_tx_chan(chan);
1351 return tx_chain.radio->get_tx_lo_sources(name, tx_chain.block_chan);
1352 }
1353
set_tx_lo_export_enabled(const bool enabled,const std::string & name=ALL_LOS,const size_t chan=0)1354 void set_tx_lo_export_enabled(
1355 const bool enabled, const std::string& name = ALL_LOS, const size_t chan = 0)
1356 {
1357 MUX_TX_API_CALL(set_tx_lo_export_enabled, enabled, name);
1358 auto tx_chain = _get_tx_chan(chan);
1359 tx_chain.radio->set_tx_lo_export_enabled(enabled, name, tx_chain.block_chan);
1360 }
1361
get_tx_lo_export_enabled(const std::string & name=ALL_LOS,const size_t chan=0)1362 bool get_tx_lo_export_enabled(
1363 const std::string& name = ALL_LOS, const size_t chan = 0)
1364 {
1365 auto tx_chain = _get_tx_chan(chan);
1366 return tx_chain.radio->get_tx_lo_export_enabled(name, tx_chain.block_chan);
1367 }
1368
set_tx_lo_freq(const double freq,const std::string & name,const size_t chan=0)1369 double set_tx_lo_freq(
1370 const double freq, const std::string& name, const size_t chan = 0)
1371 {
1372 auto tx_chain = _get_tx_chan(chan);
1373 return tx_chain.radio->set_tx_lo_freq(freq, name, tx_chain.block_chan);
1374 }
1375
get_tx_lo_freq(const std::string & name,const size_t chan=0)1376 double get_tx_lo_freq(const std::string& name, const size_t chan = 0)
1377 {
1378 auto tx_chain = _get_tx_chan(chan);
1379 return tx_chain.radio->get_tx_lo_freq(name, tx_chain.block_chan);
1380 }
1381
get_tx_lo_freq_range(const std::string & name,const size_t chan=0)1382 freq_range_t get_tx_lo_freq_range(const std::string& name, const size_t chan = 0)
1383 {
1384 auto tx_chain = _get_tx_chan(chan);
1385 return tx_chain.radio->get_tx_lo_freq_range(name, tx_chain.block_chan);
1386 }
1387
1388 /**************************************************************************
1389 * Gain controls
1390 *************************************************************************/
set_rx_gain(double gain,const std::string & name,size_t chan=0)1391 void set_rx_gain(double gain, const std::string& name, size_t chan = 0)
1392 {
1393 MUX_RX_API_CALL(set_rx_gain, gain, name);
1394 auto rx_chain = _get_rx_chan(chan);
1395 rx_chain.radio->set_rx_gain(gain, name, rx_chain.block_chan);
1396 }
1397
get_rx_gain_profile_names(const size_t chan=0)1398 std::vector<std::string> get_rx_gain_profile_names(const size_t chan = 0)
1399 {
1400 auto rx_chain = _get_rx_chan(chan);
1401 return rx_chain.radio->get_rx_gain_profile_names(rx_chain.block_chan);
1402 }
1403
set_rx_gain_profile(const std::string & profile,const size_t chan=0)1404 void set_rx_gain_profile(const std::string& profile, const size_t chan = 0)
1405 {
1406 MUX_RX_API_CALL(set_rx_gain_profile, profile);
1407 auto rx_chain = _get_rx_chan(chan);
1408 rx_chain.radio->set_rx_gain_profile(profile, rx_chain.block_chan);
1409 }
1410
get_rx_gain_profile(const size_t chan=0)1411 std::string get_rx_gain_profile(const size_t chan = 0)
1412 {
1413 auto rx_chain = _get_rx_chan(chan);
1414 return rx_chain.radio->get_rx_gain_profile(rx_chain.block_chan);
1415 }
1416
set_normalized_rx_gain(double gain,size_t chan=0)1417 void set_normalized_rx_gain(double gain, size_t chan = 0)
1418 {
1419 if (gain > 1.0 || gain < 0.0) {
1420 throw uhd::runtime_error("Normalized gain out of range, must be in [0, 1].");
1421 }
1422 MUX_RX_API_CALL(set_normalized_rx_gain, gain);
1423 gain_range_t gain_range = get_rx_gain_range(ALL_GAINS, chan);
1424 double abs_gain =
1425 (gain * (gain_range.stop() - gain_range.start())) + gain_range.start();
1426 set_rx_gain(abs_gain, ALL_GAINS, chan);
1427 }
1428
set_rx_agc(bool enable,size_t chan=0)1429 void set_rx_agc(bool enable, size_t chan = 0)
1430 {
1431 MUX_RX_API_CALL(set_rx_agc, enable);
1432 auto& rx_chain = _get_rx_chan(chan);
1433 rx_chain.radio->set_rx_agc(enable, rx_chain.block_chan);
1434 }
1435
get_rx_gain(const std::string & name,size_t chan=0)1436 double get_rx_gain(const std::string& name, size_t chan = 0)
1437 {
1438 auto& rx_chain = _get_rx_chan(chan);
1439 return rx_chain.radio->get_rx_gain(name, rx_chain.block_chan);
1440 }
1441
get_normalized_rx_gain(size_t chan=0)1442 double get_normalized_rx_gain(size_t chan = 0)
1443 {
1444 gain_range_t gain_range = get_rx_gain_range(ALL_GAINS, chan);
1445 const double gain_range_width = gain_range.stop() - gain_range.start();
1446 // In case we have a device without a range of gains:
1447 if (gain_range_width == 0.0) {
1448 return 0;
1449 }
1450 const double norm_gain =
1451 (get_rx_gain(ALL_GAINS, chan) - gain_range.start()) / gain_range_width;
1452 // Avoid rounding errors:
1453 return std::max(std::min(norm_gain, 1.0), 0.0);
1454 }
1455
get_rx_gain_range(const std::string & name,size_t chan=0)1456 gain_range_t get_rx_gain_range(const std::string& name, size_t chan = 0)
1457 {
1458 auto& rx_chain = _get_rx_chan(chan);
1459 return rx_chain.radio->get_rx_gain_range(name, rx_chain.block_chan);
1460 }
1461
get_rx_gain_names(size_t chan=0)1462 std::vector<std::string> get_rx_gain_names(size_t chan = 0)
1463 {
1464 auto& rx_chain = _get_rx_chan(chan);
1465 return rx_chain.radio->get_rx_gain_names(rx_chain.block_chan);
1466 }
1467
has_rx_power_reference(const size_t chan=0)1468 bool has_rx_power_reference(const size_t chan = 0)
1469 {
1470 auto& rx_chain = _get_rx_chan(chan);
1471 return rx_chain.radio->has_rx_power_reference(rx_chain.block_chan);
1472 }
1473
set_rx_power_reference(const double power_dbm,const size_t chan=0)1474 void set_rx_power_reference(const double power_dbm, const size_t chan = 0)
1475 {
1476 MUX_RX_API_CALL(set_rx_power_reference, power_dbm);
1477 auto& rx_chain = _get_rx_chan(chan);
1478 rx_chain.radio->set_rx_power_reference(power_dbm, rx_chain.block_chan);
1479 }
1480
get_rx_power_reference(const size_t chan=0)1481 double get_rx_power_reference(const size_t chan = 0)
1482 {
1483 auto& rx_chain = _get_rx_chan(chan);
1484 return rx_chain.radio->get_rx_power_reference(rx_chain.block_chan);
1485 }
1486
get_rx_power_range(const size_t chan)1487 meta_range_t get_rx_power_range(const size_t chan)
1488 {
1489 auto& rx_chain = _get_rx_chan(chan);
1490 return rx_chain.radio->get_rx_power_range(rx_chain.block_chan);
1491 }
1492
set_rx_antenna(const std::string & ant,size_t chan=0)1493 void set_rx_antenna(const std::string& ant, size_t chan = 0)
1494 {
1495 MUX_RX_API_CALL(set_rx_antenna, ant);
1496 auto& rx_chain = _get_rx_chan(chan);
1497 rx_chain.radio->set_rx_antenna(ant, rx_chain.block_chan);
1498 }
1499
get_rx_antenna(size_t chan=0)1500 std::string get_rx_antenna(size_t chan = 0)
1501 {
1502 auto& rx_chain = _get_rx_chan(chan);
1503 return rx_chain.radio->get_rx_antenna(rx_chain.block_chan);
1504 }
1505
get_rx_antennas(size_t chan=0)1506 std::vector<std::string> get_rx_antennas(size_t chan = 0)
1507 {
1508 auto& rx_chain = _get_rx_chan(chan);
1509 return rx_chain.radio->get_rx_antennas(rx_chain.block_chan);
1510 }
1511
set_rx_bandwidth(double bandwidth,size_t chan=0)1512 void set_rx_bandwidth(double bandwidth, size_t chan = 0)
1513 {
1514 MUX_RX_API_CALL(set_rx_bandwidth, bandwidth);
1515 auto& rx_chain = _get_rx_chan(chan);
1516 rx_chain.radio->set_rx_bandwidth(bandwidth, rx_chain.block_chan);
1517 }
1518
get_rx_bandwidth(size_t chan=0)1519 double get_rx_bandwidth(size_t chan = 0)
1520 {
1521 auto& rx_chain = _get_rx_chan(chan);
1522 return rx_chain.radio->get_rx_bandwidth(rx_chain.block_chan);
1523 }
1524
get_rx_bandwidth_range(size_t chan=0)1525 meta_range_t get_rx_bandwidth_range(size_t chan = 0)
1526 {
1527 auto& rx_chain = _get_rx_chan(chan);
1528 return rx_chain.radio->get_rx_bandwidth_range(rx_chain.block_chan);
1529 }
1530
get_rx_dboard_iface(size_t chan=0)1531 dboard_iface::sptr get_rx_dboard_iface(size_t chan = 0)
1532 {
1533 auto& rx_chain = _get_rx_chan(chan);
1534 return rx_chain.radio->get_tree()->access<dboard_iface::sptr>("iface").get();
1535 }
1536
get_rx_sensor(const std::string & name,size_t chan=0)1537 sensor_value_t get_rx_sensor(const std::string& name, size_t chan = 0)
1538 {
1539 auto rx_chain = _get_rx_chan(chan);
1540 return rx_chain.radio->get_rx_sensor(name, rx_chain.block_chan);
1541 }
1542
get_rx_sensor_names(size_t chan=0)1543 std::vector<std::string> get_rx_sensor_names(size_t chan = 0)
1544 {
1545 auto rx_chain = _get_rx_chan(chan);
1546 return rx_chain.radio->get_rx_sensor_names(rx_chain.block_chan);
1547 }
1548
set_rx_dc_offset(const bool enb,size_t chan=ALL_CHANS)1549 void set_rx_dc_offset(const bool enb, size_t chan = ALL_CHANS)
1550 {
1551 MUX_RX_API_CALL(set_rx_dc_offset, enb);
1552 const auto rx_chain = _get_rx_chan(chan);
1553 rx_chain.radio->set_rx_dc_offset(enb, rx_chain.block_chan);
1554 }
1555
set_rx_dc_offset(const std::complex<double> & offset,size_t chan=ALL_CHANS)1556 void set_rx_dc_offset(const std::complex<double>& offset, size_t chan = ALL_CHANS)
1557 {
1558 MUX_RX_API_CALL(set_rx_dc_offset, offset);
1559 const auto rx_chain = _get_rx_chan(chan);
1560 rx_chain.radio->set_rx_dc_offset(offset, rx_chain.block_chan);
1561 }
1562
get_rx_dc_offset_range(size_t chan=0)1563 meta_range_t get_rx_dc_offset_range(size_t chan = 0)
1564 {
1565 auto rx_chain = _get_rx_chan(chan);
1566 return rx_chain.radio->get_rx_dc_offset_range(rx_chain.block_chan);
1567 }
1568
set_rx_iq_balance(const bool enb,size_t chan)1569 void set_rx_iq_balance(const bool enb, size_t chan)
1570 {
1571 MUX_RX_API_CALL(set_rx_iq_balance, enb);
1572 auto rx_chain = _get_rx_chan(chan);
1573 rx_chain.radio->set_rx_iq_balance(enb, rx_chain.block_chan);
1574 }
1575
set_rx_iq_balance(const std::complex<double> & correction,size_t chan=ALL_CHANS)1576 void set_rx_iq_balance(
1577 const std::complex<double>& correction, size_t chan = ALL_CHANS)
1578 {
1579 MUX_RX_API_CALL(set_rx_iq_balance, correction);
1580 const auto rx_chain = _get_rx_chan(chan);
1581 rx_chain.radio->set_rx_iq_balance(correction, rx_chain.block_chan);
1582 }
1583
1584 /*******************************************************************
1585 * TX methods
1586 ******************************************************************/
_generate_tx_radio_chan(block_id_t radio_id,size_t block_chan)1587 tx_chan_t _generate_tx_radio_chan(block_id_t radio_id, size_t block_chan)
1588 {
1589 auto radio_blk = _graph->get_block<uhd::rfnoc::radio_control>(radio_id);
1590 // Now on to the DUC chain
1591 auto radio_sink_chain = get_block_chain(_graph, radio_id, block_chan, false);
1592
1593 // Find out if we have a DUC in the radio block chain
1594 auto duc_port_def = [this, radio_sink_chain, radio_id, block_chan]() {
1595 try {
1596 for (auto edge : radio_sink_chain) {
1597 if (block_id_t(edge.src_blockid).match("DUC")) {
1598 if (edge.src_port != block_chan) {
1599 /* We don't expect this to happen very often. But in
1600 * the case that port numbers don't match, we need to
1601 * disable DUC control to ensure we're not controlling
1602 * another channel's DDC
1603 */
1604 UHD_LOGGER_WARNING("MULTI_USRP")
1605 << "DUC in radio chain " << radio_id << ":"
1606 << std::to_string(block_chan)
1607 << " not connected to the same port number! "
1608 "Disabling DUC control.";
1609 break;
1610 }
1611 auto ddc_blk = _graph->get_block<uhd::rfnoc::duc_block_control>(
1612 edge.src_blockid);
1613 return std::tuple<uhd::rfnoc::duc_block_control::sptr, size_t>(
1614 ddc_blk, block_chan);
1615 }
1616 }
1617 } catch (const uhd::exception&) {
1618 UHD_LOGGER_DEBUG("MULTI_USRP")
1619 << "No DDC found for radio block " << radio_id << ":"
1620 << std::to_string(block_chan);
1621 // Then just return a nullptr
1622 }
1623 return std::tuple<uhd::rfnoc::duc_block_control::sptr, size_t>(nullptr, 0);
1624 }();
1625
1626 // Create the TX chan
1627 return tx_chan_t(
1628 {radio_blk, std::get<0>(duc_port_def), block_chan, radio_sink_chain});
1629 }
1630
_generate_mboard_tx_chans(const uhd::usrp::subdev_spec_t & spec,size_t mboard)1631 std::vector<tx_chan_t> _generate_mboard_tx_chans(
1632 const uhd::usrp::subdev_spec_t& spec, size_t mboard)
1633 {
1634 // Discover all of the radios on our devices and create a mapping between radio
1635 // chains and channel numbers
1636 auto radio_blk_ids = _graph->find_blocks(std::to_string(mboard) + "/Radio");
1637 // If we don't find any radios, we don't have a multi_usrp object
1638 if (radio_blk_ids.empty()) {
1639 throw uhd::runtime_error(
1640 "[multi_usrp] No radios found in the requested mboard: "
1641 + std::to_string(mboard));
1642 }
1643
1644 // Iterate through the subdev pairs, and try to find a radio that matches
1645 std::vector<tx_chan_t> new_chans;
1646 for (auto chan_subdev_pair : spec) {
1647 bool subdev_found = false;
1648 for (auto radio_id : radio_blk_ids) {
1649 auto radio_blk = _graph->get_block<uhd::rfnoc::radio_control>(radio_id);
1650 size_t block_chan;
1651 try {
1652 block_chan = radio_blk->get_chan_from_dboard_fe(
1653 chan_subdev_pair.sd_name, TX_DIRECTION);
1654 } catch (const uhd::lookup_error&) {
1655 // This is OK, since we're probing all radios, this
1656 // particular radio may not have the requested frontend name
1657 // so it's not one that we want in this list.
1658 continue;
1659 }
1660 subdev_spec_pair_t radio_subdev(radio_blk->get_slot_name(),
1661 radio_blk->get_dboard_fe_from_chan(block_chan, uhd::TX_DIRECTION));
1662 if (chan_subdev_pair == radio_subdev) {
1663 new_chans.push_back(_generate_tx_radio_chan(radio_id, block_chan));
1664 subdev_found = true;
1665 }
1666 }
1667 if (!subdev_found) {
1668 std::string err_msg("Could not find radio on mboard "
1669 + std::to_string(mboard) + " that matches subdev "
1670 + chan_subdev_pair.db_name + ":"
1671 + chan_subdev_pair.sd_name);
1672 UHD_LOG_ERROR("MULTI_USRP", err_msg);
1673 throw uhd::lookup_error(err_msg);
1674 }
1675 }
1676 UHD_LOG_TRACE("MULTI_USRP",
1677 std::string("Using TX subdev " + spec.to_string() + ", found ")
1678 + std::to_string(new_chans.size()) + " channels for mboard "
1679 + std::to_string(mboard));
1680 return new_chans;
1681 }
1682
set_tx_subdev_spec(const uhd::usrp::subdev_spec_t & spec,size_t mboard=ALL_MBOARDS)1683 void set_tx_subdev_spec(
1684 const uhd::usrp::subdev_spec_t& spec, size_t mboard = ALL_MBOARDS)
1685 {
1686 /* TODO: Refactor with get_rx_subdev_spec- the algorithms are the same, just the
1687 * types are different
1688 */
1689 // First, generate a vector of the tx channels that we need to register
1690 auto new_tx_chans = [this, spec, mboard]() {
1691 /* When setting the subdev spec in multiple mboard scenarios, there are two
1692 * cases we need to handle:
1693 * 1. Setting all mboard to the same subdev spec. This is the easy case.
1694 * 2. Setting a single mboard's subdev spec. In this case, we need to update
1695 * the requested mboard's subdev spec, and keep the old subdev spec for the
1696 * other mboards.
1697 */
1698 std::vector<tx_chan_t> new_tx_chans;
1699 for (size_t current_mboard = 0; current_mboard < get_num_mboards();
1700 ++current_mboard) {
1701 auto current_spec = [this, spec, mboard, current_mboard]() {
1702 if (mboard == ALL_MBOARDS || mboard == current_mboard) {
1703 // Update all mboards to the same subdev spec OR
1704 // only update this mboard to the new subdev spec
1705 return spec;
1706 } else {
1707 // Keep the old subdev spec for this mboard
1708 return get_tx_subdev_spec(current_mboard);
1709 }
1710 }();
1711 auto new_mboard_chans =
1712 _generate_mboard_tx_chans(current_spec, current_mboard);
1713 new_tx_chans.insert(
1714 new_tx_chans.end(), new_mboard_chans.begin(), new_mboard_chans.end());
1715 }
1716 return new_tx_chans;
1717 }();
1718
1719 // Disconnect and clear existing chains
1720 for (size_t i = 0; i < _tx_chans.size(); i++) {
1721 _disconnect_tx_chain(i);
1722 }
1723 _tx_chans.clear();
1724
1725 // Register new chains
1726 size_t musrp_tx_channel = 0;
1727 for (auto tx_chan : new_tx_chans) {
1728 _tx_chans.emplace(musrp_tx_channel++, tx_chan);
1729 }
1730 }
1731
get_tx_subdev_spec(size_t mboard=0)1732 uhd::usrp::subdev_spec_t get_tx_subdev_spec(size_t mboard = 0)
1733 {
1734 uhd::usrp::subdev_spec_t result;
1735 for (size_t tx_chan = 0; tx_chan < get_tx_num_channels(); tx_chan++) {
1736 auto& tx_chain = _tx_chans.at(tx_chan);
1737 if (tx_chain.radio->get_block_id().get_device_no() == mboard) {
1738 result.push_back(
1739 uhd::usrp::subdev_spec_pair_t(tx_chain.radio->get_slot_name(),
1740 tx_chain.radio->get_dboard_fe_from_chan(
1741 tx_chain.block_chan, uhd::TX_DIRECTION)));
1742 }
1743 }
1744
1745 return result;
1746 }
1747
get_tx_num_channels(void)1748 size_t get_tx_num_channels(void)
1749 {
1750 return _tx_chans.size();
1751 }
1752
get_tx_subdev_name(size_t chan=0)1753 std::string get_tx_subdev_name(size_t chan = 0)
1754 {
1755 auto tx_chain = _get_tx_chan(chan);
1756 return tx_chain.radio->get_fe_name(tx_chain.block_chan, uhd::TX_DIRECTION);
1757 }
1758
set_tx_rate(double rate,size_t chan=ALL_CHANS)1759 void set_tx_rate(double rate, size_t chan = ALL_CHANS)
1760 {
1761 std::lock_guard<std::recursive_mutex> l(_graph_mutex);
1762 MUX_TX_API_CALL(set_tx_rate, rate);
1763 const double actual_rate = [&]() {
1764 auto tx_chain = _get_tx_chan(chan);
1765 if (tx_chain.duc) {
1766 return tx_chain.duc->set_input_rate(rate, tx_chain.block_chan);
1767 } else {
1768 return tx_chain.radio->set_rate(rate);
1769 }
1770 }();
1771 if (actual_rate != rate) {
1772 UHD_LOGGER_WARNING("MULTI_USRP")
1773 << boost::format(
1774 "Could not set TX rate to %.3f MHz. Actual rate is %.3f MHz")
1775 % (rate / 1.0e6) % (actual_rate / 1.0e6);
1776 }
1777 _tx_rates[chan] = actual_rate;
1778 }
1779
get_tx_rate(size_t chan=0)1780 double get_tx_rate(size_t chan = 0)
1781 {
1782 std::lock_guard<std::recursive_mutex> l(_graph_mutex);
1783 auto& tx_chain = _get_tx_chan(chan);
1784 if (tx_chain.duc) {
1785 return tx_chain.duc->get_input_rate(tx_chain.block_chan);
1786 }
1787 return tx_chain.radio->get_rate();
1788 }
1789
get_tx_rates(size_t chan=0)1790 meta_range_t get_tx_rates(size_t chan = 0)
1791 {
1792 std::lock_guard<std::recursive_mutex> l(_graph_mutex);
1793 auto tx_chain = _get_tx_chan(chan);
1794 if (tx_chain.duc) {
1795 return tx_chain.duc->get_input_rates(tx_chain.block_chan);
1796 }
1797 return tx_chain.radio->get_rate_range();
1798 }
1799
set_tx_freq(const tune_request_t & tune_request,size_t chan=0)1800 tune_result_t set_tx_freq(const tune_request_t& tune_request, size_t chan = 0)
1801 {
1802 std::lock_guard<std::recursive_mutex> l(_graph_mutex);
1803 auto tx_chain = _get_tx_chan(chan);
1804
1805 tx_chain.radio->set_tx_tune_args(tune_request.args, tx_chain.block_chan);
1806 //------------------------------------------------------------------
1807 //-- calculate the tunable frequency ranges of the system
1808 //------------------------------------------------------------------
1809 freq_range_t tune_range =
1810 (tx_chain.duc)
1811 ? make_overall_tune_range(
1812 tx_chain.radio->get_tx_frequency_range(tx_chain.block_chan),
1813 tx_chain.duc->get_frequency_range(tx_chain.block_chan),
1814 tx_chain.radio->get_tx_bandwidth(tx_chain.block_chan))
1815 : tx_chain.radio->get_tx_frequency_range(tx_chain.block_chan);
1816
1817 freq_range_t rf_range =
1818 tx_chain.radio->get_tx_frequency_range(tx_chain.block_chan);
1819 freq_range_t dsp_range =
1820 (tx_chain.duc) ? tx_chain.duc->get_frequency_range(tx_chain.block_chan)
1821 : meta_range_t(0, 0);
1822 // Create lambdas to feed to tune_xx_subdev_and_dsp()
1823 // Note: If there is no DDC present, register empty lambdas for the DSP functions
1824 auto set_rf_freq = [tx_chain](double freq) {
1825 tx_chain.radio->set_tx_frequency(freq, tx_chain.block_chan);
1826 };
1827 auto get_rf_freq = [tx_chain](void) {
1828 return tx_chain.radio->get_tx_frequency(tx_chain.block_chan);
1829 };
1830 auto set_dsp_freq = [tx_chain](double freq) {
1831 (tx_chain.duc) ? tx_chain.duc->set_freq(freq, tx_chain.block_chan) : 0;
1832 };
1833 auto get_dsp_freq = [tx_chain](void) {
1834 return (tx_chain.duc) ? tx_chain.duc->get_freq(tx_chain.block_chan) : 0.0;
1835 };
1836 return tune_xx_subdev_and_dsp(TX_SIGN,
1837 tune_range,
1838 rf_range,
1839 dsp_range,
1840 set_rf_freq,
1841 get_rf_freq,
1842 set_dsp_freq,
1843 get_dsp_freq,
1844 tune_request);
1845 }
1846
get_tx_freq(size_t chan=0)1847 double get_tx_freq(size_t chan = 0)
1848 {
1849 auto& tx_chain = _get_tx_chan(chan);
1850 // extract actual dsp and IF frequencies
1851 const double actual_rf_freq =
1852 tx_chain.radio->get_tx_frequency(tx_chain.block_chan);
1853 const double actual_dsp_freq =
1854 (tx_chain.duc) ? tx_chain.duc->get_freq(tx_chain.block_chan) : 0.0;
1855
1856 return actual_rf_freq - actual_dsp_freq * TX_SIGN;
1857 }
1858
get_tx_freq_range(size_t chan=0)1859 freq_range_t get_tx_freq_range(size_t chan = 0)
1860 {
1861 auto tx_chain = _get_tx_chan(chan);
1862 return (tx_chain.duc)
1863 ? make_overall_tune_range(get_fe_tx_freq_range(chan),
1864 tx_chain.duc->get_frequency_range(tx_chain.block_chan),
1865 tx_chain.radio->get_tx_bandwidth(tx_chain.block_chan))
1866 : get_fe_tx_freq_range(chan);
1867 }
1868
get_fe_tx_freq_range(size_t chan=0)1869 freq_range_t get_fe_tx_freq_range(size_t chan = 0)
1870 {
1871 auto tx_chain = _get_tx_chan(chan);
1872 return tx_chain.radio->get_tx_frequency_range(tx_chain.block_chan);
1873 }
1874
set_tx_gain(double gain,const std::string & name,size_t chan=0)1875 void set_tx_gain(double gain, const std::string& name, size_t chan = 0)
1876 {
1877 MUX_TX_API_CALL(set_tx_gain, gain, name);
1878 auto tx_chain = _get_tx_chan(chan);
1879 tx_chain.radio->set_tx_gain(gain, name, tx_chain.block_chan);
1880 }
1881
get_tx_gain_profile_names(const size_t chan=0)1882 std::vector<std::string> get_tx_gain_profile_names(const size_t chan = 0)
1883 {
1884 auto tx_chain = _get_tx_chan(chan);
1885 return tx_chain.radio->get_tx_gain_profile_names(tx_chain.block_chan);
1886 }
1887
set_tx_gain_profile(const std::string & profile,const size_t chan=0)1888 void set_tx_gain_profile(const std::string& profile, const size_t chan = 0)
1889 {
1890 MUX_TX_API_CALL(set_tx_gain_profile, profile);
1891 auto tx_chain = _get_tx_chan(chan);
1892 tx_chain.radio->set_tx_gain_profile(profile, tx_chain.block_chan);
1893 }
1894
get_tx_gain_profile(const size_t chan=0)1895 std::string get_tx_gain_profile(const size_t chan = 0)
1896 {
1897 auto tx_chain = _get_tx_chan(chan);
1898 return tx_chain.radio->get_tx_gain_profile(tx_chain.block_chan);
1899 }
1900
set_normalized_tx_gain(double gain,size_t chan=0)1901 void set_normalized_tx_gain(double gain, size_t chan = 0)
1902 {
1903 if (gain > 1.0 || gain < 0.0) {
1904 throw uhd::runtime_error("Normalized gain out of range, must be in [0, 1].");
1905 }
1906 MUX_TX_API_CALL(set_normalized_tx_gain, gain);
1907 gain_range_t gain_range = get_tx_gain_range(ALL_GAINS, chan);
1908 double abs_gain =
1909 (gain * (gain_range.stop() - gain_range.start())) + gain_range.start();
1910 set_tx_gain(abs_gain, ALL_GAINS, chan);
1911 }
1912
get_tx_gain(const std::string & name,size_t chan=0)1913 double get_tx_gain(const std::string& name, size_t chan = 0)
1914 {
1915 auto tx_chain = _get_tx_chan(chan);
1916 return tx_chain.radio->get_tx_gain(name, tx_chain.block_chan);
1917 }
1918
get_normalized_tx_gain(size_t chan=0)1919 double get_normalized_tx_gain(size_t chan = 0)
1920 {
1921 gain_range_t gain_range = get_tx_gain_range(ALL_GAINS, chan);
1922 const double gain_range_width = gain_range.stop() - gain_range.start();
1923 // In case we have a device without a range of gains:
1924 if (gain_range_width == 0.0) {
1925 return 0;
1926 }
1927 const double norm_gain =
1928 (get_tx_gain(ALL_GAINS, chan) - gain_range.start()) / gain_range_width;
1929 // Avoid rounding errors:
1930 return std::max(std::min(norm_gain, 1.0), 0.0);
1931 }
1932
get_tx_gain_range(const std::string & name,size_t chan=0)1933 gain_range_t get_tx_gain_range(const std::string& name, size_t chan = 0)
1934 {
1935 auto tx_chain = _get_tx_chan(chan);
1936 return tx_chain.radio->get_tx_gain_range(name, tx_chain.block_chan);
1937 }
1938
get_tx_gain_names(size_t chan=0)1939 std::vector<std::string> get_tx_gain_names(size_t chan = 0)
1940 {
1941 auto tx_chain = _get_tx_chan(chan);
1942 return tx_chain.radio->get_tx_gain_names(tx_chain.block_chan);
1943 }
1944
has_tx_power_reference(const size_t chan=0)1945 bool has_tx_power_reference(const size_t chan = 0)
1946 {
1947 auto& tx_chain = _get_tx_chan(chan);
1948 return tx_chain.radio->has_rx_power_reference(tx_chain.block_chan);
1949 }
1950
set_tx_power_reference(const double power_dbm,const size_t chan=0)1951 void set_tx_power_reference(const double power_dbm, const size_t chan = 0)
1952 {
1953 MUX_TX_API_CALL(set_tx_power_reference, power_dbm);
1954 auto& tx_chain = _get_tx_chan(chan);
1955 tx_chain.radio->set_tx_power_reference(power_dbm, tx_chain.block_chan);
1956 }
1957
get_tx_power_reference(const size_t chan=0)1958 double get_tx_power_reference(const size_t chan = 0)
1959 {
1960 auto& tx_chain = _get_tx_chan(chan);
1961 return tx_chain.radio->get_tx_power_reference(tx_chain.block_chan);
1962 }
1963
get_tx_power_range(const size_t chan)1964 meta_range_t get_tx_power_range(const size_t chan)
1965 {
1966 auto& tx_chain = _get_tx_chan(chan);
1967 return tx_chain.radio->get_tx_power_range(tx_chain.block_chan);
1968 }
1969
set_tx_antenna(const std::string & ant,size_t chan=0)1970 void set_tx_antenna(const std::string& ant, size_t chan = 0)
1971 {
1972 MUX_TX_API_CALL(set_tx_antenna, ant);
1973 auto tx_chain = _get_tx_chan(chan);
1974 tx_chain.radio->set_tx_antenna(ant, tx_chain.block_chan);
1975 }
1976
get_tx_antenna(size_t chan=0)1977 std::string get_tx_antenna(size_t chan = 0)
1978 {
1979 auto& tx_chain = _get_tx_chan(chan);
1980 return tx_chain.radio->get_tx_antenna(tx_chain.block_chan);
1981 }
1982
get_tx_antennas(size_t chan=0)1983 std::vector<std::string> get_tx_antennas(size_t chan = 0)
1984 {
1985 auto& tx_chain = _get_tx_chan(chan);
1986 return tx_chain.radio->get_tx_antennas(tx_chain.block_chan);
1987 }
1988
set_tx_bandwidth(double bandwidth,size_t chan=0)1989 void set_tx_bandwidth(double bandwidth, size_t chan = 0)
1990 {
1991 MUX_TX_API_CALL(set_tx_bandwidth, bandwidth);
1992 auto tx_chain = _get_tx_chan(chan);
1993 tx_chain.radio->set_tx_bandwidth(bandwidth, tx_chain.block_chan);
1994 }
1995
get_tx_bandwidth(size_t chan=0)1996 double get_tx_bandwidth(size_t chan = 0)
1997 {
1998 auto tx_chain = _get_tx_chan(chan);
1999 return tx_chain.radio->get_tx_bandwidth(tx_chain.block_chan);
2000 }
2001
get_tx_bandwidth_range(size_t chan=0)2002 meta_range_t get_tx_bandwidth_range(size_t chan = 0)
2003 {
2004 auto tx_chain = _get_tx_chan(chan);
2005 return tx_chain.radio->get_tx_bandwidth_range(tx_chain.block_chan);
2006 }
2007
get_tx_dboard_iface(size_t chan=0)2008 dboard_iface::sptr get_tx_dboard_iface(size_t chan = 0)
2009 {
2010 auto& tx_chain = _get_tx_chan(chan);
2011 return tx_chain.radio->get_tree()->access<dboard_iface::sptr>("iface").get();
2012 }
2013
get_tx_sensor(const std::string & name,size_t chan=0)2014 sensor_value_t get_tx_sensor(const std::string& name, size_t chan = 0)
2015 {
2016 auto tx_chain = _get_tx_chan(chan);
2017 return tx_chain.radio->get_tx_sensor(name, tx_chain.block_chan);
2018 }
2019
get_tx_sensor_names(size_t chan=0)2020 std::vector<std::string> get_tx_sensor_names(size_t chan = 0)
2021 {
2022 auto tx_chain = _get_tx_chan(chan);
2023 return tx_chain.radio->get_tx_sensor_names(tx_chain.block_chan);
2024 }
2025
set_tx_dc_offset(const std::complex<double> & offset,size_t chan=ALL_CHANS)2026 void set_tx_dc_offset(const std::complex<double>& offset, size_t chan = ALL_CHANS)
2027 {
2028 MUX_TX_API_CALL(set_tx_dc_offset, offset);
2029 const auto tx_chain = _get_tx_chan(chan);
2030 tx_chain.radio->set_tx_dc_offset(offset, tx_chain.block_chan);
2031 }
2032
get_tx_dc_offset_range(size_t chan=0)2033 meta_range_t get_tx_dc_offset_range(size_t chan = 0)
2034 {
2035 auto tx_chain = _get_tx_chan(chan);
2036 return tx_chain.radio->get_tx_dc_offset_range(tx_chain.block_chan);
2037 }
2038
set_tx_iq_balance(const std::complex<double> & correction,size_t chan=ALL_CHANS)2039 void set_tx_iq_balance(
2040 const std::complex<double>& correction, size_t chan = ALL_CHANS)
2041 {
2042 MUX_TX_API_CALL(set_tx_iq_balance, correction);
2043 const auto tx_chain = _get_tx_chan(chan);
2044 tx_chain.radio->set_tx_iq_balance(correction, tx_chain.block_chan);
2045 }
2046
2047 /*******************************************************************
2048 * GPIO methods
2049 ******************************************************************/
2050 /*! Helper function to identify the radio and the bank on that radio.
2051 *
2052 * Background: Historically, multi_usrp has made up its own GPIO bank names,
2053 * unrelated to the radios. Now, we need to be a bit more clever to get that
2054 * legacy behaviour to work.
2055 *
2056 * Here's the algorithm:
2057 * - If the bank ends with 'A' or 'B' we'll use that to identify the radio
2058 * - Otherwise, we'll pick the first radio
2059 * - If the bank ends with 'A' or 'B' we strip that suffix
2060 *
2061 * The returned radio will now have a GPIO bank with the returned name.
2062 */
_get_gpio_radio_bank(const std::string & bank,const size_t mboard)2063 std::pair<uhd::rfnoc::radio_control::sptr, std::string> _get_gpio_radio_bank(
2064 const std::string& bank, const size_t mboard)
2065 {
2066 UHD_ASSERT_THROW(!bank.empty());
2067 char suffix = bank[bank.size() - 1];
2068 std::string slot_name;
2069 if (suffix == 'A' || suffix == 'a') {
2070 slot_name = "A";
2071 }
2072 if (suffix == 'B' || suffix == 'b') {
2073 slot_name = "B";
2074 }
2075
2076 uhd::rfnoc::radio_control::sptr radio = [bank, mboard, slot_name, this]() {
2077 auto radio_blocks = _graph->find_blocks<uhd::rfnoc::radio_control>(
2078 std::to_string(mboard) + "/Radio");
2079 for (auto radio_id : radio_blocks) {
2080 auto radio = _graph->get_block<uhd::rfnoc::radio_control>(radio_id);
2081 if (slot_name.empty() || radio->get_slot_name() == slot_name) {
2082 return radio;
2083 }
2084 }
2085 throw uhd::runtime_error(std::string("Could not match GPIO bank ") + bank
2086 + " to a radio block controller.");
2087 }();
2088
2089 const std::string normalized_bank = [radio, bank]() {
2090 auto radio_banks = radio->get_gpio_banks();
2091 for (auto& radio_bank : radio_banks) {
2092 if (bank.find(radio_bank) == 0) {
2093 return radio_bank;
2094 }
2095 }
2096 throw uhd::runtime_error(std::string("Could not match GPIO bank ") + bank
2097 + " to radio " + radio->get_unique_id());
2098 }();
2099
2100 return {radio, normalized_bank};
2101 }
2102
get_gpio_banks(const size_t mboard)2103 std::vector<std::string> get_gpio_banks(const size_t mboard)
2104 {
2105 auto radio_blocks = _graph->find_blocks<uhd::rfnoc::radio_control>(
2106 std::to_string(mboard) + "/Radio");
2107 std::vector<std::string> gpio_banks;
2108 for (auto radio_id : radio_blocks) {
2109 auto radio = _graph->get_block<uhd::rfnoc::radio_control>(radio_id);
2110 auto radio_banks = radio->get_gpio_banks();
2111 for (const auto& bank : radio_banks) {
2112 gpio_banks.push_back(bank + radio->get_slot_name());
2113 }
2114 }
2115
2116 return gpio_banks;
2117 }
2118
set_gpio_attr(const std::string & bank,const std::string & attr,const uint32_t value,const uint32_t mask=0xffffffff,const size_t mboard=0)2119 void set_gpio_attr(const std::string& bank,
2120 const std::string& attr,
2121 const uint32_t value,
2122 const uint32_t mask = 0xffffffff,
2123 const size_t mboard = 0)
2124 {
2125 auto radio_bank_pair = _get_gpio_radio_bank(bank, mboard);
2126 const uint32_t current =
2127 radio_bank_pair.first->get_gpio_attr(radio_bank_pair.second, attr);
2128 const uint32_t new_value = (current & ~mask) | (value & mask);
2129 radio_bank_pair.first->set_gpio_attr(radio_bank_pair.second, attr, new_value);
2130 }
2131
get_gpio_attr(const std::string & bank,const std::string & attr,const size_t mboard=0)2132 uint32_t get_gpio_attr(
2133 const std::string& bank, const std::string& attr, const size_t mboard = 0)
2134 {
2135 auto radio_bank_pair = _get_gpio_radio_bank(bank, mboard);
2136 return radio_bank_pair.first->get_gpio_attr(radio_bank_pair.second, attr);
2137 }
2138
get_gpio_src_banks(const size_t mboard=0)2139 std::vector<std::string> get_gpio_src_banks(const size_t mboard = 0)
2140 {
2141 return get_mbc(mboard)->get_gpio_banks();
2142 }
2143
get_gpio_srcs(const std::string & bank,const size_t mboard=0)2144 std::vector<std::string> get_gpio_srcs(
2145 const std::string& bank, const size_t mboard = 0)
2146 {
2147 return get_mbc(mboard)->get_gpio_srcs(bank);
2148 }
2149
get_gpio_src(const std::string & bank,const size_t mboard=0)2150 std::vector<std::string> get_gpio_src(
2151 const std::string& bank, const size_t mboard = 0)
2152 {
2153 return get_mbc(mboard)->get_gpio_src(bank);
2154 }
2155
set_gpio_src(const std::string & bank,const std::vector<std::string> & src,const size_t mboard=0)2156 void set_gpio_src(const std::string& bank,
2157 const std::vector<std::string>& src,
2158 const size_t mboard = 0)
2159 {
2160 get_mbc(mboard)->set_gpio_src(bank, src);
2161 }
2162
2163 /*******************************************************************
2164 * Filter API methods
2165 ******************************************************************/
get_rx_filter_names(const size_t chan)2166 std::vector<std::string> get_rx_filter_names(const size_t chan)
2167 {
2168 std::vector<std::string> filter_names;
2169 // Grab the Radio's filters
2170 auto rx_chan = _get_rx_chan(chan);
2171 auto radio_id = rx_chan.radio->get_block_id();
2172 auto radio_ctrl = std::dynamic_pointer_cast<detail::filter_node>(rx_chan.radio);
2173 if (radio_ctrl) {
2174 auto radio_filters = radio_ctrl->get_rx_filter_names(rx_chan.block_chan);
2175 // Prepend the radio's block ID to each filter name
2176 std::transform(radio_filters.begin(),
2177 radio_filters.end(),
2178 radio_filters.begin(),
2179 [radio_id](
2180 std::string name) { return radio_id.to_string() + ":" + name; });
2181 // Add the radio's filter names to the return vector
2182 filter_names.insert(
2183 filter_names.end(), radio_filters.begin(), radio_filters.end());
2184 } else {
2185 UHD_LOG_DEBUG("MULTI_USRP",
2186 "Radio block " + radio_id.to_string() + " does not support filters");
2187 }
2188 // Grab the DDC's filter
2189 auto ddc_id = rx_chan.ddc->get_block_id();
2190 auto ddc_ctrl = std::dynamic_pointer_cast<detail::filter_node>(rx_chan.ddc);
2191 if (ddc_ctrl) {
2192 auto ddc_filters = ddc_ctrl->get_rx_filter_names(rx_chan.block_chan);
2193 // Prepend the DDC's block ID to each filter name
2194 std::transform(ddc_filters.begin(),
2195 ddc_filters.end(),
2196 ddc_filters.begin(),
2197 [ddc_id](std::string name) { return ddc_id.to_string() + ":" + name; });
2198 // Add the radio's filter names to the return vector
2199 filter_names.insert(
2200 filter_names.end(), ddc_filters.begin(), ddc_filters.end());
2201 } else {
2202 UHD_LOG_DEBUG("MULTI_USRP",
2203 "DDC block " + ddc_id.to_string() + " does not support filters");
2204 }
2205 return filter_names;
2206 }
2207
get_rx_filter(const std::string & name,const size_t chan)2208 uhd::filter_info_base::sptr get_rx_filter(const std::string& name, const size_t chan)
2209 {
2210 try {
2211 // The block_id_t constructor is pretty smart; let it handle the parsing.
2212 block_id_t block_id(name);
2213 auto rx_chan = _get_rx_chan(chan);
2214 // The filter name is the `name` after the BLOCK_ID and a `:`
2215 std::string filter_name = name.substr(block_id.to_string().size() + 1);
2216 // Try to dynamic cast either the radio or the DDC to a filter_node, and call
2217 // its filter function
2218 auto block_ctrl = [rx_chan, block_id, chan]() -> noc_block_base::sptr {
2219 if (block_id == rx_chan.radio->get_block_id()) {
2220 return rx_chan.radio;
2221 } else if (block_id == rx_chan.ddc->get_block_id()) {
2222 return rx_chan.ddc;
2223 } else {
2224 throw uhd::runtime_error("Requested block " + block_id.to_string()
2225 + " does not match block ID in channel "
2226 + std::to_string(chan));
2227 }
2228 }();
2229 auto filter_ctrl = std::dynamic_pointer_cast<detail::filter_node>(block_ctrl);
2230 if (filter_ctrl) {
2231 return filter_ctrl->get_rx_filter(filter_name, rx_chan.block_chan);
2232 }
2233 std::string err_msg =
2234 block_ctrl->get_block_id().to_string() + " does not support filters";
2235 UHD_LOG_ERROR("MULTI_USRP", err_msg);
2236 throw uhd::runtime_error(err_msg);
2237 } catch (const uhd::value_error&) {
2238 // Catch the error from the block_id_t constructor and add better logging
2239 UHD_LOG_ERROR("MULTI_USRP",
2240 "Invalid filter name; could not determine block controller from name: "
2241 + name);
2242 throw;
2243 }
2244 }
2245
set_rx_filter(const std::string & name,uhd::filter_info_base::sptr filter,const size_t chan)2246 void set_rx_filter(
2247 const std::string& name, uhd::filter_info_base::sptr filter, const size_t chan)
2248 {
2249 MUX_RX_API_CALL(set_rx_filter, name, filter);
2250 try {
2251 // The block_id_t constructor is pretty smart; let it handle the parsing.
2252 block_id_t block_id(name);
2253 auto rx_chan = _get_rx_chan(chan);
2254 // The filter name is the `name` after the BLOCK_ID and a `:`
2255 std::string filter_name = name.substr(block_id.to_string().size() + 1);
2256 // Try to dynamic cast either the radio or the DDC to a filter_node, and call
2257 // its filter function
2258 auto block_ctrl = [rx_chan, block_id, chan]() -> noc_block_base::sptr {
2259 if (block_id == rx_chan.radio->get_block_id()) {
2260 return rx_chan.radio;
2261 } else if (block_id == rx_chan.ddc->get_block_id()) {
2262 return rx_chan.ddc;
2263 } else {
2264 throw uhd::runtime_error("Requested block " + block_id.to_string()
2265 + " does not match block ID in channel "
2266 + std::to_string(chan));
2267 }
2268 }();
2269 auto filter_ctrl = std::dynamic_pointer_cast<detail::filter_node>(block_ctrl);
2270 if (filter_ctrl) {
2271 return filter_ctrl->set_rx_filter(
2272 filter_name, filter, rx_chan.block_chan);
2273 }
2274 std::string err_msg =
2275 block_ctrl->get_block_id().to_string() + " does not support filters";
2276 UHD_LOG_ERROR("MULTI_USRP", err_msg);
2277 throw uhd::runtime_error(err_msg);
2278 } catch (const uhd::value_error&) {
2279 // Catch the error from the block_id_t constructor and add better logging
2280 UHD_LOG_ERROR("MULTI_USRP",
2281 "Invalid filter name; could not determine block controller from name: "
2282 + name);
2283 throw;
2284 }
2285 }
2286
get_tx_filter_names(const size_t chan)2287 std::vector<std::string> get_tx_filter_names(const size_t chan)
2288 {
2289 std::vector<std::string> filter_names;
2290 // Grab the Radio's filters
2291 auto tx_chan = _get_tx_chan(chan);
2292 auto radio_id = tx_chan.radio->get_block_id();
2293 auto radio_ctrl = std::dynamic_pointer_cast<detail::filter_node>(tx_chan.radio);
2294 if (radio_ctrl) {
2295 auto radio_filters = radio_ctrl->get_tx_filter_names(tx_chan.block_chan);
2296 // Prepend the radio's block ID to each filter name
2297 std::transform(radio_filters.begin(),
2298 radio_filters.end(),
2299 radio_filters.begin(),
2300 [radio_id](
2301 std::string name) { return radio_id.to_string() + ":" + name; });
2302 // Add the radio's filter names to the return vector
2303 filter_names.insert(
2304 filter_names.end(), radio_filters.begin(), radio_filters.end());
2305 } else {
2306 UHD_LOG_DEBUG("MULTI_USRP",
2307 "Radio block " + radio_id.to_string() + " does not support filters");
2308 }
2309 // Grab the DUC's filter
2310 auto duc_id = tx_chan.duc->get_block_id();
2311 auto duc_ctrl = std::dynamic_pointer_cast<detail::filter_node>(tx_chan.duc);
2312 if (duc_ctrl) {
2313 auto duc_filters = duc_ctrl->get_tx_filter_names(tx_chan.block_chan);
2314 // Prepend the DUC's block ID to each filter name
2315 std::transform(duc_filters.begin(),
2316 duc_filters.end(),
2317 duc_filters.begin(),
2318 [duc_id](std::string name) { return duc_id.to_string() + ":" + name; });
2319 // Add the radio's filter names to the return vector
2320 filter_names.insert(
2321 filter_names.end(), duc_filters.begin(), duc_filters.end());
2322 } else {
2323 UHD_LOG_DEBUG("MULTI_USRP",
2324 "DUC block " + duc_id.to_string() + " does not support filters");
2325 }
2326 return filter_names;
2327 }
2328
get_tx_filter(const std::string & name,const size_t chan)2329 uhd::filter_info_base::sptr get_tx_filter(const std::string& name, const size_t chan)
2330 {
2331 try {
2332 // The block_id_t constructor is pretty smart; let it handle the parsing.
2333 block_id_t block_id(name);
2334 auto tx_chan = _get_tx_chan(chan);
2335 // The filter name is the `name` after the BLOCK_ID and a `:`
2336 std::string filter_name = name.substr(block_id.to_string().size() + 1);
2337 // Try to dynamic cast either the radio or the DUC to a filter_node, and call
2338 // its filter function
2339 auto block_ctrl = [tx_chan, block_id, chan]() -> noc_block_base::sptr {
2340 if (block_id == tx_chan.radio->get_block_id()) {
2341 return tx_chan.radio;
2342 } else if (block_id == tx_chan.duc->get_block_id()) {
2343 return tx_chan.duc;
2344 } else {
2345 throw uhd::runtime_error("Requested block " + block_id.to_string()
2346 + " does not match block ID in channel "
2347 + std::to_string(chan));
2348 }
2349 }();
2350 auto filter_ctrl = std::dynamic_pointer_cast<detail::filter_node>(block_ctrl);
2351 if (filter_ctrl) {
2352 return filter_ctrl->get_tx_filter(filter_name, tx_chan.block_chan);
2353 }
2354 std::string err_msg =
2355 block_ctrl->get_block_id().to_string() + " does not support filters";
2356 UHD_LOG_ERROR("MULTI_USRP", err_msg);
2357 throw uhd::runtime_error(err_msg);
2358 } catch (const uhd::value_error&) {
2359 // Catch the error from the block_id_t constructor and add better logging
2360 UHD_LOG_ERROR("MULTI_USRP",
2361 "Invalid filter name; could not determine block controller from name: "
2362 + name);
2363 throw;
2364 }
2365 }
2366
set_tx_filter(const std::string & name,uhd::filter_info_base::sptr filter,const size_t chan)2367 void set_tx_filter(
2368 const std::string& name, uhd::filter_info_base::sptr filter, const size_t chan)
2369 {
2370 MUX_TX_API_CALL(set_tx_filter, name, filter);
2371 try {
2372 // The block_id_t constructor is pretty smart; let it handle the parsing.
2373 block_id_t block_id(name);
2374 auto tx_chan = _get_tx_chan(chan);
2375 // The filter name is the `name` after the BLOCK_ID and a `:`
2376 std::string filter_name = name.substr(block_id.to_string().size() + 1);
2377 // Try to dynamic cast either the radio or the DUC to a filter_node, and call
2378 // its filter function
2379 auto block_ctrl = [tx_chan, block_id, chan]() -> noc_block_base::sptr {
2380 if (block_id == tx_chan.radio->get_block_id()) {
2381 return tx_chan.radio;
2382 } else if (block_id == tx_chan.duc->get_block_id()) {
2383 return tx_chan.duc;
2384 } else {
2385 throw uhd::runtime_error("Requested block " + block_id.to_string()
2386 + " does not match block ID in channel "
2387 + std::to_string(chan));
2388 }
2389 }();
2390 auto filter_ctrl = std::dynamic_pointer_cast<detail::filter_node>(block_ctrl);
2391 if (filter_ctrl) {
2392 return filter_ctrl->set_tx_filter(
2393 filter_name, filter, tx_chan.block_chan);
2394 }
2395 std::string err_msg =
2396 block_ctrl->get_block_id().to_string() + " does not support filters";
2397 UHD_LOG_ERROR("MULTI_USRP", err_msg);
2398 throw uhd::runtime_error(err_msg);
2399 } catch (const uhd::value_error&) {
2400 // Catch the error from the block_id_t constructor and add better logging
2401 UHD_LOG_ERROR("MULTI_USRP",
2402 "Invalid filter name; could not determine block controller from name: "
2403 + name);
2404 throw;
2405 }
2406 }
2407
2408 private:
2409 /**************************************************************************
2410 * Private Helpers
2411 *************************************************************************/
get_mbc(const size_t mb_idx)2412 mb_controller::sptr get_mbc(const size_t mb_idx)
2413 {
2414 if (mb_idx >= get_num_mboards()) {
2415 throw uhd::key_error(
2416 std::string("No such mboard: ") + std::to_string(mb_idx));
2417 }
2418 return _graph->get_mb_controller(mb_idx);
2419 }
2420
_get_rx_chan(const size_t chan)2421 rx_chan_t& _get_rx_chan(const size_t chan)
2422 {
2423 if (!_rx_chans.count(chan)) {
2424 throw uhd::key_error(
2425 std::string("Invalid RX channel: ") + std::to_string(chan));
2426 }
2427 return _rx_chans.at(chan);
2428 }
2429
_get_tx_chan(const size_t chan)2430 tx_chan_t& _get_tx_chan(const size_t chan)
2431 {
2432 if (!_tx_chans.count(chan)) {
2433 throw uhd::key_error(
2434 std::string("Invalid TX channel: ") + std::to_string(chan));
2435 }
2436 return _tx_chans.at(chan);
2437 }
2438
_connect_rx_chain(size_t chan)2439 void _connect_rx_chain(size_t chan)
2440 {
2441 auto rx_chan = _rx_chans.at(chan);
2442 for (auto edge : rx_chan.edge_list) {
2443 if (block_id_t(edge.dst_blockid).match(NODE_ID_SEP)) {
2444 break;
2445 }
2446 UHD_LOG_TRACE(
2447 "MULTI_USRP", std::string("Connecting RX edge: ") + edge.to_string());
2448 _graph->connect(
2449 edge.src_blockid, edge.src_port, edge.dst_blockid, edge.dst_port);
2450 }
2451 }
2452
_connect_rx_chains(std::vector<size_t> chans)2453 void _connect_rx_chains(std::vector<size_t> chans)
2454 {
2455 for (auto chan : chans) {
2456 _connect_rx_chain(chan);
2457 }
2458 }
2459
_connect_tx_chain(size_t chan)2460 void _connect_tx_chain(size_t chan)
2461 {
2462 auto tx_chan = _tx_chans.at(chan);
2463 for (auto edge : tx_chan.edge_list) {
2464 if (block_id_t(edge.src_blockid).match(NODE_ID_SEP)) {
2465 break;
2466 }
2467 UHD_LOG_TRACE(
2468 "MULTI_USRP", std::string("Connecting TX edge: ") + edge.to_string());
2469 _graph->connect(
2470 edge.src_blockid, edge.src_port, edge.dst_blockid, edge.dst_port);
2471 }
2472 }
2473
_connect_tx_chains(std::vector<size_t> chans)2474 void _connect_tx_chains(std::vector<size_t> chans)
2475 {
2476 for (auto chan : chans) {
2477 _connect_tx_chain(chan);
2478 }
2479 }
2480
_disconnect_rx_chain(size_t chan)2481 void _disconnect_rx_chain(size_t chan)
2482 {
2483 auto rx_chan = _rx_chans.at(chan);
2484 for (auto edge : rx_chan.edge_list) {
2485 if (block_id_t(edge.dst_blockid).match(NODE_ID_SEP)) {
2486 break;
2487 }
2488 UHD_LOG_TRACE(
2489 "MULTI_USRP", std::string("Disconnecting RX edge: ") + edge.to_string());
2490 _graph->disconnect(
2491 edge.src_blockid, edge.src_port, edge.dst_blockid, edge.dst_port);
2492 }
2493 }
2494
_disconnect_rx_chains(std::vector<size_t> chans)2495 void _disconnect_rx_chains(std::vector<size_t> chans)
2496 {
2497 for (auto chan : chans) {
2498 _disconnect_rx_chain(chan);
2499 }
2500 }
2501
_disconnect_tx_chain(size_t chan)2502 void _disconnect_tx_chain(size_t chan)
2503 {
2504 auto tx_chan = _tx_chans.at(chan);
2505 for (auto edge : tx_chan.edge_list) {
2506 if (block_id_t(edge.src_blockid).match(NODE_ID_SEP)) {
2507 break;
2508 }
2509 UHD_LOG_TRACE(
2510 "MULTI_USRP", std::string("Disconnecting TX edge: ") + edge.to_string());
2511 _graph->disconnect(
2512 edge.src_blockid, edge.src_port, edge.dst_blockid, edge.dst_port);
2513 }
2514 }
2515
_disconnect_tx_chains(std::vector<size_t> chans)2516 void _disconnect_tx_chains(std::vector<size_t> chans)
2517 {
2518 for (auto chan : chans) {
2519 _disconnect_tx_chain(chan);
2520 }
2521 }
2522
2523 /**************************************************************************
2524 * Private Attributes
2525 *************************************************************************/
2526 //! Devices args used to spawn this multi_usrp
2527 const uhd::device_addr_t _args;
2528 //! Reference to rfnoc_graph
2529 rfnoc_graph::sptr _graph;
2530 //! Reference to the prop tree
2531 property_tree::sptr _tree;
2532 //! Mapping between channel number and the RFNoC blocks in that RX chain
2533 std::unordered_map<size_t, rx_chan_t> _rx_chans;
2534 //! Mapping between channel number and the RFNoC blocks in that TX chain
2535 std::unordered_map<size_t, tx_chan_t> _tx_chans;
2536 //! Cache the requested RX rates
2537 std::unordered_map<size_t, double> _rx_rates;
2538 //! Cache the requested TX rates
2539 std::unordered_map<size_t, double> _tx_rates;
2540
2541 std::recursive_mutex _graph_mutex;
2542
2543 std::shared_ptr<redirector_device> _device;
2544 };
2545
2546 /******************************************************************************
2547 * Factory
2548 *****************************************************************************/
2549 namespace uhd { namespace rfnoc { namespace detail {
2550 // Forward declare
2551 rfnoc_graph::sptr make_rfnoc_graph(
2552 detail::rfnoc_device::sptr dev, const uhd::device_addr_t& device_addr);
2553
make_rfnoc_device(detail::rfnoc_device::sptr rfnoc_device,const uhd::device_addr_t & dev_addr)2554 multi_usrp::sptr make_rfnoc_device(
2555 detail::rfnoc_device::sptr rfnoc_device, const uhd::device_addr_t& dev_addr)
2556 {
2557 auto graph = uhd::rfnoc::detail::make_rfnoc_graph(rfnoc_device, dev_addr);
2558 return std::make_shared<multi_usrp_rfnoc>(graph, dev_addr);
2559 }
2560
2561 }}} // namespace uhd::rfnoc::detail
2562