1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
|
//
// Copyright 2019 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#include "rfnoc_graph_mock_nodes.hpp"
#include <uhd/rfnoc/actions.hpp>
#include <uhd/rfnoc/node.hpp>
#include <uhd/utils/log.hpp>
#include <uhdlib/rfnoc/graph.hpp>
#include <uhdlib/rfnoc/node_accessor.hpp>
#include <uhdlib/rfnoc/prop_accessor.hpp>
#include <boost/test/unit_test.hpp>
#include <iostream>
const std::string STREAM_CMD_KEY = "stream_cmd";
BOOST_AUTO_TEST_CASE(test_actions_single_node)
{
node_accessor_t node_accessor{};
// Define some mock nodes:
mock_radio_node_t mock_radio(0);
auto stream_cmd =
stream_cmd_action_info::make(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS);
auto other_cmd = action_info::make("FOO");
node_accessor.send_action(&mock_radio, {res_source_info::INPUT_EDGE, 0}, stream_cmd);
node_accessor.send_action(&mock_radio, {res_source_info::INPUT_EDGE, 0}, other_cmd);
mock_radio.update_fwd_policy(node_t::forwarding_policy_t::ONE_TO_ONE);
node_accessor.send_action(&mock_radio, {res_source_info::INPUT_EDGE, 0}, other_cmd);
mock_radio.update_fwd_policy(node_t::forwarding_policy_t::ONE_TO_FAN);
node_accessor.send_action(&mock_radio, {res_source_info::INPUT_EDGE, 0}, other_cmd);
mock_radio.update_fwd_policy(node_t::forwarding_policy_t::ONE_TO_ALL);
node_accessor.send_action(&mock_radio, {res_source_info::INPUT_EDGE, 0}, other_cmd);
mock_radio.update_fwd_policy(node_t::forwarding_policy_t::ONE_TO_ALL_IN);
node_accessor.send_action(&mock_radio, {res_source_info::INPUT_EDGE, 0}, other_cmd);
mock_radio.update_fwd_policy(node_t::forwarding_policy_t::ONE_TO_ALL_OUT);
node_accessor.send_action(&mock_radio, {res_source_info::INPUT_EDGE, 0}, other_cmd);
uhd::rfnoc::detail::graph_t graph{};
graph.connect(&mock_radio, &mock_radio, {0, 0, graph_edge_t::DYNAMIC, false});
graph.commit();
stream_cmd =
stream_cmd_action_info::make(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE);
stream_cmd->stream_cmd.num_samps = 37;
node_accessor.post_action(&mock_radio, {res_source_info::USER, 0}, stream_cmd);
BOOST_REQUIRE_EQUAL(mock_radio.last_num_samps, 37);
}
BOOST_AUTO_TEST_CASE(test_actions_simple_graph)
{
node_accessor_t node_accessor{};
uhd::rfnoc::detail::graph_t graph{};
// Define some mock nodes:
mock_radio_node_t mock_rx_radio{0};
mock_ddc_node_t mock_ddc{};
mock_fifo_t mock_fifo{1};
mock_streamer_t mock_streamer{1};
// These init calls would normally be done by the framework
node_accessor.init_props(&mock_rx_radio);
node_accessor.init_props(&mock_ddc);
node_accessor.init_props(&mock_fifo);
node_accessor.init_props(&mock_streamer);
graph.connect(&mock_rx_radio, &mock_ddc, {0, 0, graph_edge_t::DYNAMIC, true});
graph.connect(&mock_ddc, &mock_fifo, {0, 0, graph_edge_t::DYNAMIC, true});
graph.connect(&mock_fifo, &mock_streamer, {0, 0, graph_edge_t::DYNAMIC, true});
graph.commit();
// Force the DDC to actually set a decimation rate != 1
mock_streamer.set_property<double>("samp_rate", 10e6, 0);
uhd::stream_cmd_t num_samps_cmd(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE);
constexpr size_t NUM_SAMPS = 100;
num_samps_cmd.num_samps = NUM_SAMPS;
mock_streamer.issue_stream_cmd(num_samps_cmd, 0);
BOOST_CHECK_EQUAL(
NUM_SAMPS * mock_ddc.get_property<int>("decim", 0), mock_rx_radio.last_num_samps);
}
BOOST_AUTO_TEST_CASE(test_action_forwarding_map)
{
node_accessor_t node_accessor{};
uhd::rfnoc::detail::graph_t graph{};
constexpr size_t NUM_GENERATOR_OUTPUTS = 8;
constexpr size_t NUM_RECEIVER_INPUTS = 8;
node_t::forwarding_map_t fwd_map = {
// input edges 0-3 --> output edges 3-0
{{res_source_info::INPUT_EDGE, 0}, {{res_source_info::OUTPUT_EDGE, 3}}},
{{res_source_info::INPUT_EDGE, 1}, {{res_source_info::OUTPUT_EDGE, 2}}},
{{res_source_info::INPUT_EDGE, 2}, {{res_source_info::OUTPUT_EDGE, 1}}},
{{res_source_info::INPUT_EDGE, 3}, {{res_source_info::OUTPUT_EDGE, 0}}},
// input edge 4 --> output edges 4 and 5
{{res_source_info::INPUT_EDGE, 4},
{{res_source_info::OUTPUT_EDGE, 4}, {res_source_info::OUTPUT_EDGE, 5}}},
// input edge 5 --> output edges 6 and 7
{{res_source_info::INPUT_EDGE, 5},
{{res_source_info::OUTPUT_EDGE, 6}, {res_source_info::OUTPUT_EDGE, 7}}},
// input edge 6 no destination (i.e. drop)
{{res_source_info::INPUT_EDGE, 6}, {}}
// input edge 7 not in map (i.e. drop)
};
mock_edge_node_t generator{0, NUM_GENERATOR_OUTPUTS, "MOCK_EDGE_NODE<generator>"};
mock_routing_node_t router{NUM_GENERATOR_OUTPUTS, NUM_RECEIVER_INPUTS};
mock_edge_node_t receiver{NUM_RECEIVER_INPUTS, 0, "MOCK_EDGE_NODE<receiver>"};
router.set_action_forwarding_map(fwd_map);
// These init calls would normally be done by the framework
node_accessor.init_props(&generator);
node_accessor.init_props(&router);
node_accessor.init_props(&receiver);
using graph_edge_t = uhd::rfnoc::detail::graph_t::graph_edge_t;
// Connect the nodes in the graph
for (size_t i = 0; i < NUM_GENERATOR_OUTPUTS; i++) {
graph.connect(&generator, &router, {i, i, graph_edge_t::DYNAMIC, true});
}
for (size_t i = 0; i < NUM_RECEIVER_INPUTS; i++) {
graph.connect(&router, &receiver, {i, i, graph_edge_t::DYNAMIC, true});
}
graph.commit();
UHD_LOG_INFO("TEST", "Now testing map-driven action forwarding");
for (size_t i = 0; i < NUM_GENERATOR_OUTPUTS; i++) {
auto cmd = action_info::make("action");
// The payload of the outgoing event sent from the generator
// consists of the port number on which the action was emitted.
// This makes it easier for us to ensure that the action was routed
// to the correct receiver port, since the actions are all named
// 'action'.
cmd->payload.push_back(static_cast<uint8_t>(i));
generator.post_output_edge_action(cmd, i);
}
auto received_actions = receiver.get_received_actions_map();
auto receiver_port_get_action_count = [received_actions](size_t port) -> size_t {
auto itr = received_actions.find({res_source_info::INPUT_EDGE, port});
if (itr == received_actions.end()) {
return 0;
}
return itr->second.size();
};
auto receiver_port_get_action = [received_actions](
size_t port, size_t n) -> action_info::sptr {
auto itr = received_actions.find({res_source_info::INPUT_EDGE, port});
if (itr == received_actions.end()) {
// If the action wasn't found, return a dummy action
auto bad_action = action_info::make("invalid_action");
return bad_action;
}
return itr->second.at(n);
};
// Ensure correct count of actions at each receiver port
BOOST_CHECK_EQUAL(receiver_port_get_action_count(0), 1);
BOOST_CHECK_EQUAL(receiver_port_get_action_count(1), 1);
BOOST_CHECK_EQUAL(receiver_port_get_action_count(2), 1);
BOOST_CHECK_EQUAL(receiver_port_get_action_count(3), 1);
BOOST_CHECK_EQUAL(receiver_port_get_action_count(4), 1);
BOOST_CHECK_EQUAL(receiver_port_get_action_count(5), 1);
BOOST_CHECK_EQUAL(receiver_port_get_action_count(6), 1);
BOOST_CHECK_EQUAL(receiver_port_get_action_count(7), 1);
// Ensure correct payload of received action (indicates source edge from
// which the action was generator)
BOOST_CHECK_EQUAL(receiver_port_get_action(0, 0)->payload.at(0), 3);
BOOST_CHECK_EQUAL(receiver_port_get_action(1, 0)->payload.at(0), 2);
BOOST_CHECK_EQUAL(receiver_port_get_action(2, 0)->payload.at(0), 1);
BOOST_CHECK_EQUAL(receiver_port_get_action(3, 0)->payload.at(0), 0);
BOOST_CHECK_EQUAL(receiver_port_get_action(4, 0)->payload.at(0), 4);
BOOST_CHECK_EQUAL(receiver_port_get_action(5, 0)->payload.at(0), 4);
BOOST_CHECK_EQUAL(receiver_port_get_action(6, 0)->payload.at(0), 5);
BOOST_CHECK_EQUAL(receiver_port_get_action(7, 0)->payload.at(0), 5);
}
BOOST_AUTO_TEST_CASE(test_action_forwarding_map_exception_invalid_destination)
{
node_accessor_t node_accessor{};
uhd::rfnoc::detail::graph_t graph{};
// Create a map that will generate an exception at action forwarding time
// due to the mapping pointing to a non-existent port
node_t::forwarding_map_t invalid_fwd_map = {
// input edge 0 --> output edge 1 (output port does not exist)
{{res_source_info::INPUT_EDGE, 0}, {{res_source_info::OUTPUT_EDGE, 1}}},
};
mock_edge_node_t generator{0, 1, "MOCK_EDGE_NODE<generator>"};
mock_routing_node_t router{1, 1};
mock_edge_node_t receiver{1, 0, "MOCK_EDGE_NODE<receiver>"};
router.set_action_forwarding_map(invalid_fwd_map);
// These init calls would normally be done by the framework
node_accessor.init_props(&generator);
node_accessor.init_props(&router);
node_accessor.init_props(&receiver);
using graph_edge_t = uhd::rfnoc::detail::graph_t::graph_edge_t;
// Connect the nodes in the graph
graph.connect(&generator, &router, {0, 0, graph_edge_t::DYNAMIC, true});
graph.connect(&router, &receiver, {0, 0, graph_edge_t::DYNAMIC, true});
graph.commit();
UHD_LOG_INFO(
"TEST", "Now testing action forwarding with invalid map (no destination port)");
auto cmd = action_info::make("action");
BOOST_REQUIRE_THROW(generator.post_output_edge_action(cmd, 0), uhd::rfnoc_error);
}
|