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
|
//
// Copyright 2020 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/defaults.hpp>
#include <uhd/rfnoc/mock_block.hpp>
#include <uhd/rfnoc/switchboard_block_control.hpp>
#include <uhdlib/rfnoc/graph.hpp>
#include <uhdlib/rfnoc/node_accessor.hpp>
#include <uhdlib/utils/narrow.hpp>
#include <boost/test/unit_test.hpp>
#include <iostream>
using namespace uhd::rfnoc;
// Redeclare this here, since it's only defined outside of UHD_API
noc_block_base::make_args_t::~make_args_t() = default;
/*
* This class extends mock_reg_iface_t by adding poke and peek hooks that
* monitor writes and reads to the registers implemented within the
* Switchboard RFNoC block hardware and emulating the expected behavior of
* the hardware when those registers are read and written.
*/
const size_t NUM_INPUTS = 4;
const size_t NUM_OUTPUTS = 4;
constexpr size_t DEFAULT_MTU = 8000;
class switchboard_mock_reg_iface_t : public mock_reg_iface_t
{
public:
switchboard_mock_reg_iface_t()
{
for (size_t in = 0; in < NUM_INPUTS; in++)
output_select.push_back(0);
for (size_t out = 0; out < NUM_OUTPUTS; out++)
input_select.push_back(0);
}
void _poke_cb(
uint32_t addr, uint32_t data, uhd::time_spec_t /*time*/, bool /*ack*/) override
{
size_t chan = addr / switchboard_block_control::REG_BLOCK_SIZE;
size_t offset = addr % switchboard_block_control::REG_BLOCK_SIZE;
if (offset == switchboard_block_control::REG_DEMUX_SELECT_ADDR) {
output_select[chan] = data;
} else if (offset == switchboard_block_control::REG_MUX_SELECT_ADDR) {
input_select[chan] = data;
} else {
throw uhd::assertion_error("Invalid write from out of bounds address");
}
}
void _peek_cb(uint32_t addr, uhd::time_spec_t /*time*/) override
{
size_t chan = addr / switchboard_block_control::REG_BLOCK_SIZE;
size_t offset = addr % switchboard_block_control::REG_BLOCK_SIZE;
if (offset == switchboard_block_control::REG_DEMUX_SELECT_ADDR) {
read_memory[addr] = output_select.at(chan);
} else if (offset == switchboard_block_control::REG_MUX_SELECT_ADDR) {
read_memory[addr] = input_select.at(chan);
} else {
throw uhd::assertion_error("Invalid read from out of bounds address");
}
}
std::vector<uint32_t> input_select{};
std::vector<uint32_t> output_select{};
};
/* switchboard_block_fixture is a class which is instantiated before each test
* case is run. It sets up the block container, mock register interface,
* and mux_block_control object, all of which are accessible to the test
* case. The instance of the object is destroyed at the end of each test
* case.
*/
struct switchboard_block_fixture
{
switchboard_block_fixture()
: reg_iface(std::make_shared<switchboard_mock_reg_iface_t>())
, block_container(get_mock_block(SWITCHBOARD_BLOCK,
NUM_INPUTS,
NUM_OUTPUTS,
uhd::device_addr_t(),
DEFAULT_MTU,
ANY_DEVICE,
reg_iface))
, test_switchboard(block_container.get_block<switchboard_block_control>())
{
node_accessor.init_props(test_switchboard.get());
}
std::shared_ptr<switchboard_mock_reg_iface_t> reg_iface;
mock_block_container block_container;
std::shared_ptr<switchboard_block_control> test_switchboard;
node_accessor_t node_accessor{};
};
BOOST_FIXTURE_TEST_CASE(swboard_test_construction, switchboard_block_fixture)
{
// Check that default register values correctly initialized
for (size_t i = 0; i < NUM_INPUTS; i++)
BOOST_CHECK_EQUAL(reg_iface->output_select.at(i), 0);
for (size_t i = 0; i < NUM_OUTPUTS; i++)
BOOST_CHECK_EQUAL(reg_iface->input_select.at(i), 0);
}
BOOST_FIXTURE_TEST_CASE(swboard_test_connect, switchboard_block_fixture)
{
// Check that connect() correctly sets register values
for (size_t i = 0; i < NUM_INPUTS; i++) {
for (size_t o = 0; o < NUM_OUTPUTS; o++) {
test_switchboard->connect(i, o);
BOOST_CHECK_EQUAL(
reg_iface->output_select.at(i), o);
BOOST_CHECK_EQUAL(
reg_iface->input_select.at(o), i);
}
}
}
BOOST_FIXTURE_TEST_CASE(swboard_test_exceptions, switchboard_block_fixture)
{
// Check that out of bounds value is not written and exception thrown
BOOST_CHECK_THROW(test_switchboard->connect(0, NUM_OUTPUTS), std::exception);
BOOST_CHECK_THROW(test_switchboard->connect(NUM_INPUTS, 0), std::exception);
BOOST_CHECK_EQUAL(reg_iface->output_select.at(0), 0);
BOOST_CHECK_EQUAL(reg_iface->input_select.at(0), 0);
}
BOOST_FIXTURE_TEST_CASE(swboard_test_graph, switchboard_block_fixture)
{
detail::graph_t graph{};
mock_edge_node_t source{0, NUM_INPUTS, "MOCK_SOURCE"};
mock_edge_node_t sink{NUM_OUTPUTS, 0, "MOCK_SINK"};
UHD_LOG_INFO("TEST", "Creating graph...");
for (size_t port = 0; port < NUM_INPUTS; port++) {
graph.connect(&source,
test_switchboard.get(),
graph_edge_t{port, port, detail::graph_t::graph_edge_t::DYNAMIC, true});
}
for (size_t port = 0; port < NUM_OUTPUTS; port++) {
graph.connect(test_switchboard.get(),
&sink,
graph_edge_t{port, port, detail::graph_t::graph_edge_t::DYNAMIC, true});
}
UHD_LOG_INFO("TEST", "Committing graph...");
graph.commit();
UHD_LOG_INFO("TEST", "Commit complete.");
}
|