diff options
author | Martin Braun <martin.braun@ettus.com> | 2019-06-28 11:44:37 -0700 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2019-11-26 11:49:30 -0800 |
commit | a36fc4604f9b37c46fd203d6461d0d38065a97e9 (patch) | |
tree | 8f1687fceb7ffc05eaed91acc8d4c794a5e448f2 /host | |
parent | dbca54be2480a9ea1e84dd4b73349fd07fcaafe9 (diff) | |
download | uhd-a36fc4604f9b37c46fd203d6461d0d38065a97e9.tar.gz uhd-a36fc4604f9b37c46fd203d6461d0d38065a97e9.tar.bz2 uhd-a36fc4604f9b37c46fd203d6461d0d38065a97e9.zip |
rfnoc: DDC: Fix property propagation
- Combine scaling and samp_rate resolvers
- Prioritize decim when user has set it for DDC:
When samp_rate_in changes, either the samp_rate_out or the decim
values may change to accommodate it. If decim has been set by the
user (which can be determined by the valid flag), prefer changing
samp_rate_out over decim.
Diffstat (limited to 'host')
-rw-r--r-- | host/lib/rfnoc/ddc_block_control.cpp | 166 | ||||
-rw-r--r-- | host/tests/CMakeLists.txt | 10 | ||||
-rw-r--r-- | host/tests/rfnoc_blocks_test.cpp | 73 | ||||
-rw-r--r-- | host/tests/rfnoc_graph_mock_nodes.hpp | 61 |
4 files changed, 225 insertions, 85 deletions
diff --git a/host/lib/rfnoc/ddc_block_control.cpp b/host/lib/rfnoc/ddc_block_control.cpp index 56bb8cc5a..0c2721833 100644 --- a/host/lib/rfnoc/ddc_block_control.cpp +++ b/host/lib/rfnoc/ddc_block_control.cpp @@ -6,11 +6,12 @@ #include <uhd/exception.hpp> #include <uhd/rfnoc/ddc_block_control.hpp> +#include <uhd/rfnoc/defaults.hpp> #include <uhd/rfnoc/property.hpp> #include <uhd/rfnoc/registry.hpp> -#include <uhd/rfnoc/defaults.hpp> #include <uhd/types/ranges.hpp> #include <uhd/utils/log.hpp> +#include <uhd/utils/math.hpp> #include <uhdlib/usrp/cores/dsp_core_utils.hpp> #include <uhdlib/utils/compat_check.hpp> #include <uhdlib/utils/math.hpp> @@ -20,11 +21,10 @@ namespace { -constexpr double DEFAULT_RATE = 1e9; -constexpr double DEFAULT_SCALING = 1.0; -constexpr int DEFAULT_DECIM = 1; -constexpr double DEFAULT_FREQ = 0.0; -const uhd::rfnoc::io_type_t DEFAULT_TYPE = uhd::rfnoc::IO_TYPE_SC16; +constexpr double DEFAULT_SCALING = 1.0; +constexpr int DEFAULT_DECIM = 1; +constexpr double DEFAULT_FREQ = 0.0; +const uhd::rfnoc::io_type_t DEFAULT_TYPE = uhd::rfnoc::IO_TYPE_SC16; //! Space (in bytes) between register banks per channel constexpr uint32_t REG_CHAN_OFFSET = 2048; @@ -74,7 +74,7 @@ public: // Load list of valid decimation values std::set<size_t> decims{1}; // 1 is always a valid decimatino for (size_t hb = 0; hb < _num_halfbands; hb++) { - for (size_t cic_decim = 0; cic_decim < _cic_max_decim; cic_decim++) { + for (size_t cic_decim = 1; cic_decim <= _cic_max_decim; cic_decim++) { decims.insert((1 << hb) * cic_decim); } } @@ -122,24 +122,29 @@ public: uhd::freq_range_t get_frequency_range(const size_t chan) const { - const double input_rate = _samp_rate_in.at(chan).get(); + const double input_rate = + _samp_rate_in.at(chan).is_valid() ? _samp_rate_in.at(chan).get() : 1.0; // TODO add steps return uhd::freq_range_t(-input_rate / 2, input_rate / 2); } double get_input_rate(const size_t chan) const { - return _samp_rate_in.at(chan).get(); + return _samp_rate_in.at(chan).is_valid() ? _samp_rate_in.at(chan).get() : 1.0; } double get_output_rate(const size_t chan) const { - return _samp_rate_out.at(chan).get(); + return _samp_rate_out.at(chan).is_valid() ? _samp_rate_out.at(chan).get() : 1.0; } uhd::meta_range_t get_output_rates(const size_t chan) const { uhd::meta_range_t result; + if (!_samp_rate_in.at(chan).is_valid()) { + result.push_back(uhd::range_t(1.0)); + return result; + } const double input_rate = _samp_rate_in.at(chan).get(); // The decimations are stored in order (from smallest to biggest), so // iterate in reverse order so we can add rates from smallest to biggest @@ -151,9 +156,17 @@ public: double set_output_rate(const double rate, const size_t chan) { - const int coerced_decim = coerce_decim(get_input_rate(chan) / rate); - set_property<int>("decim", coerced_decim, chan); - return _decim.at(chan).get(); + if (_samp_rate_in.at(chan).is_valid()) { + const int coerced_decim = coerce_decim(get_input_rate(chan) / rate); + set_property<int>("decim", coerced_decim, chan); + } else { + RFNOC_LOG_DEBUG( + "Property samp_rate@" + << chan + << " is not valid, attempting to set output rate via the edge property."); + set_property<double>("samp_rate", rate, {res_source_info::OUTPUT_EDGE, chan}); + } + return _samp_rate_out.at(chan).get(); } // Somewhat counter-intuitively, we post a stream command as a message to @@ -191,10 +204,10 @@ private: void _register_props(const size_t chan) { // Create actual properties and store them - _samp_rate_in.push_back(property_t<double>( - PROP_KEY_SAMP_RATE, DEFAULT_RATE, {res_source_info::INPUT_EDGE, chan})); - _samp_rate_out.push_back(property_t<double>( - PROP_KEY_SAMP_RATE, DEFAULT_RATE, {res_source_info::OUTPUT_EDGE, chan})); + _samp_rate_in.push_back( + property_t<double>(PROP_KEY_SAMP_RATE, {res_source_info::INPUT_EDGE, chan})); + _samp_rate_out.push_back( + property_t<double>(PROP_KEY_SAMP_RATE, {res_source_info::OUTPUT_EDGE, chan})); _scaling_in.push_back(property_t<double>( PROP_KEY_SCALING, DEFAULT_SCALING, {res_source_info::INPUT_EDGE, chan})); _scaling_out.push_back(property_t<double>( @@ -239,22 +252,11 @@ private: /********************************************************************** * Add resolvers *********************************************************************/ - // Resolver for the output scaling: This cannot be updated, we reset it - // to its previous value. - add_property_resolver({scaling_out}, - {scaling_out}, - [this, - chan, - &decim = *decim, - &scaling_in = *scaling_in, - &scaling_out = *scaling_out]() { - scaling_out = scaling_in.get() * _residual_scaling.at(chan); - }); // Resolver for _decim: this gets executed when the user directly // modifies _decim. the desired behaviour is to coerce it first, then // keep the input rate constant, and re-calculate the output rate. add_property_resolver({decim}, - {decim, samp_rate_out, scaling_in}, + {decim, samp_rate_out, samp_rate_in, scaling_in}, [this, chan, &decim = *decim, @@ -264,7 +266,11 @@ private: RFNOC_LOG_TRACE("Calling resolver for `decim'@" << chan); decim = coerce_decim(double(decim.get())); set_decim(decim.get(), chan); - samp_rate_out = samp_rate_in.get() / decim.get(); + if (samp_rate_in.is_valid()) { + samp_rate_out = samp_rate_in.get() / decim.get(); + } else if (samp_rate_out.is_valid()) { + samp_rate_in = samp_rate_out.get() * decim.get(); + } scaling_in.force_dirty(); }); // Resolver for _freq: this gets executed when the user directly @@ -272,73 +278,80 @@ private: add_property_resolver( {freq}, {freq}, [this, chan, &samp_rate_in = *samp_rate_in, &freq = *freq]() { RFNOC_LOG_TRACE("Calling resolver for `freq'@" << chan); - freq = _set_freq(freq.get(), samp_rate_in.get(), chan); + if (samp_rate_in.is_valid()) { + const double new_freq = + _set_freq(freq.get(), samp_rate_in.get(), chan); + // If the frequency we just set is sufficiently close to the old + // frequency, don't bother updating the property in software + if (!uhd::math::frequencies_are_equal(new_freq, freq.get())) { + freq = new_freq; + } + } else { + RFNOC_LOG_DEBUG("Not setting frequency until sampling rate is set."); + } }); // Resolver for the input rate: we try and match decim so that the output // rate is not modified. if decim needs to be coerced, only then the // output rate is modified. // Note this will also affect the frequency. - add_property_resolver({samp_rate_in}, - {decim, samp_rate_out, scaling_in, freq}, + add_property_resolver({samp_rate_in, scaling_in}, + {decim, samp_rate_out, freq, scaling_out}, [this, chan, &decim = *decim, &freq = *freq, - &scaling_in = *scaling_in, &samp_rate_out = *samp_rate_out, - &samp_rate_in = *samp_rate_in]() { - RFNOC_LOG_TRACE("Calling resolver for `samp_rate_in'@" << chan); - // If decim changes, it will trigger the decim resolver to run - decim = coerce_decim(samp_rate_in.get() / samp_rate_out.get()); - samp_rate_out = samp_rate_in.get() / decim.get(); - // If the input rate changes, we need to update the DDS, too, - // since it works on frequencies normalized by the input rate. - freq.force_dirty(); + &samp_rate_in = *samp_rate_in, + &scaling_in = *scaling_in, + &scaling_out = *scaling_out]() { + RFNOC_LOG_TRACE( + "Calling resolver for `samp_rate_in/scaling_in'@" << chan); + if (samp_rate_in.is_valid()) { + RFNOC_LOG_TRACE("New samp_rate_in is " << samp_rate_in.get()); + // decim takes priority over samp_rate_out if set + // If decim changes, it will trigger the decim resolver to run + if (not decim.is_valid()) { + decim = coerce_decim(samp_rate_in.get() / samp_rate_out.get()); + } + samp_rate_out = samp_rate_in.get() / decim.get(); + RFNOC_LOG_TRACE("New samp_rate_out is " << samp_rate_out.get()); + // If the input rate changes, we need to update the DDS, too, + // since it works on frequencies normalized by the input rate. + freq.force_dirty(); + } + scaling_out = scaling_in.get() * _residual_scaling.at(chan); }); // Resolver for the output rate: like the previous one, but flipped. - add_property_resolver({samp_rate_out}, - {decim, samp_rate_in}, + add_property_resolver({samp_rate_out, scaling_out}, + {decim, samp_rate_in, scaling_out}, [this, chan, &decim = *decim, - &scaling_in = *scaling_in, &samp_rate_out = *samp_rate_out, - &samp_rate_in = *samp_rate_in]() { - RFNOC_LOG_TRACE("Calling resolver for `samp_rate_out'@" << chan); - decim = coerce_decim(int(samp_rate_in.get() / samp_rate_out.get())); - // If decim is dirty, it will trigger the decim resolver. - // However, the decim resolver will set the output rate based - // on the input rate, so we need to force the input rate first. - if (decim.is_dirty()) { - samp_rate_in = samp_rate_out.get() * decim.get(); + &samp_rate_in = *samp_rate_in, + &scaling_in = *scaling_in, + &scaling_out = *scaling_out]() { + RFNOC_LOG_TRACE( + "Calling resolver for `samp_rate_out/scaling_out'@" << chan); + if (samp_rate_out.is_valid()) { + if (samp_rate_in.is_valid()) { + decim = + coerce_decim(int(samp_rate_in.get() / samp_rate_out.get())); + } + // If decim is dirty, it will trigger the decim resolver. + // However, the decim resolver will set the output rate based + // on the input rate, so we need to force the input rate first. + if (decim.is_dirty()) { + samp_rate_in = samp_rate_out.get() * decim.get(); + } } - }); - // Resolver for the input scaling: When updated, we forward the changes - // to the output scaling. - add_property_resolver({scaling_in}, - {scaling_out}, - [this, chan, &decim = *decim, &scaling_out = *scaling_out]() { - // We don't actually change the value here, because the - // resolution might be not be complete. The resolver for the - // output scaling can take care of things. - scaling_out.force_dirty(); - }); - // Resolver for the output scaling: This cannot be updated, we reset it - // to its previous value. - add_property_resolver({scaling_out}, - {scaling_out}, - [this, - chan, - &decim = *decim, - &scaling_in = *scaling_in, - &scaling_out = *scaling_out]() { scaling_out = scaling_in.get() * _residual_scaling.at(chan); }); // Resolvers for type: These are constants - add_property_resolver({type_in}, {type_in}, [this, &type_in = *type_in]() { + add_property_resolver({type_in}, {type_in}, [&type_in = *type_in]() { type_in.set(IO_TYPE_SC16); }); - add_property_resolver({type_out}, {type_out}, [this, &type_out = *type_out]() { + add_property_resolver({type_out}, {type_out}, [&type_out = *type_out]() { type_out.set(IO_TYPE_SC16); }); } @@ -396,6 +409,7 @@ private: */ void set_decim(int decim, const size_t chan) { + RFNOC_LOG_TRACE("Set decim to " << decim); // Step 1: Calculate number of halfbands uint32_t hb_enable = 0; uint32_t cic_decim = decim; @@ -465,7 +479,7 @@ private: */ int coerce_decim(const double requested_decim) const { - UHD_ASSERT_THROW(requested_decim > 0); + UHD_ASSERT_THROW(requested_decim >= 0); return static_cast<int>(_valid_decims.clip(requested_decim, true)); } diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt index 1cdb42b96..0df2d810c 100644 --- a/host/tests/CMakeLists.txt +++ b/host/tests/CMakeLists.txt @@ -245,17 +245,25 @@ UHD_ADD_NONAPI_TEST( ${CMAKE_SOURCE_DIR}/lib/rfnoc/client_zero.cpp ) +set_source_files_properties( + ${CMAKE_SOURCE_DIR}/lib/utils/system_time.cpp + PROPERTIES COMPILE_DEFINITIONS + "HAVE_MICROSEC_CLOCK" +) UHD_ADD_NONAPI_TEST( TARGET rfnoc_blocks_test.cpp EXTRA_SOURCES ${CMAKE_SOURCE_DIR}/lib/rfnoc/noc_block_base.cpp ${CMAKE_SOURCE_DIR}/lib/rfnoc/block_id.cpp ${CMAKE_SOURCE_DIR}/lib/utils/compat_check.cpp + ${CMAKE_SOURCE_DIR}/lib/utils/system_time.cpp ${CMAKE_SOURCE_DIR}/lib/rfnoc/ddc_block_control.cpp ${CMAKE_SOURCE_DIR}/lib/rfnoc/null_block_control.cpp - ${CMAKE_SOURCE_DIR}/lib/rfnoc/radio_control_impl.cpp ${CMAKE_SOURCE_DIR}/lib/rfnoc/register_iface_holder.cpp ${CMAKE_SOURCE_DIR}/lib/usrp/cores/dsp_core_utils.cpp + ${CMAKE_SOURCE_DIR}/lib/rfnoc/graph.cpp + INCLUDE_DIRS + ${CMAKE_BINARY_DIR}/lib/ic_reg_maps/ ) UHD_ADD_NONAPI_TEST( diff --git a/host/tests/rfnoc_blocks_test.cpp b/host/tests/rfnoc_blocks_test.cpp index 13fc15848..ca82ee305 100644 --- a/host/tests/rfnoc_blocks_test.cpp +++ b/host/tests/rfnoc_blocks_test.cpp @@ -5,12 +5,14 @@ // #include "rfnoc_mock_reg_iface.hpp" -#include <uhd/rfnoc/null_block_control.hpp> -#include <uhd/rfnoc/ddc_block_control.hpp> +#include "rfnoc_graph_mock_nodes.hpp" #include <uhd/rfnoc/actions.hpp> +#include <uhd/rfnoc/ddc_block_control.hpp> +#include <uhd/rfnoc/defaults.hpp> +#include <uhd/rfnoc/null_block_control.hpp> #include <uhdlib/rfnoc/clock_iface.hpp> #include <uhdlib/rfnoc/node_accessor.hpp> -#include <uhdlib/rfnoc/clock_iface.hpp> +#include <uhdlib/rfnoc/graph.hpp> #include <uhdlib/utils/narrow.hpp> #include <boost/test/unit_test.hpp> #include <iostream> @@ -22,7 +24,9 @@ namespace { noc_block_base::make_args_ptr make_make_args(noc_block_base::noc_id_t noc_id, const std::string& block_id, const size_t n_inputs, - const size_t n_outputs) + const size_t n_outputs, + const std::string& tb_clock_name = CLOCK_KEY_GRAPH, + const std::string& cp_clock_name = "MOCK_CLOCK") { auto make_args = std::make_unique<noc_block_base::make_args_t>(); make_args->noc_id = noc_id; @@ -30,8 +34,8 @@ noc_block_base::make_args_ptr make_make_args(noc_block_base::noc_id_t noc_id, make_args->num_output_ports = n_outputs; make_args->reg_iface = std::make_shared<mock_reg_iface_t>(); make_args->block_id = block_id; - make_args->ctrlport_clk_iface = std::make_shared<clock_iface>("MOCK_CLOCK"); - make_args->tb_clk_iface = std::make_shared<clock_iface>("MOCK_CLOCK"); + make_args->ctrlport_clk_iface = std::make_shared<clock_iface>(cp_clock_name); + make_args->tb_clk_iface = std::make_shared<clock_iface>(tb_clock_name); make_args->tree = uhd::property_tree::make(); return make_args; } @@ -141,6 +145,7 @@ BOOST_AUTO_TEST_CASE(test_ddc_block) constexpr uint32_t max_cic = 128; constexpr size_t num_chans = 4; constexpr noc_block_base::noc_id_t mock_noc_id = 0x7E57DDC0; + constexpr int TEST_DECIM = 20; auto ddc_make_args = make_make_args(mock_noc_id, "0/DDC#0", num_chans, num_chans); ddc_make_args->args = uhd::device_addr_t("foo=bar"); @@ -154,10 +159,62 @@ BOOST_AUTO_TEST_CASE(test_ddc_block) node_accessor.init_props(test_ddc.get()); UHD_LOG_DEBUG("TEST", "Init done."); - test_ddc->set_property<int>("decim", 4, 0); + test_ddc->set_property<int>("decim", TEST_DECIM, 0); BOOST_REQUIRE(ddc_reg_iface->write_memory.count(ddc_block_control::SR_DECIM_ADDR)); BOOST_CHECK_EQUAL( - ddc_reg_iface->write_memory.at(ddc_block_control::SR_DECIM_ADDR), 2 << 8 | 1); + ddc_reg_iface->write_memory.at(ddc_block_control::SR_DECIM_ADDR), 2 << 8 | 5); + + // Now plop it in a graph + detail::graph_t graph{}; + detail::graph_t::graph_edge_t edge_info; + edge_info.src_port = 0; + edge_info.dst_port = 0; + edge_info.property_propagation_active = true; + edge_info.edge = detail::graph_t::graph_edge_t::DYNAMIC; + + mock_terminator_t mock_source_term(1); + mock_terminator_t mock_sink_term(1); + + UHD_LOG_INFO("TEST", "Priming mock source node props"); + mock_source_term.set_edge_property<std::string>( + "type", "sc16", {res_source_info::OUTPUT_EDGE, 0}); + mock_source_term.set_edge_property<double>( + "scaling", 1.0, {res_source_info::OUTPUT_EDGE, 0}); + mock_source_term.set_edge_property<double>( + "samp_rate", 1.0, {res_source_info::OUTPUT_EDGE, 0}); + + UHD_LOG_INFO("TEST", "Creating graph"); + graph.connect(&mock_source_term, test_ddc.get(), edge_info); + graph.connect(test_ddc.get(), &mock_sink_term, edge_info); + graph.commit(); + // We need to set the decimation again, because the rates will screw it + // change it w.r.t. to the previous setting + test_ddc->set_property<int>("decim", TEST_DECIM, 0); + BOOST_CHECK_EQUAL(test_ddc->get_property<int>("decim", 0), TEST_DECIM); + BOOST_CHECK(mock_source_term.get_edge_property<double>( + "samp_rate", {res_source_info::OUTPUT_EDGE, 0}) + == mock_sink_term.get_edge_property<double>( + "samp_rate", {res_source_info::INPUT_EDGE, 0}) + * TEST_DECIM); + BOOST_CHECK(mock_sink_term.get_edge_property<double>( + "scaling", {res_source_info::INPUT_EDGE, 0}) + != 1.0); + + UHD_LOG_INFO("TEST", "Setting freq to 1/8 of input rate"); + constexpr double TEST_FREQ = 1.0/8; + test_ddc->set_property<double>("freq", TEST_FREQ, 0); + const uint32_t freq_word_1 = + ddc_reg_iface->write_memory.at(ddc_block_control::SR_FREQ_ADDR); + BOOST_REQUIRE(freq_word_1 != 0); + UHD_LOG_INFO("TEST", "Doubling input rate (to 2.0)"); + // Now this should change the freq word, but not the absolute frequency + mock_source_term.set_edge_property<double>( + "samp_rate", 2.0, {res_source_info::OUTPUT_EDGE, 0}); + const double freq_word_2 = + ddc_reg_iface->write_memory.at(ddc_block_control::SR_FREQ_ADDR); + // The frequency word is the phase increment, which will halve. We skirt + // around fixpoint/floating point accuracy issues by using CLOSE. + BOOST_CHECK_CLOSE(double(freq_word_1) / double(freq_word_2), 2.0, 1e-6); } diff --git a/host/tests/rfnoc_graph_mock_nodes.hpp b/host/tests/rfnoc_graph_mock_nodes.hpp index 2137e3336..63e8bc534 100644 --- a/host/tests/rfnoc_graph_mock_nodes.hpp +++ b/host/tests/rfnoc_graph_mock_nodes.hpp @@ -10,6 +10,7 @@ #include <uhd/rfnoc/defaults.hpp> #include <uhd/rfnoc/node.hpp> #include <uhd/types/stream_cmd.hpp> +#include <uhdlib/rfnoc/node_accessor.hpp> using namespace uhd::rfnoc; @@ -350,4 +351,64 @@ private: const size_t _num_ports; }; +/*! Terminator: Probe edge properties + */ +class mock_terminator_t : public node_t +{ +public: + static size_t counter; + + mock_terminator_t(const size_t num_ports) + : _num_ports(num_ports), _term_count(counter++) + { + set_prop_forwarding_policy(forwarding_policy_t::DROP); + set_action_forwarding_policy(forwarding_policy_t::DROP); + } + + std::string get_unique_id() const + { + return "MOCK_TERMINATOR" + std::to_string(_term_count); + } + + size_t get_num_input_ports() const + { + return _num_ports; + } + + size_t get_num_output_ports() const + { + return _num_ports; + } + + template <typename data_t> + void set_edge_property(const std::string& id, data_t val, res_source_info edge_info) + { + UHD_ASSERT_THROW(edge_info.type == res_source_info::INPUT_EDGE + || edge_info.type == res_source_info::OUTPUT_EDGE); + try { + set_property<data_t>(id, val, edge_info); + } catch (const uhd::lookup_error&) { + node_accessor_t node_accessor{}; + auto edge_info_inverted = edge_info; + edge_info_inverted.type = res_source_info::invert_edge(edge_info.type); + property_t<data_t> new_prop(id, val, edge_info_inverted); + node_accessor.forward_edge_property(this, edge_info.instance, &new_prop); + set_property<data_t>(id, val, edge_info); + } + } + + template <typename data_t> + data_t get_edge_property(const std::string& id, res_source_info edge_info) + { + UHD_ASSERT_THROW(edge_info.type == res_source_info::INPUT_EDGE + || edge_info.type == res_source_info::OUTPUT_EDGE); + return get_property<data_t>(id, edge_info); + } + +private: + const size_t _num_ports; + const size_t _term_count; +}; +size_t mock_terminator_t::counter = 0; + #endif /* INCLUDED_LIBUHD_TESTS_MOCK_NODES_HPP */ |