USRP Hardware Driver and Device Manual  Version: 4.11.0.0-0-g0d7ed3b1
UHD and USRP Manual
uhd::rfnoc::duc_block_control Class Referenceabstract

#include <uhd/rfnoc/duc_block_control.hpp>

Inheritance diagram for uhd::rfnoc::duc_block_control:
uhd::rfnoc::noc_block_base uhd::rfnoc::node_t uhd::rfnoc::register_iface_holder

Detailed Description

DUC Block Control Class

Overview

The DUC is a multi-channel digital upconverter with a built-in DDS frequency shifter. It is commonly placed directly before a radio block to increase the sampling rate and shift a signal to the desired transmit frequency.

The block processes signed complex 16-bit samples (sc16). Each channel has an independent frequency shift and interpolation setting. The number of channels and the maximum supported interpolation are configured in the FPGA at synthesis time. The block controller reads these maximum capabilities, stored in capability registers, during initialization.

Features

  • Per-channel frequency shifting with optional timed commands.
  • Integer interpolation using a cascade of half-band filters followed by a CIC interpolator.
  • IQ scaling to compensate for the gain of the interpolation filters.
  • Independent rate and frequency configuration for every channel.

Theory of Operation

The total interpolation is the product of the CIC interpolation and the half-band interpolation stages. With NUM_HB half-band stages and a CIC limit of CIC_MAX_INTERP, the maximum supported interpolation is \(\mathrm{CIC\_MAX\_INTERP} \times 2^{\mathrm{NUM\_HB}}\).

FPGA Compile-Time Configuration

The DUC FPGA block is configured through the following parameters:

  • NUM_PORTS: Number of independent DUC channels.
  • NUM_HB: Number of half-band filter stages in each channel.
  • CIC_MAX_INTERP: Maximum interpolation through CIC filter stage in each channel.
  • NIPC: Number of samples processed per clock cycle. NIPC == 1 selects the legacy single-sample implementation. Values greater than one select the multisample implementation, which processes samples in parallel for wideband images.

The in-tree DUC YAML descriptor derives NIPC from the configured RF bandwidth. Each parallel processing chain is sized for approximately 200 MHz of RF bandwidth, with the resulting value rounded up to a power of two. Customized FPGA images may choose a different value based on their clock rates and resource budget.

Runtime Configuration

This block exposes two user-configurable configuration parameters per channel:

  • freq: Frequency shift in Hz. The set_freq() convenience method should be preferred over setting the property directly because it also handles a command time.
  • interp: Integer interpolation value.

The interpolation value can be changed at runtime, but a change is applied only between bursts. Once an interpolation value has been applied, it remains in effect for the entire burst.

The legacy single-sample DUC and the multisample (wideband) DUC react differently to timed and untimed frequency-shift commands:

  • Legacy single-sample implementation:
    • Untimed commands: Configure the frequency shift for the next IQ sample processed by the DUC and all subsequent samples until the next frequency change. If the DUC has not processed data since the command was issued, a subsequent untimed command overwrites the earlier one.
    • Timed commands: Follow the standard timed-command mechanism. The command is held until the IQ sample corresponding to its timestamp is processed, and the new frequency shift applies to that sample and all subsequent samples until the next frequency change.
  • Multisample (wideband) implementation: Timed and untimed commands are stored in the same 32-entry FPGA command queue. Commands are applied only when the DUC is actively processing data, and only the command at the front of the queue can be applied.
    • Untimed commands: An untimed command waits for all commands already ahead of it in the queue, including timed commands. It is then applied on the next data transfer. Consequently, successive untimed commands are not collapsed or skipped; each remains active for at least one data transfer containing NIPC IQ samples.
    • Timed commands: A timed command waits in the queue until it reaches the front and its requested timestamp has been reached. It is applied starting with the data transfer containing the IQ sample corresponding to that timestamp and remains active until the next frequency change.

In the multisample implementation, timed frequency shifts have word-level rather than sample-level granularity. One word is one data transfer containing NIPC samples (see NIPC). Therefore, if the requested timestamp falls within a word, the new frequency shift will be applied to samples earlier in that same word. For example, if the timestamp corresponds to the third sample in a word of eight samples, the new frequency shift will be applied to all eight samples in that word, including the first two samples preceding the requested timestamp, and to all subsequent samples until the next frequency change. The exact sample-level transition is not guaranteed.

Register Maps and Compatibility

The DUC has two register-map generations:

  • Compatibility major 0 is the legacy single-sample register map, used by implementations for bandwidths up to 200 MHz.
  • Compatibility major 1 is the multisample register map, used by the wideband implementation.

The block controller reads the FPGA compatibility number from REG_COMPAT_NUM and selects the corresponding register map. An unsupported major version is rejected. REG_ADDRS_V0 and REG_ADDRS_V1 contain the version-specific addresses used by the controller.

Classes

struct  reg_addrs_t
 Version-specific register addresses. More...
 

Public Member Functions

virtual double set_freq (const double freq, const size_t chan, const std::optional< uhd::time_spec_t > time={})=0
 
double set_freq (const double freq, const size_t chan, const uhd::time_spec_t time)
 
