rfnoc: duc: Fix frequency range for DUC block

The tuning range of the DUC depends on the output sample rate (which is
larger), but it was using the input sample rate. This was causing a bug
where for Tx, the DSP tuning range was limited when using multi_usrp API,
and thus would not allow to DSP-tune beyond the current sampling rate.

In this patch, we also re-use the existing calculation of the sampling
rate, and harmonize that code between duc_block_control and
ddc_block_control.

Consider the following Python REPL code:

>>> import uhd
>>> U = uhd.usrp.MultiUSRP('type=x300')
>>> U.set_rx_rate(10e6)
>>> U.set_tx_rate(10e6)
>>> # Creating a streaming is required, or the input rate will not be
>>> # set:
>>> S = U.get_tx_stream(uhd.usrp.StreamArgs("fc32", "sc16"))
>>> treq = uhd.types.TuneRequest(1e9)
>>> treq.rf_freq = 1e9
>>> treq.dsp_freq = 50e6
>>> treq.dsp_freq_policy = uhd.types.TuneRequestPolicy.manual
>>> treq.rf_freq_policy = uhd.types.TuneRequestPolicy.manual
>>> tres = U.set_rx_freq(treq, 0)
>>> print(str(tres))
Tune Result:
Target RF  Freq: 1000.000000 (MHz)
Actual RF  Freq: 1000.000000 (MHz)
Target DSP Freq: 50.000000 (MHz)
Actual DSP Freq: 5.000000 (MHz)
>>> # Note the last two lines: The *target* DSP freq was already clipped
>>> # to 5 MHz. These lines show 50.0 MHz when this patch is applied.

This bugfix is accompanied some related changes:
- The unit test is amended to verify the behaviour
- The API documentation is amended with details on its behaviour
This commit is contained in:
Martin Braun 2021-08-24 11:45:31 +02:00 committed by Aaron Rossetto
parent 062745a771
commit 1c2fd53311
4 changed files with 32 additions and 13 deletions

View File

@ -77,6 +77,13 @@ public:
virtual double get_freq(const size_t chan) const = 0;
/*! Return the range of frequencies that \p chan can be set to.
*
* The frequency shifter is the last component in the DUC, and thus can
* shift frequencies (digitally) between -get_output_rate()/2
* and +get_output_rate()/2.
*
* The returned values are in Hz (not normalized frequencies) and are valid
* inputs for set_freq().
*
* \return The range of frequencies that the DUC can shift the input by
*/

View File

@ -126,8 +126,7 @@ public:
uhd::freq_range_t get_frequency_range(const size_t chan) const override
{
const double input_rate =
_samp_rate_in.at(chan).is_valid() ? _samp_rate_in.at(chan).get() : 1.0;
const double input_rate = get_input_rate(chan);
// TODO add steps
return uhd::freq_range_t(-input_rate / 2, input_rate / 2);
}

View File

