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