virtual double set_freq (const double freq, const size_t chan, const boost::optional< uhd::time_spec_t > time)
 
virtual double get_freq (const size_t chan) const =0
 
virtual uhd::freq_range_t get_frequency_range (const size_t chan) const =0
 
virtual double get_input_rate (const size_t chan) const =0
 
virtual double get_output_rate (const size_t chan) const =0
 
virtual void set_output_rate (const double rate, const size_t chan)=0
 
virtual uhd::meta_range_t get_input_rates (const size_t chan) const =0
 
virtual double set_input_rate (const double rate, const size_t chan)=0
 
- Public Member Functions inherited from uhd::rfnoc::noc_block_base
 ~noc_block_base () override
 
std::string get_unique_id () const override
 Unique ID for an RFNoC block is its block ID. More...
 
size_t get_num_input_ports () const override
 
size_t get_num_output_ports () const override
 
noc_id_t get_noc_id () const
 
const block_id_t & get_block_id () const
 
double get_tick_rate () const
 
size_t get_mtu (const res_source_info &edge)
 
size_t get_chdr_hdr_len (const bool account_for_ts=true) const
 
size_t get_max_payload_size (const res_source_info &edge, const bool account_for_ts=true)
 
uhd::device_addr_t get_block_args () const
 
uhd::property_tree::sptr & get_tree () const
 Return a reference to this block's subtree. More...
 
uhd::property_tree::sptr & get_tree ()
 Return a reference to this block's subtree (non-const version) More...
 
std::shared_ptr< mb_controller > get_mb_controller ()
 
- Public Member Functions inherited from uhd::rfnoc::node_t
 node_t ()
 
virtual ~node_t ()
 
std::vector< std::string > get_property_ids () const
 
template<typename prop_data_t >
void set_property (const std::string &id, const prop_data_t &val, const size_t instance=0)
 
void set_properties (const uhd::device_addr_t &props, const size_t instance=0)
 
template<typename prop_data_t >
const prop_data_t & get_property (const std::string &id, const size_t instance=0)
 
virtual void set_command_time (uhd::time_spec_t time, const size_t instance)
 
virtual uhd::time_spec_t get_command_time (const size_t instance) const
 
virtual void clear_command_time (const size_t instance)
 
- Public Member Functions inherited from uhd::rfnoc::register_iface_holder
 register_iface_holder (register_iface::sptr reg)
 
virtual ~register_iface_holder ()=default
 
register_iface & regs ()
 

Static Public Attributes

static const uint32_t REG_COMPAT_NUM
 
static const reg_addrs_t REG_ADDRS_V0
 
static const reg_addrs_t REG_ADDRS_V1
 
- Static Public Attributes inherited from uhd::rfnoc::node_t
static const size_t ANY_PORT = size_t(~0)
 

Additional Inherited Members

- Public Types inherited from uhd::rfnoc::noc_block_base
using sptr = std::shared_ptr< noc_block_base >
 
using make_args_ptr = std::unique_ptr< make_args_int_t, make_args_deleter >
 Opaque pointer to the constructor arguments with custom deleter. More...
 
- Public Types inherited from uhd::rfnoc::node_t
enum class  action_mode_t { SYNC , ASYNC }
 Action execution modes. More...
 
enum class  forwarding_policy_t {
  ONE_TO_ONE , ONE_TO_FAN , ONE_TO_ALL_IN , ONE_TO_ALL_OUT ,
  ONE_TO_ALL , DROP , USE_MAP
}
 Types of property/action forwarding for those not defined by the block itself. More...
 
using resolver_fn_t = std::function< void(void)>
 
using resolve_callback_t = std::function< void(void)>
 
using graph_mutex_callback_t = std::function< std::recursive_mutex &(void)>
 
using action_handler_t = std::function< void(const res_source_info &, action_info::sptr)>
 
using post_action_handler_t = std::function< void(const res_source_info &, action_info::sptr, action_mode_t)>
 
using forwarding_map_t = std::unordered_map< res_source_info, std::vector< res_source_info > >
 
- Protected Types inherited from uhd::rfnoc::node_t
using prop_ptrs_t = std::vector< property_base_t * >
 
- Protected Member Functions inherited from uhd::rfnoc::noc_block_base
 noc_block_base (make_args_ptr make_args)
 
void set_num_input_ports (const size_t num_ports)
 
void set_num_output_ports (const size_t num_ports)
 
void set_tick_rate (const double tick_rate)
 
void set_mtu_forwarding_policy (const forwarding_policy_t policy)
 
void set_mtu (const res_source_info &edge, const size_t new_mtu)
 
property_base_t * get_mtu_prop_ref (const res_source_info &edge)
 
virtual void deinit ()
 
- Protected Member Functions inherited from uhd::rfnoc::node_t
void register_property (property_base_t *prop, resolve_callback_t &&clean_callback=nullptr)
 
void add_property_resolver (prop_ptrs_t &&inputs, prop_ptrs_t &&outputs, resolver_fn_t &&resolver_fn)
 
void set_prop_forwarding_policy (forwarding_policy_t policy, const std::string &prop_id="")
 