@ -123,10 +123,9 @@ public:
uhd::freq_range_t get_frequency_range(const size_t chan) const override
{
const double input_rate =
_samp_rate_in.at(chan).is_valid() ? _samp_rate_in.at(chan).get() : 1.0;
const double output_rate = get_output_rate(chan);
// TODO add steps
return uhd::freq_range_t(-input_rate / 2, input_rate / 2);
return uhd::freq_range_t(-output_rate / 2, output_rate / 2);
}
double get_input_rate(const size_t chan) const override
@ -241,7 +240,7 @@ private:
* Add resolvers
*********************************************************************/
// Resolver for _interp: this gets executed when the user directly
// modifies interp. the desired behaviour is to coerce it first, then
// modifies 'interp'. The desired behaviour is to coerce it first, then
// keep the output rate constant, and re-calculate the input rate.
add_property_resolver({interp, scaling_in},
{interp, samp_rate_out, samp_rate_in, scaling_in},

View File

@ -34,6 +34,7 @@ BOOST_AUTO_TEST_CASE(test_duc_block)
constexpr size_t num_chans = 4;
constexpr noc_id_t noc_id = DUC_BLOCK;
constexpr int TEST_INTERP = 20; // 2 halfbands, CIC==5
constexpr double DEFAULT_RATE = 200e6; // Matches typical MCR of X310
auto block_container = get_mock_block(noc_id, num_chans, num_chans);
auto& duc_reg_iface = block_container.reg_iface;
@ -70,14 +71,21 @@ BOOST_AUTO_TEST_CASE(test_duc_block)
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});
"samp_rate", DEFAULT_RATE / TEST_INTERP, {res_source_info::OUTPUT_EDGE, 0});
UHD_LOG_INFO("TEST", "Priming mock sink node props");
mock_sink_term.set_edge_property<std::string>(
"type", "sc16", {res_source_info::INPUT_EDGE, 0});
mock_sink_term.set_edge_property<double>(
"scaling", 1.0, {res_source_info::INPUT_EDGE, 0});
mock_sink_term.set_edge_property<double>(
"samp_rate", 1.0, {res_source_info::INPUT_EDGE, 0});
"samp_rate", DEFAULT_RATE, {res_source_info::INPUT_EDGE, 0});
#define CHECK_OUTPUT_RATE(req_rate) \
BOOST_REQUIRE_CLOSE(mock_sink_term.get_edge_property<double>( \
"samp_rate", {res_source_info::INPUT_EDGE, 0}), \
req_rate, \
1e-6);
UHD_LOG_INFO("TEST", "Creating graph...");
graph.connect(&mock_source_term, test_duc.get(), edge_info);
@ -85,6 +93,7 @@ BOOST_AUTO_TEST_CASE(test_duc_block)
UHD_LOG_INFO("TEST", "Committing graph...");
graph.commit();
UHD_LOG_INFO("TEST", "Commit complete.");
CHECK_OUTPUT_RATE(DEFAULT_RATE);
// We need to set the interpation again, because the rates will screw it
// change it w.r.t. to the previous setting
test_duc->set_property<int>("interp", TEST_INTERP, 0);
@ -94,6 +103,8 @@ BOOST_AUTO_TEST_CASE(test_duc_block)
* TEST_INTERP
== mock_sink_term.get_edge_property<double>(
"samp_rate", {res_source_info::INPUT_EDGE, 0}));
// Output rate should remain unchanged
CHECK_OUTPUT_RATE(DEFAULT_RATE);
const double initial_input_scaling = mock_source_term.get_edge_property<double>(
"scaling", {res_source_info::OUTPUT_EDGE, 0});
const double initial_output_scaling = mock_sink_term.get_edge_property<double>(
@ -122,25 +133,28 @@ BOOST_AUTO_TEST_CASE(test_duc_block)
"scaling", {res_source_info::OUTPUT_EDGE, 0});
BOOST_CHECK_EQUAL(doubled_input_scaling, 2 * initial_input_scaling);
UHD_LOG_INFO("TEST", "Setting freq to 1/8 of input rate");
constexpr double TEST_FREQ = 1.0 / 8;
BOOST_CHECK_CLOSE(test_duc->get_frequency_range(0).start(), -DEFAULT_RATE / 2, 1e-6);
BOOST_CHECK_CLOSE(test_duc->get_frequency_range(0).stop(), DEFAULT_RATE / 2, 1e-6);
UHD_LOG_INFO("TEST",
"Setting freq to 1/8 of input rate (to " << (DEFAULT_RATE / 8) / 1e6 << " MHz)");
constexpr double TEST_FREQ = DEFAULT_RATE / 8;
test_duc->set_property<double>("freq", TEST_FREQ, 0);
const uint32_t freq_word_1 =
duc_reg_iface->write_memory.at(duc_block_control::SR_FREQ_ADDR);
BOOST_REQUIRE(freq_word_1 != 0);
UHD_LOG_INFO("TEST", "Doubling input rate (to 2.0)");
UHD_LOG_INFO(
"TEST", "Doubling input rate (to " << (DEFAULT_RATE / 4) / 1e6 << " MHz)");
// Now this should change the freq word, but not the absolute frequency
mock_sink_term.set_edge_property<double>("samp_rate",
2
* mock_sink_term.get_edge_property<double>(
"samp_rate", {res_source_info::INPUT_EDGE, 0}),
"samp_rate", {res_source_info::INPUT_EDGE, 0}),
{res_source_info::INPUT_EDGE, 0});
const double freq_word_2 =
duc_reg_iface->write_memory.at(duc_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);
// Reset the interpolation
test_duc->set_property<int>("interp", TEST_INTERP, 0);
BOOST_REQUIRE_EQUAL(test_duc->get_property<int>("interp", 0), TEST_INTERP);