diff options
-rw-r--r-- | host/docs/properties.dox | 10 | ||||
-rw-r--r-- | host/include/uhd/rfnoc/defaults.hpp | 1 | ||||
-rw-r--r-- | host/lib/include/uhdlib/rfnoc/radio_control_impl.hpp | 9 | ||||
-rw-r--r-- | host/lib/include/uhdlib/rfnoc/rfnoc_rx_streamer.hpp | 1 | ||||
-rw-r--r-- | host/lib/include/uhdlib/rfnoc/rfnoc_tx_streamer.hpp | 1 | ||||
-rw-r--r-- | host/lib/include/uhdlib/transport/rx_streamer_impl.hpp | 6 | ||||
-rw-r--r-- | host/lib/include/uhdlib/transport/tx_streamer_impl.hpp | 6 | ||||
-rw-r--r-- | host/lib/rfnoc/radio_control_impl.cpp | 37 | ||||
-rw-r--r-- | host/lib/rfnoc/rfnoc_rx_streamer.cpp | 33 | ||||
-rw-r--r-- | host/lib/rfnoc/rfnoc_tx_streamer.cpp | 32 | ||||
-rw-r--r-- | host/tests/rfnoc_block_tests/x4xx_radio_block_test.cpp | 106 |
11 files changed, 232 insertions, 10 deletions
diff --git a/host/docs/properties.dox b/host/docs/properties.dox index a8f505382..b6e899dfe 100644 --- a/host/docs/properties.dox +++ b/host/docs/properties.dox @@ -400,6 +400,16 @@ Edge Properties: much they have affected the amplitude. The TX and RX streamer objects can then apply the inverse of this scaling factor to correct for the scaling before returning the signal to the user. +- `atomic_item_size`: Blocks (e.g. uhd::rfnoc::radio_control) might need a + non-dividable amount of data per clock cycle, e.g. the radio block needs + `sample_per_cycle` times the size of the type bytes of data in each cycle. + This property allows a block to specify this amount of data per cycle which + allows other blocks or the streamer objects to adapt the samples per packet + accordingly. + Note: Because all blocks in a graph must agree on one value for this property + the blocks have to calculate the `atomic_item_size` as the least common + multiple of their own `atomic_item_size` and the edge property to accommodate + for other blocks up- or downstream. User properties: - `spp`: Blocks that produce data in chunks of samples can use this to describe diff --git a/host/include/uhd/rfnoc/defaults.hpp b/host/include/uhd/rfnoc/defaults.hpp index 02cd00dbe..aa7778aac 100644 --- a/host/include/uhd/rfnoc/defaults.hpp +++ b/host/include/uhd/rfnoc/defaults.hpp @@ -23,6 +23,7 @@ static const std::string PROP_KEY_FREQ("freq"); static const std::string PROP_KEY_TICK_RATE("tick_rate"); static const std::string PROP_KEY_SPP("spp"); static const std::string PROP_KEY_MTU("mtu"); +static const std::string PROP_KEY_ATOMIC_ITEM_SIZE("atomic_item_size"); static const std::string NODE_ID_SEP("SEP"); diff --git a/host/lib/include/uhdlib/rfnoc/radio_control_impl.hpp b/host/lib/include/uhdlib/rfnoc/radio_control_impl.hpp index de83c403c..ace5b7a45 100644 --- a/host/lib/include/uhdlib/rfnoc/radio_control_impl.hpp +++ b/host/lib/include/uhdlib/rfnoc/radio_control_impl.hpp @@ -323,6 +323,13 @@ protected: return _rate; } + /* + * Returns the number of bytes to be processed in each clock cycle. + */ + size_t get_atomic_item_size() const + { + return (_samp_width / 8) * _spc; + } //! Properties for samp_rate (one per port) std::vector<property_t<double>> _samp_rate_in; //! Properties for samp_rate (one per port) @@ -380,6 +387,8 @@ private: const uint32_t _spc; std::vector<property_t<int>> _spp_prop; + std::vector<property_t<size_t>> _atomic_item_size_in; + std::vector<property_t<size_t>> _atomic_item_size_out; //! Properties for type_in (one per port) std::vector<property_t<io_type_t>> _type_in; //! Properties for type_out (one per port) diff --git a/host/lib/include/uhdlib/rfnoc/rfnoc_rx_streamer.hpp b/host/lib/include/uhdlib/rfnoc/rfnoc_rx_streamer.hpp index ca569e338..2ed11182a 100644 --- a/host/lib/include/uhdlib/rfnoc/rfnoc_rx_streamer.hpp +++ b/host/lib/include/uhdlib/rfnoc/rfnoc_rx_streamer.hpp @@ -110,6 +110,7 @@ private: std::vector<property_t<double>> _tick_rate_in; std::vector<property_t<std::string>> _type_in; std::vector<property_t<size_t>> _mtu_in; + std::vector<property_t<size_t>> _atomic_item_size_in; // Streamer unique ID const std::string _unique_id; diff --git a/host/lib/include/uhdlib/rfnoc/rfnoc_tx_streamer.hpp b/host/lib/include/uhdlib/rfnoc/rfnoc_tx_streamer.hpp index a071d7248..76369474f 100644 --- a/host/lib/include/uhdlib/rfnoc/rfnoc_tx_streamer.hpp +++ b/host/lib/include/uhdlib/rfnoc/rfnoc_tx_streamer.hpp @@ -113,6 +113,7 @@ private: std::vector<property_t<double>> _tick_rate_out; std::vector<property_t<std::string>> _type_out; std::vector<property_t<size_t>> _mtu_out; + std::vector<property_t<size_t>> _atomic_item_size_out; // Streamer unique ID const std::string _unique_id; diff --git a/host/lib/include/uhdlib/transport/rx_streamer_impl.hpp b/host/lib/include/uhdlib/transport/rx_streamer_impl.hpp index 1b25b308b..03cd3824a 100644 --- a/host/lib/include/uhdlib/transport/rx_streamer_impl.hpp +++ b/host/lib/include/uhdlib/transport/rx_streamer_impl.hpp @@ -208,6 +208,12 @@ protected: _converters[chan]->set_scalar(scale_factor); } + //! set maximum number of sample (per packet) + void set_max_num_samps(const size_t value) + { + _spp = value; + } + //! Returns the maximum payload size size_t get_mtu() const { diff --git a/host/lib/include/uhdlib/transport/tx_streamer_impl.hpp b/host/lib/include/uhdlib/transport/tx_streamer_impl.hpp index 6b34c1c10..38f16671a 100644 --- a/host/lib/include/uhdlib/transport/tx_streamer_impl.hpp +++ b/host/lib/include/uhdlib/transport/tx_streamer_impl.hpp @@ -329,6 +329,12 @@ protected: return _zero_copy_streamer.get_tick_rate(); } + //! set maximum number of sample (per packet) + void set_max_num_samps(const size_t value) + { + _spp = value; + } + //! Returns the maximum payload size size_t get_mtu() const { diff --git a/host/lib/rfnoc/radio_control_impl.cpp b/host/lib/rfnoc/radio_control_impl.cpp index a5c69e229..a08470d8a 100644 --- a/host/lib/rfnoc/radio_control_impl.cpp +++ b/host/lib/rfnoc/radio_control_impl.cpp @@ -9,6 +9,7 @@ #include <uhd/rfnoc/multichan_register_iface.hpp> #include <uhd/rfnoc/register_iface.hpp> #include <uhd/utils/log.hpp> +#include <uhd/utils/math.hpp> #include <uhdlib/rfnoc/radio_control_impl.hpp> #include <uhdlib/utils/compat_check.hpp> #include <map> @@ -106,6 +107,8 @@ radio_control_impl::radio_control_impl(make_args_ptr make_args) }); // Register spp properties and resolvers _spp_prop.reserve(get_num_output_ports()); + _atomic_item_size_in.reserve(get_num_input_ports()); + _atomic_item_size_out.reserve(get_num_output_ports()); _samp_rate_in.reserve(get_num_input_ports()); _samp_rate_out.reserve(get_num_output_ports()); _type_in.reserve(get_num_input_ports()); @@ -116,6 +119,14 @@ radio_control_impl::radio_control_impl(make_args_ptr make_args) get_max_spp(get_max_payload_size({res_source_info::OUTPUT_EDGE, chan})); _spp_prop.push_back( property_t<int>(PROP_KEY_SPP, default_spp, {res_source_info::USER, chan})); + _atomic_item_size_in.push_back( + property_t<size_t>(PROP_KEY_ATOMIC_ITEM_SIZE, + get_atomic_item_size(), + {res_source_info::INPUT_EDGE, chan})); + _atomic_item_size_out.push_back( + property_t<size_t>(PROP_KEY_ATOMIC_ITEM_SIZE, + get_atomic_item_size(), + {res_source_info::OUTPUT_EDGE, chan})); _samp_rate_in.push_back(property_t<double>( PROP_KEY_SAMP_RATE, get_tick_rate(), {res_source_info::INPUT_EDGE, chan})); _samp_rate_out.push_back(property_t<double>( @@ -132,6 +143,8 @@ radio_control_impl::radio_control_impl(make_args_ptr make_args) _radio_reg_iface.poke32( regmap::REG_RX_MAX_WORDS_PER_PKT, words_per_pkt, chan); }); + register_property(&_atomic_item_size_in.back()); + register_property(&_atomic_item_size_out.back()); register_property(&_samp_rate_in.back()); register_property(&_samp_rate_out.back()); register_property(&_type_in.back()); @@ -163,6 +176,30 @@ radio_control_impl::radio_control_impl(make_args_ptr make_args) "spp must be greater than zero! Coercing to " << spp.get()); } }); + add_property_resolver({&_atomic_item_size_in.back(), + get_mtu_prop_ref({res_source_info::INPUT_EDGE, chan})}, + {&_atomic_item_size_in.back()}, + [this, chan, + &ais_in = _atomic_item_size_in.back()]() { + ais_in = uhd::math::lcm<size_t>(ais_in, get_atomic_item_size()); + ais_in = std::min<size_t>(ais_in, get_mtu({res_source_info::INPUT_EDGE, chan})); + if (ais_in.get() % get_atomic_item_size() > 0) { + ais_in = ais_in - (ais_in.get() % get_atomic_item_size()); + } + RFNOC_LOG_TRACE("Resolve atomic item size in to " << ais_in); + }); + add_property_resolver({&_atomic_item_size_out.back(), + get_mtu_prop_ref({res_source_info::OUTPUT_EDGE, chan})}, + {&_atomic_item_size_out.back()}, + [this, chan, + &ais_out = _atomic_item_size_out.back()]() { + ais_out = uhd::math::lcm<size_t>(ais_out, get_atomic_item_size()); + ais_out = std::min<size_t>(ais_out, get_mtu({res_source_info::OUTPUT_EDGE, chan})); + if (ais_out.get() % get_atomic_item_size() > 0) { + ais_out = ais_out - (ais_out.get() % get_atomic_item_size()); + } + RFNOC_LOG_TRACE("Resolve atomic item size out to " << ais_out); + }); // Note: The following resolver calls coerce_rate(), which is virtual. // At run time, it will use the implementation by the child class. add_property_resolver({&_samp_rate_in.back(), &_samp_rate_out.back()}, diff --git a/host/lib/rfnoc/rfnoc_rx_streamer.cpp b/host/lib/rfnoc/rfnoc_rx_streamer.cpp index d57b8aab2..5e6dddcbe 100644 --- a/host/lib/rfnoc/rfnoc_rx_streamer.cpp +++ b/host/lib/rfnoc/rfnoc_rx_streamer.cpp @@ -59,6 +59,7 @@ rfnoc_rx_streamer::rfnoc_rx_streamer(const size_t num_chans, _tick_rate_in.reserve(num_chans); _type_in.reserve(num_chans); _mtu_in.reserve(num_chans); + _atomic_item_size_in.reserve(num_chans); for (size_t i = 0; i < num_chans; i++) { _register_props(i, stream_args.otw_format); @@ -188,13 +189,16 @@ void rfnoc_rx_streamer::_register_props(const size_t chan, const std::string& ot PROP_KEY_TYPE, otw_format, {res_source_info::INPUT_EDGE, chan})); _mtu_in.emplace_back( property_t<size_t>(PROP_KEY_MTU, get_mtu(), {res_source_info::INPUT_EDGE, chan})); + _atomic_item_size_in.emplace_back( + property_t<size_t>(PROP_KEY_ATOMIC_ITEM_SIZE, 1, {res_source_info::INPUT_EDGE, chan})); // Give us some shorthands for the rest of this function - property_t<double>* scaling_in = &_scaling_in.back(); - property_t<double>* samp_rate_in = &_samp_rate_in.back(); - property_t<double>* tick_rate_in = &_tick_rate_in.back(); - property_t<std::string>* type_in = &_type_in.back(); - property_t<size_t>* mtu_in = &_mtu_in.back(); + property_t<double>* scaling_in = &_scaling_in.back(); + property_t<double>* samp_rate_in = &_samp_rate_in.back(); + property_t<double>* tick_rate_in = &_tick_rate_in.back(); + property_t<std::string>* type_in = &_type_in.back(); + property_t<size_t>* mtu_in = &_mtu_in.back(); + property_t<size_t>* atomic_item_size_in = &_atomic_item_size_in.back(); // Register them register_property(scaling_in); @@ -202,6 +206,7 @@ void rfnoc_rx_streamer::_register_props(const size_t chan, const std::string& ot register_property(tick_rate_in); register_property(type_in); register_property(mtu_in); + register_property(atomic_item_size_in); // Add resolvers add_property_resolver({scaling_in}, {}, [& scaling_in = *scaling_in, chan, this]() { @@ -228,6 +233,24 @@ void rfnoc_rx_streamer::_register_props(const size_t chan, const std::string& ot this->set_tick_rate(tick_rate_in.get()); } }); + + add_property_resolver( + {atomic_item_size_in, mtu_in}, {}, [&ais = *atomic_item_size_in, chan, this]() { + const auto UHD_UNUSED(log_chan) = chan; + RFNOC_LOG_TRACE("Calling resolver for `atomic_item_size'@" << chan); + if (ais.is_valid()) { + const auto spp = this->rx_streamer_impl::get_max_num_samps(); + if (spp < ais.get()) { + throw uhd::value_error("samples per package must not be smaller than atomic item size"); + } + const auto misalignment = spp % ais.get(); + RFNOC_LOG_TRACE("Check atomic item size " << ais.get() << " divides spp " << spp); + if (misalignment > 0) { + RFNOC_LOG_TRACE("Reduce spp by " << misalignment << " to align with atomic item size"); + this->rx_streamer_impl::set_max_num_samps(spp - misalignment); + } + } + }); } void rfnoc_rx_streamer::_handle_rx_event_action( diff --git a/host/lib/rfnoc/rfnoc_tx_streamer.cpp b/host/lib/rfnoc/rfnoc_tx_streamer.cpp index 969c41ae6..411affe62 100644 --- a/host/lib/rfnoc/rfnoc_tx_streamer.cpp +++ b/host/lib/rfnoc/rfnoc_tx_streamer.cpp @@ -47,6 +47,7 @@ rfnoc_tx_streamer::rfnoc_tx_streamer(const size_t num_chans, _tick_rate_out.reserve(num_chans); _type_out.reserve(num_chans); _mtu_out.reserve(num_chans); + _atomic_item_size_out.reserve(num_chans); for (size_t i = 0; i < num_chans; i++) { _register_props(i, stream_args.otw_format); @@ -171,13 +172,16 @@ void rfnoc_tx_streamer::_register_props(const size_t chan, const std::string& ot PROP_KEY_TYPE, otw_format, {res_source_info::OUTPUT_EDGE, chan})); _mtu_out.push_back(property_t<size_t>( PROP_KEY_MTU, get_mtu(), {res_source_info::OUTPUT_EDGE, chan})); + _atomic_item_size_out.push_back( + property_t<size_t>(PROP_KEY_ATOMIC_ITEM_SIZE, 1, {res_source_info::OUTPUT_EDGE, chan})); // Give us some shorthands for the rest of this function - property_t<double>* scaling_out = &_scaling_out.back(); - property_t<double>* samp_rate_out = &_samp_rate_out.back(); - property_t<double>* tick_rate_out = &_tick_rate_out.back(); - property_t<std::string>* type_out = &_type_out.back(); - property_t<size_t>* mtu_out = &_mtu_out.back(); + property_t<double>* scaling_out = &_scaling_out.back(); + property_t<double>* samp_rate_out = &_samp_rate_out.back(); + property_t<double>* tick_rate_out = &_tick_rate_out.back(); + property_t<std::string>* type_out = &_type_out.back(); + property_t<size_t>* mtu_out = &_mtu_out.back(); + property_t<size_t>* atomic_item_size_out = &_atomic_item_size_out.back(); // Register them register_property(scaling_out); @@ -185,6 +189,7 @@ void rfnoc_tx_streamer::_register_props(const size_t chan, const std::string& ot register_property(tick_rate_out); register_property(type_out); register_property(mtu_out); + register_property(atomic_item_size_out); // Add resolvers add_property_resolver( @@ -212,6 +217,23 @@ void rfnoc_tx_streamer::_register_props(const size_t chan, const std::string& ot this->set_tick_rate(tick_rate_out.get()); } }); + add_property_resolver( + {atomic_item_size_out, mtu_out}, {}, [&ais = *atomic_item_size_out, chan, this]() { + const auto UHD_UNUSED(log_chan) = chan; + RFNOC_LOG_TRACE("Calling resolver for `atomic_item_size'@" << chan); + if (ais.is_valid()) { + const auto spp = this->tx_streamer_impl::get_max_num_samps(); + if (spp < ais.get()) { + throw uhd::value_error("samples per package must not be smaller than atomic item size"); + } + const auto misalignment = spp % ais.get(); + RFNOC_LOG_TRACE("Check atomic item size " << ais.get() << " divides spp " << spp); + if (misalignment > 0) { + RFNOC_LOG_TRACE("Reduce spp by " << misalignment << " to align with atomic item size"); + this->tx_streamer_impl::set_max_num_samps(spp - misalignment); + } + } + }); } void rfnoc_tx_streamer::_handle_tx_event_action( diff --git a/host/tests/rfnoc_block_tests/x4xx_radio_block_test.cpp b/host/tests/rfnoc_block_tests/x4xx_radio_block_test.cpp index b6c6c21ba..79cb27039 100644 --- a/host/tests/rfnoc_block_tests/x4xx_radio_block_test.cpp +++ b/host/tests/rfnoc_block_tests/x4xx_radio_block_test.cpp @@ -316,6 +316,112 @@ BOOST_FIXTURE_TEST_CASE(x400_radio_test_graph, x400_radio_fixture) UHD_LOG_INFO("TEST", "Commit complete."); } +/****************************************************************************** + * RFNoC atomic item size property test + * + * This test case ensures that the radio block propagates atomic item size correct + *****************************************************************************/ +BOOST_FIXTURE_TEST_CASE(x400_radio_test_prop_prop, x400_radio_fixture) +{ + detail::graph_t graph{}; + detail::graph_t::graph_edge_t edge_port_info0; + edge_port_info0.src_port = 0; + edge_port_info0.dst_port = 0; + edge_port_info0.property_propagation_active = true; + edge_port_info0.edge = detail::graph_t::graph_edge_t::DYNAMIC; + detail::graph_t::graph_edge_t edge_port_info1; + edge_port_info1.src_port = 1; + edge_port_info1.dst_port = 1; + edge_port_info1.property_propagation_active = true; + edge_port_info1.edge = detail::graph_t::graph_edge_t::DYNAMIC; + + mock_terminator_t mock_sink_term(2, {}, "MOCK_SINK"); + mock_terminator_t mock_source_term(2, {}, "MOCK_SOURCE"); + + UHD_LOG_INFO("TEST", "Priming mock block properties"); + mock_source_term.set_edge_property<size_t>( + "atomic_item_size", 1, {res_source_info::OUTPUT_EDGE, 0}); + mock_source_term.set_edge_property<size_t>( + "atomic_item_size", 1, {res_source_info::OUTPUT_EDGE, 1}); + mock_source_term.set_edge_property<size_t>( + "mtu", 99, {res_source_info::OUTPUT_EDGE, 0}); + mock_sink_term.set_edge_property<size_t>( + "atomic_item_size", 999, {res_source_info::INPUT_EDGE, 0}); + mock_sink_term.set_edge_property<size_t>( + "atomic_item_size", 999, {res_source_info::INPUT_EDGE, 1}); + + UHD_LOG_INFO("TEST", "Creating graph..."); + graph.connect(&mock_source_term, test_radio.get(), edge_port_info0); + graph.connect(&mock_source_term, test_radio.get(), edge_port_info1); + graph.connect(test_radio.get(), &mock_sink_term, edge_port_info0); + graph.connect(test_radio.get(), &mock_sink_term, edge_port_info1); + UHD_LOG_INFO("TEST", "Committing graph..."); + graph.commit(); + + UHD_LOG_INFO("TEST", "Testing atomic item size propagation..."); + + // radio has a sample width of 32 bits (sc16) and spc of 1 by default + // this results in a atomic item size of 4 for the radio + // because property gets propagated immediately after setting in + // the committed graph we can check the result of the propagation + // at the mock edges + + mock_source_term.set_edge_property<size_t>( + "atomic_item_size", 1, {res_source_info::OUTPUT_EDGE, 0}); + BOOST_CHECK_EQUAL(mock_source_term.get_edge_property<size_t>( + "atomic_item_size", {res_source_info::OUTPUT_EDGE, 0}), 4); + + mock_source_term.set_edge_property<size_t>( + "atomic_item_size", 4, {res_source_info::OUTPUT_EDGE, 1}); + BOOST_CHECK_EQUAL(mock_source_term.get_edge_property<size_t> + ("atomic_item_size", {res_source_info::OUTPUT_EDGE, 1}), 4); + + mock_source_term.set_edge_property<size_t>( + "atomic_item_size", 9, {res_source_info::OUTPUT_EDGE, 0}); + BOOST_CHECK_EQUAL(mock_source_term.get_edge_property<size_t>( + "atomic_item_size", {res_source_info::OUTPUT_EDGE, 0}), 36); + + mock_source_term.set_edge_property<size_t>( + "atomic_item_size", 10, {res_source_info::OUTPUT_EDGE, 1}); + BOOST_CHECK_EQUAL(mock_source_term.get_edge_property<size_t>( + "atomic_item_size", {res_source_info::OUTPUT_EDGE, 1}), 20); + + mock_source_term.set_edge_property<size_t>( + "mtu", 99, {res_source_info::OUTPUT_EDGE, 0}); + mock_source_term.set_edge_property<size_t>( + "atomic_item_size", 25, {res_source_info::OUTPUT_EDGE, 0}); + BOOST_CHECK_EQUAL(mock_source_term.get_edge_property<size_t>( + "atomic_item_size", {res_source_info::OUTPUT_EDGE, 0}), 96); + + //repeat for sink + mock_sink_term.set_edge_property<size_t>( + "atomic_item_size", 1, {res_source_info::INPUT_EDGE, 0}); + BOOST_CHECK_EQUAL(mock_sink_term.get_edge_property<size_t>( + "atomic_item_size", {res_source_info::INPUT_EDGE, 0}), 4); + + mock_sink_term.set_edge_property<size_t>( + "atomic_item_size", 4, {res_source_info::INPUT_EDGE, 1}); + BOOST_CHECK_EQUAL(mock_sink_term.get_edge_property<size_t> + ("atomic_item_size", {res_source_info::INPUT_EDGE, 1}), 4); + + mock_sink_term.set_edge_property<size_t>( + "atomic_item_size", 7, {res_source_info::INPUT_EDGE, 0}); + BOOST_CHECK_EQUAL(mock_sink_term.get_edge_property<size_t>( + "atomic_item_size", {res_source_info::INPUT_EDGE, 0}), 28); + + mock_sink_term.set_edge_property<size_t>( + "atomic_item_size", 22, {res_source_info::INPUT_EDGE, 1}); + BOOST_CHECK_EQUAL(mock_sink_term.get_edge_property<size_t>( + "atomic_item_size", {res_source_info::INPUT_EDGE, 1}), 44); + + mock_sink_term.set_edge_property<size_t>( + "mtu", 179, {res_source_info::INPUT_EDGE, 0}); + mock_sink_term.set_edge_property<size_t>( + "atomic_item_size", 47, {res_source_info::INPUT_EDGE, 0}); + BOOST_CHECK_EQUAL(mock_sink_term.get_edge_property<size_t>( + "atomic_item_size", {res_source_info::INPUT_EDGE, 0}), 176); +} + BOOST_FIXTURE_TEST_CASE(zbx_api_freq_tx_test, x400_radio_fixture) { const std::string log = "ZBX_API_TX_FREQUENCY_TEST"; |