void set_prop_forwarding_map (const forwarding_map_t &map)
 
template<typename prop_data_t >
void set_property (const std::string &id, const prop_data_t &val, const res_source_info &src_info)
 
template<typename prop_data_t >
const prop_data_t & get_property (const std::string &id, const res_source_info &src_info)
 
void register_action_handler (const std::string &id, action_handler_t &&handler)
 
void set_action_forwarding_policy (forwarding_policy_t policy, const std::string &action_key="")
 
void set_action_forwarding_map (const forwarding_map_t &map)
 
void post_action (const res_source_info &edge_info, action_info::sptr action, action_mode_t mode=action_mode_t::SYNC)
 
virtual bool check_topology (const std::vector< size_t > &connected_inputs, const std::vector< size_t > &connected_outputs)
 
- Protected Member Functions inherited from uhd::rfnoc::register_iface_holder
void update_reg_iface (register_iface::sptr new_iface=nullptr)
 
- Static Protected Attributes inherited from uhd::rfnoc::node_t
static dirtifier_t ALWAYS_DIRTY
 A dirtifyer object, useful for properties that always need updating. More...
 

Member Function Documentation

◆ get_freq()

virtual double uhd::rfnoc::duc_block_control::get_freq ( const size_t  chan) const
pure virtual

Return the current DDS frequency

Returns
The current frequency of the DDS

◆ get_frequency_range()

virtual uhd::freq_range_t uhd::rfnoc::duc_block_control::get_frequency_range ( const size_t  chan) const
pure virtual

Return the range of frequencies that 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().

Returns
The range of frequencies that the DUC can shift the input by

◆ get_input_rate()

virtual double uhd::rfnoc::duc_block_control::get_input_rate ( const size_t  chan) const
pure virtual

Return the sampling rate at this block's input

Parameters
chanThe channel for which the rate is being queried
Returns
the sampling rate at this block's input

◆ get_input_rates()

virtual uhd::meta_range_t uhd::rfnoc::duc_block_control::get_input_rates ( const size_t  chan) const
pure virtual

Return a range of valid input rates, based on the current output rate

Note the return value is only valid as long as the output rate does not change.

◆ get_output_rate()

virtual double uhd::rfnoc::duc_block_control::get_output_rate ( const size_t  chan) const
pure virtual

Return the sampling rate at this block's output

This is equivalent to calling get_input_rate() multiplied by the interpolation

Parameters
chanThe channel for which the rate is being queried
Returns
the sampling rate at this block's input

◆ set_freq() [1/3]

virtual double uhd::rfnoc::duc_block_control::set_freq ( const double  freq,
const size_t  chan,
const boost::optional< uhd::time_spec_t >  time 
)
inlinevirtual

◆ set_freq() [2/3]

virtual double uhd::rfnoc::duc_block_control::set_freq ( const double  freq,
const size_t  chan,
const std::optional< uhd::time_spec_t >  time = {} 
)
pure virtual

Set the DDS frequency

This block applies the frequency shift after interpolation. The frequency is specified in Hz rather than as a normalized frequency; the valid range is from -get_output_rate()/2 to +get_output_rate()/2.

Note: When the sample rate is modified, the frequency shift is kept constant. Because the FPGA internally uses a relative phase increment, changing the input sampling rate will trigger a property propagation to recalculate the phase increment based off of this value.

For the multisample implementation, timed and untimed frequency commands are queued in a 32-entry FPGA queue. While the DUC is actively processing data, queued commands are processed one at a time; an untimed command is applied on the next transfer, while a timed command also waits for its requested timestamp.

This function will coerce the frequency to a valid value, and return the coerced value.

Parameters
freqThe frequency shift in Hz
chanThe channel to which this change shall be applied
timeWhen to apply the new frequency
Returns
The coerced, actual current frequency of the DDS

◆ set_freq() [3/3]

double uhd::rfnoc::duc_block_control::set_freq ( const double  freq,
const size_t  chan,
const uhd::time_spec_t  time 
)
inline

◆ set_input_rate()

virtual double uhd::rfnoc::duc_block_control::set_input_rate ( const double  rate,
const size_t  chan 
)
pure virtual

Attempt to set the input rate of this block

This will set the interpolation such that the output rate is untouched, and that the output rate divided by the new interpolation is as close as possible to the requested rate.

Parameters
rateThe requested rate
chanThe channel for which the rate is being queried
Returns
the coerced sampling rate at this block's output

◆ set_output_rate()

virtual void uhd::rfnoc::duc_block_control::set_output_rate ( const double  rate,
const size_t  chan 
)
pure virtual

Manually set the sampling rate at this block's output

Parameters
rateThe requested rate
chanThe channel for which the rate is being set

Member Data Documentation

◆ REG_ADDRS_V0

const reg_addrs_t uhd::rfnoc::duc_block_control::REG_ADDRS_V0
static

◆ REG_ADDRS_V1

const reg_addrs_t uhd::rfnoc::duc_block_control::REG_ADDRS_V1
static

◆ REG_COMPAT_NUM

const uint32_t uhd::rfnoc::duc_block_control::REG_COMPAT_NUM
static

The documentation for this class was generated from the following file: