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/constants.hpp>
9 #include <uhd/rfnoc/defaults.hpp>
10 #include <uhd/rfnoc/mb_controller.hpp>
11 #include <uhd/rfnoc/noc_block_make_args.hpp>
12 #include <uhd/rfnoc/node.hpp>
13 #include <uhd/rfnoc_graph.hpp>
14 #include <uhdlib/rfnoc/block_container.hpp>
15 #include <uhdlib/rfnoc/factory.hpp>
16 #include <uhdlib/rfnoc/graph.hpp>
17 #include <uhdlib/rfnoc/graph_stream_manager.hpp>
18 #include <uhdlib/rfnoc/rfnoc_device.hpp>
19 #include <uhdlib/rfnoc/rfnoc_rx_streamer.hpp>
20 #include <uhdlib/rfnoc/rfnoc_tx_streamer.hpp>
21 #include <uhdlib/usrp/common/io_service_mgr.hpp>
22 #include <uhdlib/utils/narrow.hpp>
23 #include <memory>
24
25 using namespace uhd;
26 using namespace uhd::rfnoc;
27
28 namespace {
29 const std::string LOG_ID("RFNOC::GRAPH");
30
31 //! Which blocks are actually stored at a given port on the crossbar
32 struct block_xbar_info
33 {
34 size_t xbar_port;
35 noc_id_t noc_id;
36 size_t inst_num;
37 };
38
39 //! Information about a specific connection (used for streamer disconnect)
40 struct connection_info_t
41 {
42 detail::graph_t::node_ref_t src;
43 detail::graph_t::node_ref_t dst;
44 graph_edge_t edge;
45 };
46
47 //! Information about a streamer (used for streamer disconnect)
48 struct streamer_info_t
49 {
50 detail::graph_t::node_ref_t node;
51 std::map<size_t, connection_info_t> connections;
52 };
53
54 //! Information about a route (used for physical connect/disconnect)
55 struct route_info_t
56 {
57 graph_edge_t::edge_t edge_type;
58 graph_edge_t src_static_edge;
59 graph_edge_t dst_static_edge;
60 };
61 } // namespace
62
63 class rfnoc_graph_impl : public rfnoc_graph
64 {
65 public:
66 /**************************************************************************
67 * Structors
68 *************************************************************************/
rfnoc_graph_impl(detail::rfnoc_device::sptr dev,const uhd::device_addr_t & dev_addr)69 rfnoc_graph_impl(
70 detail::rfnoc_device::sptr dev, const uhd::device_addr_t& dev_addr) try
71 : _device(dev),
72 _tree(_device->get_tree()),
73 _num_mboards(_tree->list("/mboards").size()),
74 _block_registry(std::make_unique<detail::block_container_t>()),
75 _graph(std::make_unique<uhd::rfnoc::detail::graph_t>()) {
76 _mb_controllers.reserve(_num_mboards);
77 // Now initialize all subsystems:
78 _init_io_srv_mgr(dev_addr); // Global I/O Service Manager
79 _init_mb_controllers();
80 _init_gsm(); // Graph Stream Manager
81 try {
82 // If anything fails here, we immediately deinit all the other
83 // blocks to avoid any more fallout, then safely bring down the
84 // device.
85 for (size_t mb_idx = 0; mb_idx < _num_mboards; ++mb_idx) {
86 _init_blocks(mb_idx, dev_addr);
87 }
88 UHD_LOG_TRACE(LOG_ID, "Initializing properties on all blocks...");
89 _block_registry->init_props();
90 _init_sep_map();
91 _init_static_connections();
92 _init_mbc();
93 // Start with time set to zero, but don't complain if sync fails
94 rfnoc_graph_impl::synchronize_devices(uhd::time_spec_t(0.0), true);
95 } catch (...) {
96 _block_registry->shutdown();
97 throw;
98 }
99 } catch (const std::exception& ex) {
100 UHD_LOG_ERROR(LOG_ID, "Caught exception while initializing graph: " << ex.what());
101 throw uhd::runtime_error("Failure to create rfnoc_graph.");
102 } catch (...) {
103 UHD_LOG_ERROR(LOG_ID, "Caught unknown exception while initializing graph!");
104 throw uhd::runtime_error("Failure to create rfnoc_graph.");
105 }
106
~rfnoc_graph_impl()107 ~rfnoc_graph_impl()
108 {
109 UHD_LOG_TRACE(LOG_ID, "Shutting down detail::graph...");
110 _graph->shutdown();
111 UHD_LOG_TRACE(LOG_ID, "Shutting down all blocks ...");
112 _block_registry->shutdown();
113 _graph.reset();
114 }
115
116 /**************************************************************************
117 * Block Discovery/Retrieval
118 *************************************************************************/
find_blocks(const std::string & block_id_hint) const119 std::vector<block_id_t> find_blocks(const std::string& block_id_hint) const
120 {
121 return _block_registry->find_blocks(block_id_hint);
122 }
123
has_block(const block_id_t & block_id) const124 bool has_block(const block_id_t& block_id) const
125 {
126 return _block_registry->has_block(block_id);
127 }
128
get_block(const block_id_t & block_id) const129 noc_block_base::sptr get_block(const block_id_t& block_id) const
130 {
131 return _block_registry->get_block(block_id);
132 }
133
134 /**************************************************************************
135 * Graph Connections
136 *************************************************************************/
is_connectable(const block_id_t & src_blk,size_t src_port,const block_id_t & dst_blk,size_t dst_port)137 bool is_connectable(const block_id_t& src_blk,
138 size_t src_port,
139 const block_id_t& dst_blk,
140 size_t dst_port)
141 {
142 try {
143 const std::string src_blk_info =
144 src_blk.to_string() + ":" + std::to_string(src_port);
145 const std::string dst_blk_info =
146 dst_blk.to_string() + ":" + std::to_string(dst_port);
147
148 // Find the static edge for src_blk:src_port
149 auto src_static_edge_o = _get_static_edge(
150 [src_blk_id = src_blk.to_string(), src_port](const graph_edge_t& edge) {
151 return edge.src_blockid == src_blk_id && edge.src_port == src_port;
152 });
153 // If the edge doesn't exist, then it's not even connected in the
154 // FPGA.
155 if (!src_static_edge_o) {
156 return false;
157 }
158 graph_edge_t src_static_edge = src_static_edge_o.get();
159
160 // Now see if it's already connected to the destination
161 if (src_static_edge.dst_blockid == dst_blk.to_string()
162 && src_static_edge.dst_port == dst_port) {
163 return true;
164 }
165
166 // If they're not statically connected, the source *must* be connected
167 // to an SEP, or this route is impossible
168 if (block_id_t(src_static_edge.dst_blockid).get_block_name() != NODE_ID_SEP) {
169 return false;
170 }
171
172 // OK, now we know which source SEP we have
173 const std::string src_sep_info = src_static_edge.dst_blockid;
174 // const sep_addr_t src_sep_addr = _sep_map.at(src_sep_info);
175
176 // Now find the static edge for the destination SEP
177 auto dst_static_edge_o = _get_static_edge(
178 [dst_blk_id = dst_blk.to_string(), dst_port](const graph_edge_t& edge) {
179 return edge.dst_blockid == dst_blk_id && edge.dst_port == dst_port;
180 });
181 // If the edge doesn't exist, then it's not even connected in the
182 // FPGA.
183 if (!dst_static_edge_o) {
184 return false;
185 }
186 graph_edge_t dst_static_edge = dst_static_edge_o.get();
187
188 // If they're not statically connected, the source *must* be connected
189 // to an SEP, or this route is impossible
190 if (block_id_t(dst_static_edge.src_blockid).get_block_name() != NODE_ID_SEP) {
191 return false;
192 }
193
194 // OK, now we know which destination SEP we have
195 const std::string dst_sep_info = dst_static_edge.src_blockid;
196 // const sep_addr_t dst_sep_addr = _sep_map.at(dst_sep_info);
197
198 UHD_LOG_WARNING(LOG_ID,
199 "is_connectable() currently assuming that SEPs"
200 << dst_sep_info << " and " << src_sep_info
201 << " are connectable. "
202 "Please implement a better check.");
203 } catch (...) {
204 return false;
205 }
206 return true;
207 }
208
209
connect(const block_id_t & src_blk,size_t src_port,const block_id_t & dst_blk,size_t dst_port,bool skip_property_propagation)210 void connect(const block_id_t& src_blk,
211 size_t src_port,
212 const block_id_t& dst_blk,
213 size_t dst_port,
214 bool skip_property_propagation)
215 {
216 if (!has_block(src_blk)) {
217 throw uhd::lookup_error(
218 std::string("Cannot connect blocks, source block not found: ")
219 + src_blk.to_string());
220 }
221 if (!has_block(dst_blk)) {
222 throw uhd::lookup_error(
223 std::string("Cannot connect blocks, destination block not found: ")
224 + dst_blk.to_string());
225 }
226 auto edge_type = _physical_connect(src_blk, src_port, dst_blk, dst_port);
227 _connect(get_block(src_blk),
228 src_port,
229 get_block(dst_blk),
230 dst_port,
231 edge_type,
232 skip_property_propagation);
233 }
234
disconnect(const block_id_t & src_blk,size_t src_port,const block_id_t & dst_blk,size_t dst_port)235 void disconnect(const block_id_t& src_blk,
236 size_t src_port,
237 const block_id_t& dst_blk,
238 size_t dst_port)
239 {
240 if (not has_block(src_blk)) {
241 throw uhd::lookup_error(
242 std::string("Cannot disconnect blocks, source block not found: ")
243 + src_blk.to_string());
244 }
245 if (not has_block(dst_blk)) {
246 throw uhd::lookup_error(
247 std::string("Cannot disconnect blocks, destination block not found: ")
248 + dst_blk.to_string());
249 }
250 auto edge_type = _physical_disconnect(src_blk, src_port, dst_blk, dst_port);
251 graph_edge_t edge_info(src_port, dst_port, edge_type, true);
252 auto src = get_block(src_blk);
253 auto dst = get_block(dst_blk);
254 edge_info.src_blockid = src->get_unique_id();
255 edge_info.dst_blockid = dst->get_unique_id();
256 _graph->disconnect(src.get(), dst.get(), edge_info);
257 }
258
connect(uhd::tx_streamer::sptr streamer,size_t strm_port,const block_id_t & dst_blk,size_t dst_port,uhd::transport::adapter_id_t adapter_id)259 void connect(uhd::tx_streamer::sptr streamer,
260 size_t strm_port,
261 const block_id_t& dst_blk,
262 size_t dst_port,
263 uhd::transport::adapter_id_t adapter_id)
264 {
265 // Verify the streamer was created by us
266 auto rfnoc_streamer = std::dynamic_pointer_cast<rfnoc_tx_streamer>(streamer);
267 if (!rfnoc_streamer) {
268 throw uhd::type_error("Streamer is not rfnoc capable");
269 }
270
271 // Verify src_blk even exists in this graph
272 if (!has_block(dst_blk)) {
273 throw uhd::lookup_error(
274 std::string("Cannot connect block to streamer, source block not found: ")
275 + dst_blk.to_string());
276 }
277
278 // Verify src_blk has an SEP upstream
279 graph_edge_t dst_static_edge = _assert_edge(
280 _get_static_edge(
281 [dst_blk_id = dst_blk.to_string(), dst_port](const graph_edge_t& edge) {
282 return edge.dst_blockid == dst_blk_id && edge.dst_port == dst_port;
283 }),
284 dst_blk.to_string());
285 if (block_id_t(dst_static_edge.src_blockid).get_block_name() != NODE_ID_SEP) {
286 const std::string err_msg =
287 dst_blk.to_string() + ":" + std::to_string(dst_port)
288 + " is not connected to an SEP! Routing impossible.";
289 UHD_LOG_ERROR(LOG_ID, err_msg);
290 throw uhd::routing_error(err_msg);
291 }
292
293 // Now get the name and address of the SEP
294 const std::string sep_block_id = dst_static_edge.src_blockid;
295 const sep_addr_t sep_addr = _sep_map.at(sep_block_id);
296
297 const sw_buff_t pyld_fmt =
298 bits_to_sw_buff(rfnoc_streamer->get_otw_item_comp_bit_width());
299 const sw_buff_t mdata_fmt = BUFF_U64;
300
301 auto xport = _gsm->create_host_to_device_data_stream(sep_addr,
302 pyld_fmt,
303 mdata_fmt,
304 adapter_id,
305 rfnoc_streamer->get_stream_args().args,
306 rfnoc_streamer->get_unique_id());
307
308 rfnoc_streamer->connect_channel(strm_port, std::move(xport));
309
310 // If this worked, then also connect the streamer in the BGL graph
311 auto dst = get_block(dst_blk);
312 graph_edge_t edge_info(strm_port, dst_port, graph_edge_t::TX_STREAM, true);
313 _graph->connect(rfnoc_streamer.get(), dst.get(), edge_info);
314
315 _tx_streamers[rfnoc_streamer->get_unique_id()].node = rfnoc_streamer.get();
316 _tx_streamers[rfnoc_streamer->get_unique_id()].connections[strm_port] = {
317 rfnoc_streamer.get(), dst.get(), edge_info};
318 }
319
connect(const block_id_t & src_blk,size_t src_port,uhd::rx_streamer::sptr streamer,size_t strm_port,uhd::transport::adapter_id_t adapter_id)320 void connect(const block_id_t& src_blk,
321 size_t src_port,
322 uhd::rx_streamer::sptr streamer,
323 size_t strm_port,
324 uhd::transport::adapter_id_t adapter_id)
325 {
326 // Verify the streamer was created by us
327 auto rfnoc_streamer = std::dynamic_pointer_cast<rfnoc_rx_streamer>(streamer);
328 if (!rfnoc_streamer) {
329 throw uhd::type_error("Streamer is not rfnoc capable");
330 }
331
332 // Verify src_blk even exists in this graph
333 if (!has_block(src_blk)) {
334 throw uhd::lookup_error(
335 std::string("Cannot connect block to streamer, source block not found: ")
336 + src_blk.to_string());
337 }
338
339 // Verify src_blk has an SEP downstream
340 graph_edge_t src_static_edge = _assert_edge(
341 _get_static_edge(
342 [src_blk_id = src_blk.to_string(), src_port](const graph_edge_t& edge) {
343 return edge.src_blockid == src_blk_id && edge.src_port == src_port;
344 }),
345 src_blk.to_string());
346 if (block_id_t(src_static_edge.dst_blockid).get_block_name() != NODE_ID_SEP) {
347 const std::string err_msg =
348 src_blk.to_string() + ":" + std::to_string(src_port)
349 + " is not connected to an SEP! Routing impossible.";
350 UHD_LOG_ERROR(LOG_ID, err_msg);
351 throw uhd::routing_error(err_msg);
352 }
353
354 // Now get the name and address of the SEP
355 const std::string sep_block_id = src_static_edge.dst_blockid;
356 const sep_addr_t sep_addr = _sep_map.at(sep_block_id);
357
358 const sw_buff_t pyld_fmt =
359 bits_to_sw_buff(rfnoc_streamer->get_otw_item_comp_bit_width());
360 const sw_buff_t mdata_fmt = BUFF_U64;
361
362 auto xport = _gsm->create_device_to_host_data_stream(sep_addr,
363 pyld_fmt,
364 mdata_fmt,
365 adapter_id,
366 rfnoc_streamer->get_stream_args().args,
367 rfnoc_streamer->get_unique_id());
368
369 rfnoc_streamer->connect_channel(strm_port, std::move(xport));
370
371 // If this worked, then also connect the streamer in the BGL graph
372 auto src = get_block(src_blk);
373 graph_edge_t edge_info(src_port, strm_port, graph_edge_t::RX_STREAM, true);
374 _graph->connect(src.get(), rfnoc_streamer.get(), edge_info);
375
376 _rx_streamers[rfnoc_streamer->get_unique_id()].node = rfnoc_streamer.get();
377 _rx_streamers[rfnoc_streamer->get_unique_id()].connections[strm_port] = {
378 src.get(), rfnoc_streamer.get(), edge_info};
379 }
380
disconnect(const std::string & streamer_id)381 void disconnect(const std::string& streamer_id)
382 {
383 UHD_LOG_TRACE(LOG_ID, std::string("Disconnecting ") + streamer_id);
384 if (_tx_streamers.count(streamer_id)) {
385 // TODO: Physically disconnect all connections
386 // This may not be strictly necessary because the destruction of
387 // the xport will prevent packets from being sent to the
388 // destination.
389
390 // Remove the node from the graph
391 _graph->remove(_tx_streamers[streamer_id].node);
392
393 // Remove the streamer from the map
394 _tx_streamers.erase(streamer_id);
395 } else if (_rx_streamers.count(streamer_id)) {
396 // TODO: Physically disconnect all connections
397
398 // Remove the node from the graph (logically disconnect)
399 _graph->remove(_rx_streamers[streamer_id].node);
400
401 // Remove the streamer from the map
402 _rx_streamers.erase(streamer_id);
403 }
404 UHD_LOG_TRACE(LOG_ID, std::string("Disconnected ") + streamer_id);
405 }
406
disconnect(const std::string & streamer_id,size_t port)407 void disconnect(const std::string& streamer_id, size_t port)
408 {
409 std::string id_str = streamer_id + ":" + std::to_string(port);
410 UHD_LOG_TRACE(LOG_ID, std::string("Disconnecting ") + id_str);
411 if (_tx_streamers.count(streamer_id)) {
412 if (_tx_streamers[streamer_id].connections.count(port)) {
413 auto connection = _tx_streamers[streamer_id].connections[port];
414 _graph->disconnect(connection.src, connection.dst, connection.edge);
415 _tx_streamers[streamer_id].connections.erase(port);
416 } else {
417 throw uhd::lookup_error(
418 std::string("Cannot disconnect. Port not connected: ") + id_str);
419 }
420 } else if (_rx_streamers.count(streamer_id)) {
421 if (_rx_streamers[streamer_id].connections.count(port)) {
422 auto connection = _rx_streamers[streamer_id].connections[port];
423 // TODO: Physically disconnect port
424 _graph->disconnect(connection.src, connection.dst, connection.edge);
425 _rx_streamers[streamer_id].connections.erase(port);
426 } else {
427 throw uhd::lookup_error(
428 std::string("Cannot disconnect. Port not connected: ") + id_str);
429 }
430 }
431 UHD_LOG_TRACE(LOG_ID, std::string("Disconnected ") + id_str);
432 }
433
create_rx_streamer(const size_t num_ports,const uhd::stream_args_t & args)434 uhd::rx_streamer::sptr create_rx_streamer(
435 const size_t num_ports, const uhd::stream_args_t& args)
436 {
437 auto this_graph = shared_from_this();
438 return std::make_shared<rfnoc_rx_streamer>(
439 num_ports, args, [this_graph](const std::string& id) { this_graph->disconnect(id); });
440 }
441
create_tx_streamer(const size_t num_ports,const uhd::stream_args_t & args)442 uhd::tx_streamer::sptr create_tx_streamer(
443 const size_t num_ports, const uhd::stream_args_t& args)
444 {
445 auto this_graph = shared_from_this();
446 return std::make_shared<rfnoc_tx_streamer>(
447 num_ports, args, [this_graph](const std::string& id) { this_graph->disconnect(id); });
448 }
449
get_num_mboards() const450 size_t get_num_mboards() const
451 {
452 return _num_mboards;
453 }
454
get_mb_controller(const size_t mb_index=0)455 std::shared_ptr<mb_controller> get_mb_controller(const size_t mb_index = 0)
456 {
457 if (_mb_controllers.size() <= mb_index) {
458 throw uhd::index_error(
459 std::string("Could not get mb controller for motherboard index ")
460 + std::to_string(mb_index));
461 }
462 return _mb_controllers.at(mb_index);
463 }
464
synchronize_devices(const uhd::time_spec_t & time_spec,const bool quiet)465 bool synchronize_devices(const uhd::time_spec_t& time_spec, const bool quiet)
466 {
467 auto mb_controllers_copy = _mb_controllers;
468 bool result =
469 _mb_controllers.at(0)->synchronize(mb_controllers_copy, time_spec, quiet);
470 if (mb_controllers_copy.size() != _mb_controllers.size()) {
471 // This shouldn't happen until we allow different device types in a
472 // rfnoc_graph
473 UHD_LOG_ERROR(LOG_ID, "Some devices wouldn't be sync'd!");
474 return false;
475 }
476 return result;
477 }
478
get_tree(void) const479 uhd::property_tree::sptr get_tree(void) const
480 {
481 return _tree;
482 }
483
enumerate_adapters_to_dst(const block_id_t & dst_blk,size_t dst_port)484 std::vector<uhd::transport::adapter_id_t> enumerate_adapters_to_dst(
485 const block_id_t& dst_blk, size_t dst_port)
486 {
487 // Verify dst_blk even exists in this graph
488 if (!has_block(dst_blk)) {
489 throw uhd::lookup_error(
490 std::string("Cannot connect block to streamer, source block not found: ")
491 + dst_blk.to_string());
492 }
493
494 // Verify dst_blk has an SEP upstream
495 graph_edge_t dst_static_edge = _assert_edge(
496 _get_static_edge(
497 [dst_blk_id = dst_blk.to_string(), dst_port](const graph_edge_t& edge) {
498 return edge.dst_blockid == dst_blk_id && edge.dst_port == dst_port;
499 }),
500 dst_blk.to_string());
501 if (block_id_t(dst_static_edge.src_blockid).get_block_name() != NODE_ID_SEP) {
502 const std::string err_msg =
503 dst_blk.to_string() + ":" + std::to_string(dst_port)
504 + " is not connected to an SEP! Routing impossible.";
505 UHD_LOG_ERROR(LOG_ID, err_msg);
506 throw uhd::routing_error(err_msg);
507 }
508
509 // Now get the name and address of the SEP
510 const std::string sep_block_id = dst_static_edge.src_blockid;
511 const sep_addr_t sep_addr = _sep_map.at(sep_block_id);
512
513 // Find links that can reach the SEP
514 return _gsm->get_adapters(sep_addr);
515 }
516
enumerate_adapters_from_src(const block_id_t & src_blk,size_t src_port)517 std::vector<uhd::transport::adapter_id_t> enumerate_adapters_from_src(
518 const block_id_t& src_blk, size_t src_port)
519 {
520 // Verify src_blk even exists in this graph
521 if (!has_block(src_blk)) {
522 throw uhd::lookup_error(
523 std::string("Cannot connect block to streamer, source block not found: ")
524 + src_blk.to_string());
525 }
526
527 // Verify src_blk has an SEP downstream
528 graph_edge_t src_static_edge = _assert_edge(
529 _get_static_edge(
530 [src_blk_id = src_blk.to_string(), src_port](const graph_edge_t& edge) {
531 return edge.src_blockid == src_blk_id && edge.src_port == src_port;
532 }),
533 src_blk.to_string());
534 if (block_id_t(src_static_edge.dst_blockid).get_block_name() != NODE_ID_SEP) {
535 const std::string err_msg =
536 src_blk.to_string() + ":" + std::to_string(src_port)
537 + " is not connected to an SEP! Routing impossible.";
538 UHD_LOG_ERROR(LOG_ID, err_msg);
539 throw uhd::routing_error(err_msg);
540 }
541
542 // Now get the name and address of the SEP
543 const std::string sep_block_id = src_static_edge.dst_blockid;
544 const sep_addr_t sep_addr = _sep_map.at(sep_block_id);
545
546 // Find links that can reach the SEP
547 return _gsm->get_adapters(sep_addr);
548 }
549
enumerate_active_connections()550 std::vector<graph_edge_t> enumerate_active_connections()
551
552 {
553 return _graph->enumerate_edges();
554 }
555
enumerate_static_connections() const556 std::vector<graph_edge_t> enumerate_static_connections() const
557 {
558 return _static_edges;
559 }
560
commit()561 void commit()
562 {
563 _graph->commit();
564 }
565
release()566 void release()
567 {
568 _graph->release();
569 }
570
571 private:
572 /**************************************************************************
573 * Device Setup
574 *************************************************************************/
_init_io_srv_mgr(const uhd::device_addr_t & dev_addr)575 void _init_io_srv_mgr(const uhd::device_addr_t& dev_addr)
576 {
577 _io_srv_mgr = usrp::io_service_mgr::make(dev_addr);
578 for (size_t mb_idx = 0; mb_idx < _num_mboards; mb_idx++) {
579 _device->get_mb_iface(mb_idx).set_io_srv_mgr(_io_srv_mgr);
580 }
581 }
582
_init_mb_controllers()583 void _init_mb_controllers()
584 {
585 UHD_LOG_TRACE(LOG_ID, "Initializing MB controllers...");
586 for (size_t i = 0; i < _num_mboards; ++i) {
587 _mb_controllers.push_back(_device->get_mb_controller(i));
588 }
589 }
590
_init_gsm()591 void _init_gsm()
592 {
593 UHD_LOG_TRACE(LOG_ID, "Initializing GSM...");
594 auto e2s = [](uhd::endianness_t endianness) {
595 return endianness == uhd::ENDIANNESS_BIG ? "BIG" : "LITTLE";
596 };
597 const chdr_w_t chdr_w = _device->get_mb_iface(0).get_chdr_w();
598 const uhd::endianness_t endianness = _device->get_mb_iface(0).get_endianness();
599 for (size_t mb_idx = 1; mb_idx < _num_mboards; mb_idx++) {
600 if (_device->get_mb_iface(mb_idx).get_chdr_w() != chdr_w) {
601 throw uhd::runtime_error(
602 std::string("Non-homogenous devices: Graph CHDR width is ")
603 + std::to_string(chdr_w_to_bits(chdr_w)) + " but device "
604 + std::to_string(mb_idx) + " has CHDR width of "
605 + std::to_string(
606 chdr_w_to_bits(_device->get_mb_iface(mb_idx).get_chdr_w()))
607 + " bits!");
608 }
609 if (_device->get_mb_iface(mb_idx).get_endianness() != endianness) {
610 throw uhd::runtime_error(
611 std::string("Non-homogenous devices: Graph endianness is ")
612 + e2s(endianness) + " but device " + std::to_string(mb_idx)
613 + " has endianness " + e2s(endianness) + "!");
614 }
615 }
616 UHD_LOG_TRACE(LOG_ID,
617 "Creating packet factory with CHDR width "
618 << chdr_w_to_bits(chdr_w) << " bits and endianness " << e2s(endianness));
619 _pkt_factory = std::make_unique<chdr::chdr_packet_factory>(chdr_w, endianness);
620 // Create a collection of link definitions: (ID, MB) pairs
621 std::vector<std::pair<device_id_t, mb_iface*>> links;
622 for (size_t mb_idx = 0; mb_idx < _num_mboards; mb_idx++) {
623 const auto device_ids = _device->get_mb_iface(mb_idx).get_local_device_ids();
624 for (const device_id_t local_device_id : device_ids) {
625 if (_device->get_mb_iface(mb_idx).get_endianness(local_device_id)
626 != endianness) {
627 throw uhd::runtime_error(
628 std::string("Non-homogenous devices: Graph endianness is ")
629 + e2s(endianness) + " but device " + std::to_string(mb_idx)
630 + " has endianness " + e2s(endianness) + "!");
631 }
632 links.push_back(
633 std::make_pair(local_device_id, &_device->get_mb_iface(mb_idx)));
634 }
635 }
636 if (links.empty()) {
637 UHD_LOG_ERROR(
638 LOG_ID, "No links found for " << _num_mboards << " motherboards!");
639 throw uhd::runtime_error("[rfnoc_graph] No links found!");
640 }
641 UHD_LOG_TRACE(LOG_ID, "Found a total of " << links.size() << " links.");
642 try {
643 _gsm = graph_stream_manager::make(*_pkt_factory, _epid_alloc, links);
644 } catch (uhd::io_error& ex) {
645 UHD_LOG_ERROR(LOG_ID, "IO Error during GSM initialization. " << ex.what());
646 throw;
647 }
648
649 // Configure endpoint_manager, make sure all routes are established
650 // FIXME
651 }
652
653 // Initialize client zero and all block controllers for motherboard mb_idx
_init_blocks(const size_t mb_idx,const uhd::device_addr_t & dev_addr)654 void _init_blocks(const size_t mb_idx, const uhd::device_addr_t& dev_addr)
655 {
656 UHD_LOG_TRACE(LOG_ID, "Initializing blocks for MB " << mb_idx << "...");
657 // Setup the interfaces for this mboard and get some configuration info
658 mb_iface& mb = _device->get_mb_iface(mb_idx);
659 // Ask GSM to allow us to talk to our remote mb
660 sep_addr_t ctrl_sep_addr(mb.get_remote_device_id(), 0);
661 _gsm->connect_host_to_device(ctrl_sep_addr);
662 // Grab and stash the Client Zero for this mboard
663 detail::client_zero::sptr mb_cz = _gsm->get_client_zero(ctrl_sep_addr);
664 // Client zero port numbers are based on the control xbar numbers,
665 // which have the client 0 interface first, followed by stream
666 // endpoints, and then the blocks.
667 _client_zeros.emplace(mb_idx, mb_cz);
668
669 const size_t num_blocks = mb_cz->get_num_blocks();
670 const size_t first_block_port = 1 + mb_cz->get_num_stream_endpoints();
671
672 /* Flush and reset each block in the mboard
673 * We do this before we enumerate the blocks to ensure they're in a clean
674 * state before we construct their block controller, and so that we don't
675 * reset any setting that the block controller writes
676 */
677 UHD_LOG_TRACE(LOG_ID,
678 std::string("Flushing and resetting blocks on mboard ")
679 + std::to_string(mb_idx));
680 _flush_and_reset_mboard(mb_cz, num_blocks, first_block_port);
681
682 // Make a map to count the number of each block we have
683 std::unordered_map<std::string, uint16_t> block_count_map;
684
685 // Iterate through and register each of the blocks in this mboard
686 for (size_t portno = 0; portno < num_blocks; ++portno) {
687 const auto noc_id = mb_cz->get_noc_id(portno + first_block_port);
688 const auto device_type = mb_cz->get_device_type();
689 auto block_factory_info = factory::get_block_factory(noc_id, device_type);
690 auto block_info = mb_cz->get_block_info(portno + first_block_port);
691 block_id_t block_id(mb_idx,
692 block_factory_info.block_name,
693 block_count_map[block_factory_info.block_name]++);
694 // Get access to the clock interface objects. We have some rules
695 // here:
696 // - The ctrlport clock must always be provided through the
697 // BSP via mb_iface
698 // - The timebase clock can be set to CLOCK_KEY_GRAPH, which means
699 // the block takes care of the timebase itself (via property
700 // propagation). In that case, we generate a clock iface
701 // object on the fly here.
702 // - In all other cases, the BSP must provide us that clock
703 // iface object through the mb_iface
704 auto ctrlport_clk_iface = mb.get_clock_iface(block_factory_info.ctrlport_clk);
705 auto tb_clk_iface = (block_factory_info.timebase_clk == CLOCK_KEY_GRAPH)
706 ? std::make_shared<clock_iface>(CLOCK_KEY_GRAPH)
707 : mb.get_clock_iface(block_factory_info.timebase_clk);
708 // A "graph" clock is always "running"
709 if (block_factory_info.timebase_clk == CLOCK_KEY_GRAPH) {
710 tb_clk_iface->set_running(true);
711 }
712 auto block_reg_iface = _gsm->get_block_register_iface(
713 ctrl_sep_addr, portno, *ctrlport_clk_iface.get(), *tb_clk_iface.get());
714 auto make_args_uptr = std::make_unique<noc_block_base::make_args_t>();
715 make_args_uptr->noc_id = noc_id;
716 make_args_uptr->block_id = block_id;
717 make_args_uptr->num_input_ports = block_info.num_inputs;
718 make_args_uptr->num_output_ports = block_info.num_outputs;
719 make_args_uptr->mtu =
720 (1 << block_info.data_mtu) * chdr_w_to_bits(mb.get_chdr_w()) / 8;
721 make_args_uptr->reg_iface = block_reg_iface;
722 make_args_uptr->tb_clk_iface = tb_clk_iface;
723 make_args_uptr->ctrlport_clk_iface = ctrlport_clk_iface;
724 make_args_uptr->mb_control =
725 block_factory_info.mb_access ? _mb_controllers.at(mb_idx) : nullptr;
726 const uhd::fs_path block_path(uhd::fs_path("/blocks") / block_id.to_string());
727 _tree->create<uint32_t>(block_path / "noc_id").set(noc_id);
728 make_args_uptr->tree = _tree->subtree(block_path);
729 make_args_uptr->args = dev_addr; // TODO filter the device args
730 try {
731 _block_registry->register_block(
732 block_factory_info.factory_fn(std::move(make_args_uptr)));
733 } catch (...) {
734 UHD_LOG_ERROR(
735 LOG_ID, "Error during initialization of block " << block_id << "!");
736 throw;
737 }
738 _xbar_block_config[block_id.to_string()] = {
739 portno, noc_id, block_id.get_block_count()};
740
741 _port_block_map.insert({{mb_idx, portno + first_block_port}, block_id});
742 }
743 }
744
_init_sep_map()745 void _init_sep_map()
746 {
747 for (size_t mb_idx = 0; mb_idx < _num_mboards; ++mb_idx) {
748 auto remote_device_id = _device->get_mb_iface(mb_idx).get_remote_device_id();
749 auto& cz = _client_zeros.at(mb_idx);
750 for (size_t sep_idx = 0; sep_idx < cz->get_num_stream_endpoints();
751 ++sep_idx) {
752 // Register ID in _port_block_map
753 block_id_t id(mb_idx, NODE_ID_SEP, sep_idx);
754 _port_block_map.insert({{mb_idx, sep_idx + 1}, id});
755 _sep_map.insert({id.to_string(), sep_addr_t(remote_device_id, sep_idx)});
756 }
757 }
758 }
759
_init_static_connections()760 void _init_static_connections()
761 {
762 UHD_LOG_TRACE(LOG_ID, "Identifying static connections...");
763 for (auto& kv_cz : _client_zeros) {
764 auto& adjacency_list = kv_cz.second->get_adjacency_list();
765 for (auto& edge : adjacency_list) {
766 // Assemble edge
767 auto graph_edge = graph_edge_t();
768 UHD_ASSERT_THROW(
769 _port_block_map.count({kv_cz.first, edge.src_blk_index}));
770 graph_edge.src_blockid =
771 _port_block_map.at({kv_cz.first, edge.src_blk_index});
772 UHD_ASSERT_THROW(
773 _port_block_map.count({kv_cz.first, edge.dst_blk_index}));
774 graph_edge.dst_blockid =
775 _port_block_map.at({kv_cz.first, edge.dst_blk_index});
776 graph_edge.src_port = edge.src_blk_port;
777 graph_edge.dst_port = edge.dst_blk_port;
778 graph_edge.edge = graph_edge_t::edge_t::STATIC;
779 _static_edges.push_back(graph_edge);
780 UHD_LOG_TRACE(LOG_ID, "Static connection: " << graph_edge.to_string());
781 }
782 }
783 }
784
785 //! Initialize the motherboard controllers, if they require it
_init_mbc()786 void _init_mbc()
787 {
788 for (size_t i = 0; i < _mb_controllers.size(); ++i) {
789 UHD_LOG_TRACE(LOG_ID, "Calling MBC init for motherboard " << i);
790 _mb_controllers.at(i)->init();
791 }
792 }
793
794 /**************************************************************************
795 * Helpers
796 *************************************************************************/
797 /*! Internal connection helper
798 *
799 * Make the connections in the _graph, and set up property propagation
800 * Prerequisite: \p src_blk and \p dst_blk need to point to valid nodes
801 */
_connect(std::shared_ptr<node_t> src_blk,size_t src_port,std::shared_ptr<node_t> dst_blk,size_t dst_port,graph_edge_t::edge_t edge_type,bool skip_property_propagation)802 void _connect(std::shared_ptr<node_t> src_blk,
803 size_t src_port,
804 std::shared_ptr<node_t> dst_blk,
805 size_t dst_port,
806 graph_edge_t::edge_t edge_type,
807 bool skip_property_propagation)
808 {
809 graph_edge_t edge_info(
810 src_port, dst_port, edge_type, not skip_property_propagation);
811 edge_info.src_blockid = src_blk->get_unique_id();
812 edge_info.dst_blockid = dst_blk->get_unique_id();
813 _graph->connect(src_blk.get(), dst_blk.get(), edge_info);
814 }
815
816 /*! Internal helper to get route information
817 *
818 * Checks the validity of the route and returns route information.
819 *
820 * \throws uhd::routing_error
821 * if the routing is impossible
822 */
_get_route_info(const block_id_t & src_blk,size_t src_port,const block_id_t & dst_blk,size_t dst_port)823 route_info_t _get_route_info(const block_id_t& src_blk,
824 size_t src_port,
825 const block_id_t& dst_blk,
826 size_t dst_port)
827 {
828 graph_edge_t::edge_t edge_type = graph_edge_t::DYNAMIC;
829
830 const std::string src_blk_info =
831 src_blk.to_string() + ":" + std::to_string(src_port);
832 const std::string dst_blk_info =
833 dst_blk.to_string() + ":" + std::to_string(dst_port);
834
835 // Find the static edge for src_blk:src_port
836 auto src_static_edge = _assert_edge(
837 _get_static_edge(
838 [src_blk_id = src_blk.to_string(), src_port](const graph_edge_t& edge) {
839 return edge.src_blockid == src_blk_id && edge.src_port == src_port;
840 }),
841 src_blk_info);
842
843 // Now find the static edge for the destination SEP
844 auto dst_static_edge = _assert_edge(
845 _get_static_edge(
846 [dst_blk_id = dst_blk.to_string(), dst_port](const graph_edge_t& edge) {
847 return edge.dst_blockid == dst_blk_id && edge.dst_port == dst_port;
848 }),
849 dst_blk_info);
850
851 // Now see if it's already connected to the destination
852 if (src_static_edge.dst_blockid == dst_blk.to_string()
853 && src_static_edge.dst_port == dst_port) {
854 // Blocks are statically connected
855 UHD_LOG_TRACE(LOG_ID,
856 "Blocks " << src_blk_info << " and " << dst_blk_info
857 << " are statically connected");
858 edge_type = graph_edge_t::STATIC;
859 } else if (block_id_t(src_static_edge.dst_blockid).get_block_name()
860 != NODE_ID_SEP) {
861 // Blocks are not statically connected and the source is not
862 // connected to an SEP
863 const std::string err_msg =
864 src_blk_info + " is neither statically connected to " + dst_blk_info
865 + " nor to an SEP! Routing impossible.";
866 UHD_LOG_ERROR(LOG_ID, err_msg);
867 throw uhd::routing_error(err_msg);
868 } else if (block_id_t(dst_static_edge.src_blockid).get_block_name()
869 != NODE_ID_SEP) {
870 // Blocks are not statically connected and the destination is not
871 // connected to an SEP
872 const std::string err_msg =
873 dst_blk_info + " is neither statically connected to " + src_blk_info
874 + " nor to an SEP! Routing impossible.";
875 UHD_LOG_ERROR(LOG_ID, err_msg);
876 throw uhd::routing_error(err_msg);
877 }
878
879 return {edge_type, src_static_edge, dst_static_edge};
880 }
881
882 /*! Internal physical connection helper
883 *
884 * Make the connections in the physical device
885 *
886 * \throws uhd::routing_error
887 * if the routing is impossible
888 */
_physical_connect(const block_id_t & src_blk,size_t src_port,const block_id_t & dst_blk,size_t dst_port)889 graph_edge_t::edge_t _physical_connect(const block_id_t& src_blk,
890 size_t src_port,
891 const block_id_t& dst_blk,
892 size_t dst_port)
893 {
894 auto route_info = _get_route_info(src_blk, src_port, dst_blk, dst_port);
895
896 if (route_info.edge_type == graph_edge_t::DYNAMIC) {
897 const std::string src_sep_info = route_info.src_static_edge.dst_blockid;
898 const sep_addr_t src_sep_addr = _sep_map.at(src_sep_info);
899 const std::string dst_sep_info = route_info.dst_static_edge.src_blockid;
900 const sep_addr_t dst_sep_addr = _sep_map.at(dst_sep_info);
901
902 auto strm_info = _gsm->create_device_to_device_data_stream(
903 dst_sep_addr, src_sep_addr, false, 0.1, 0.0, false);
904
905 UHD_LOGGER_DEBUG(LOG_ID)
906 << boost::format(
907 "Data stream between EPID %d and EPID %d established "
908 "where downstream buffer can hold %lu bytes and %u packets")
909 % std::get<0>(strm_info).first % std::get<0>(strm_info).second
910 % std::get<1>(strm_info).bytes % std::get<1>(strm_info).packets;
911 }
912
913 return route_info.edge_type;
914 }
915
916 /*! Internal physical disconnection helper
917 *
918 * Disconnects the physical device
919 *
920 * \throws uhd::routing_error
921 * if the routing is impossible
922 */
_physical_disconnect(const block_id_t & src_blk,size_t src_port,const block_id_t & dst_blk,size_t dst_port)923 graph_edge_t::edge_t _physical_disconnect(const block_id_t& src_blk,
924 size_t src_port,
925 const block_id_t& dst_blk,
926 size_t dst_port)
927 {
928 auto route_info = _get_route_info(src_blk, src_port, dst_blk, dst_port);
929
930 if (route_info.edge_type == graph_edge_t::DYNAMIC) {
931 // TODO: Add call into _gsm to physically disconnect the SEPs
932 }
933
934 return route_info.edge_type;
935 }
936
937 //! Flush and reset each connected port on the mboard
_flush_and_reset_mboard(detail::client_zero::sptr mb_cz,const size_t num_blocks,const size_t first_block_port)938 void _flush_and_reset_mboard(detail::client_zero::sptr mb_cz,
939 const size_t num_blocks,
940 const size_t first_block_port)
941 {
942 if (!mb_cz->complete_flush_all_blocks()) {
943 UHD_LOG_WARNING(LOG_ID, "One or more blocks timed out during flush!");
944 }
945
946 // Reset
947 for (size_t portno = 0; portno < num_blocks; ++portno) {
948 auto block_portno = portno + first_block_port;
949 mb_cz->reset_chdr(block_portno);
950 mb_cz->reset_ctrl(block_portno);
951 }
952 }
953
954 /*! Find the static edge that matches \p pred
955 *
956 * \throws uhd::assertion_error if the edge can't be found. So be careful!
957 */
958 template <typename UnaryPredicate>
_get_static_edge(UnaryPredicate && pred)959 boost::optional<graph_edge_t> _get_static_edge(UnaryPredicate&& pred)
960 {
961 auto edge_it = std::find_if(_static_edges.cbegin(), _static_edges.cend(), pred);
962 if (edge_it == _static_edges.cend()) {
963 return boost::none;
964 }
965 return *edge_it;
966 }
967
968 /*! Make sure an optional edge info is valid, or throw.
969 */
_assert_edge(boost::optional<graph_edge_t> edge_o,const std::string & blk_info)970 graph_edge_t _assert_edge(
971 boost::optional<graph_edge_t> edge_o, const std::string& blk_info)
972 {
973 if (!bool(edge_o)) {
974 const std::string err_msg = std::string("Cannot connect block ") + blk_info
975 + ", port is unconnected in the FPGA!";
976 UHD_LOG_ERROR(LOG_ID, err_msg);
977 throw uhd::routing_error(err_msg);
978 }
979 return edge_o.get();
980 }
981
982 /**************************************************************************
983 * Attributes
984 *************************************************************************/
985 //! Reference to the underlying device implementation
986 detail::rfnoc_device::sptr _device;
987
988 //! Reference to the property tree
989 uhd::property_tree::sptr _tree;
990
991 //! Number of motherboards, this is technically redundant but useful for
992 // easy lookups.
993 size_t _num_mboards;
994
995 //! Reference to the global I/O Service Manager
996 uhd::usrp::io_service_mgr::sptr _io_srv_mgr;
997
998 //! Registry for the blocks (it's a separate class)
999 std::unique_ptr<detail::block_container_t> _block_registry;
1000
1001 /*! Registry for the actual block connections on the crossbar
1002 * When we register blocks in the _block_registry, we also need to store some
1003 * information in this map so we can easily figure out which crossbar port a block
1004 * controller is connected to
1005 * \p keys are the string representation of the block ID
1006 * \p values are the block crossbar information structs
1007 * TODO: change from string block IDs to block_id_t with COOL custom hashing
1008 */
1009 std::unordered_map<std::string, block_xbar_info> _xbar_block_config;
1010
1011 //! Reference to the graph
1012 std::unique_ptr<detail::graph_t> _graph;
1013
1014 //! Stash a list of motherboard controllers
1015 std::vector<mb_controller::sptr> _mb_controllers;
1016
1017 //! Stash of the client zeros for all motherboards
1018 std::unordered_map<size_t, detail::client_zero::sptr> _client_zeros;
1019
1020 //! Map a pair (motherboard index, control crossbar port) to an RFNoC block
1021 // or SEP
1022 std::map<std::pair<size_t, size_t>, block_id_t> _port_block_map;
1023
1024 //! Map SEP block ID (e.g. 0/SEP#0) onto a sep_addr_t
1025 std::unordered_map<std::string, sep_addr_t> _sep_map;
1026
1027 //! List of statically connected edges. Includes SEPs too!
1028 std::vector<graph_edge_t> _static_edges;
1029
1030 //! uptr to graph stream manager
1031 graph_stream_manager::uptr _gsm;
1032
1033 //! EPID allocator. Technically not required by the rfnoc_graph, but we'll
1034 // store it here because it's such a central thing.
1035 epid_allocator::sptr _epid_alloc = std::make_shared<epid_allocator>();
1036
1037 //! Reference to a packet factory object. Gets initialized just before the GSM
1038 std::unique_ptr<chdr::chdr_packet_factory> _pkt_factory;
1039
1040 //! Map from TX streamer ID to streamer info
1041 std::map<std::string, streamer_info_t> _tx_streamers;
1042
1043 //! Map from RX streamer ID to streamer info
1044 std::map<std::string, streamer_info_t> _rx_streamers;
1045 }; /* class rfnoc_graph_impl */
1046
1047
1048 /******************************************************************************
1049 * Factory
1050 *****************************************************************************/
1051 namespace uhd { namespace rfnoc { namespace detail {
1052
1053 // Simple factory: If we already have the device, simply pass it on
make_rfnoc_graph(detail::rfnoc_device::sptr dev,const uhd::device_addr_t & device_addr)1054 rfnoc_graph::sptr make_rfnoc_graph(
1055 detail::rfnoc_device::sptr dev, const uhd::device_addr_t& device_addr)
1056 {
1057 static std::mutex _map_mutex;
1058 static std::map<std::weak_ptr<rfnoc_device>,
1059 std::weak_ptr<rfnoc_graph>,
1060 std::owner_less<std::weak_ptr<rfnoc_device>>>
1061 dev_to_graph;
1062 rfnoc_graph::sptr graph;
1063
1064 // Check if a graph was already created for this device
1065 std::lock_guard<std::mutex> lock(_map_mutex);
1066 if (dev_to_graph.count(dev) and not dev_to_graph[dev].expired()) {
1067 graph = dev_to_graph[dev].lock();
1068 if (graph != nullptr) {
1069 return graph;
1070 }
1071 }
1072
1073 // Create a new graph
1074 graph = std::make_shared<rfnoc_graph_impl>(dev, device_addr);
1075 dev_to_graph[dev] = graph;
1076 return graph;
1077 }
1078
1079 }}} /* namespace uhd::rfnoc::detail */
1080
1081 // If we don't have a device yet, create it and see if it's really an RFNoC
1082 // device. This is used by multi_usrp_rfnoc, for example.
make(const uhd::device_addr_t & device_addr)1083 rfnoc_graph::sptr rfnoc_graph::make(const uhd::device_addr_t& device_addr)
1084 {
1085 auto dev =
1086 std::dynamic_pointer_cast<detail::rfnoc_device>(uhd::device::make(device_addr));
1087 if (!dev) {
1088 throw uhd::key_error(std::string("No RFNoC devices found for ----->\n")
1089 + device_addr.to_pp_string());
1090 }
1091 return std::make_shared<rfnoc_graph_impl>(dev, device_addr);
1092 }
1093