mirror of
https://github.com/EttusResearch/uhd.git
synced 2026-10-25 17:36:10 +00:00
host: dsp: add wideband ddc and duc rfnoc blocks
Updated the host related files to support wideband DxC block variants. The singlesample and multisample variants both have the same API, but internally they are using different register address spaces to interact with the FPGA implementation. The host implementation checks which implementation a RFNoC DxC block is using by checking the block version number and will then automatically choose the correct register address scheme. This is all transparent to the user, the Block API stays the same. Co-authored-by: Niels Garibaldi <niels.steffen.garibaldi@emerson.com> Co-authored-by: André Zoch <andre.zoch@emerson.com> Co-authored-by: Meghana Halladamath <meghana.halladamath@emerson.com> Co-authored-by: Wade Fife <wade.fife@ettus.com> Co-authored-by: Max Köhler <max.koehler@emerson.com>
This commit is contained in:
parent
b4aeb78438
commit
e466f427fd
@ -14,6 +14,12 @@ parameters:
|
||||
NUM_PORTS: 1
|
||||
NUM_HB: 3
|
||||
CIC_MAX_DECIM: 255
|
||||
# NIPC is calculated based on RF_BW, which is expected to be provided by the
|
||||
# parent image YAML file. The formula rounds NIPC up to the nearest power of
|
||||
# 2 that can support the given RF_BW, assuming each of the NIPC parallel
|
||||
# chains supports 200 MHz of RF bandwidth (with a corresponding sampling rate
|
||||
# of 250 MSps). If RF_BW is not provided or is zero, NIPC defaults to 1.
|
||||
NIPC: "${ 1 << ((-(-int(config.device.parameters.get('RF_BW', 200) // 200)) - 1).bit_length()) }"
|
||||
|
||||
clocks:
|
||||
- name: rfnoc_chdr
|
||||
@ -40,7 +46,7 @@ data:
|
||||
in:
|
||||
num_ports: NUM_PORTS
|
||||
item_width: 32
|
||||
nipc: 1
|
||||
nipc: NIPC
|
||||
info_fifo_depth: 32
|
||||
payload_fifo_depth: 32
|
||||
format: sc16
|
||||
@ -48,7 +54,7 @@ data:
|
||||
out:
|
||||
num_ports: NUM_PORTS
|
||||
item_width: 32
|
||||
nipc: 1
|
||||
nipc: NIPC
|
||||
info_fifo_depth: 32
|
||||
payload_fifo_depth: 2**MTU
|
||||
sideband_at_end: True
|
||||
|
||||
@ -14,6 +14,12 @@ parameters:
|
||||
NUM_PORTS: 1
|
||||
NUM_HB: 3
|
||||
CIC_MAX_INTERP: 255
|
||||
# NIPC is calculated based on RF_BW, which is expected to be provided by the
|
||||
# parent image YAML file. The formula rounds NIPC up to the nearest power of
|
||||
# 2 that can support the given RF_BW, assuming each of the NIPC parallel
|
||||
# chains supports 200 MHz of RF bandwidth (with a corresponding sampling rate
|
||||
# of 250 MSps). If RF_BW is not provided or is zero, NIPC defaults to 1.
|
||||
NIPC: "${ 1 << ((-(-int(config.device.parameters.get('RF_BW', 200) // 200)) - 1).bit_length()) }"
|
||||
|
||||
clocks:
|
||||
- name: rfnoc_chdr
|
||||
@ -40,7 +46,7 @@ data:
|
||||
in:
|
||||
num_ports: NUM_PORTS
|
||||
item_width: 32
|
||||
nipc: 1
|
||||
nipc: NIPC
|
||||
info_fifo_depth: 32
|
||||
payload_fifo_depth: 32
|
||||
format: sc16
|
||||
@ -48,7 +54,7 @@ data:
|
||||
out:
|
||||
num_ports: NUM_PORTS
|
||||
item_width: 32
|
||||
nipc: 1
|
||||
nipc: NIPC
|
||||
info_fifo_depth: 32
|
||||
payload_fifo_depth: 2**MTU
|
||||
sideband_at_end: True
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include <uhd/rfnoc/noc_block_base.hpp>
|
||||
#include <uhd/types/ranges.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <optional>
|
||||
|
||||
namespace uhd { namespace rfnoc {
|
||||
|
||||
@ -33,22 +34,38 @@ class UHD_API ddc_block_control : public noc_block_base
|
||||
public:
|
||||
RFNOC_DECLARE_BLOCK(ddc_block_control)
|
||||
|
||||
static const uint16_t MAJOR_COMPAT;
|
||||
static const uint16_t MINOR_COMPAT;
|
||||
// Readback addresses
|
||||
//! Version-specific register map for the DDC block
|
||||
struct reg_addrs_t
|
||||
{
|
||||
uint16_t major_compat; //!< Expected major compat number for this version
|
||||
uint16_t minor_compat; //!< Expected minor compat number for this version
|
||||
// Readback addresses
|
||||
uint32_t rb_num_hb; //!< Address of num-halfbands readback register
|
||||
uint32_t rb_cic_max_decim; //!< Address of max-CIC-decimation readback register
|
||||
//! Address of samples-per-clock readback register; std::nullopt for V0
|
||||
std::optional<uint32_t> rb_spc;
|
||||
// Per-channel write addresses
|
||||
uint32_t sr_decim_addr; //!< Decimation word
|
||||
uint32_t sr_freq_addr; //!< DDS frequency word
|
||||
uint32_t sr_scale_iq_addr; //!< IQ scale factor
|
||||
std::optional<uint32_t> sr_n_addr; //!< Rate N; std::nullopt for V1
|
||||
std::optional<uint32_t> sr_m_addr; //!< Rate M; std::nullopt for V1
|
||||
uint32_t sr_time_incr_addr; //!< Timestamp increment
|
||||
// Single-sample-only registers (not poked by block controller, exposed for
|
||||
// external callers). std::nullopt for V1.
|
||||
std::optional<uint32_t> sr_config_addr;
|
||||
std::optional<uint32_t> sr_mux_addr;
|
||||
std::optional<uint32_t> sr_coeffs_addr;
|
||||
//! Per-channel register bank stride in bytes (= 2^DDC_PORT_ADDR_W in the RTL)
|
||||
uint32_t reg_chan_offset;
|
||||
};
|
||||
|
||||
//! Compat register address (same across all register map versions)
|
||||
static const uint32_t RB_COMPAT_NUM;
|
||||
static const uint32_t RB_NUM_HB;
|
||||
static const uint32_t RB_CIC_MAX_DECIM;
|
||||
// Write addresses
|
||||
static const uint32_t SR_N_ADDR;
|
||||
static const uint32_t SR_M_ADDR;
|
||||
static const uint32_t SR_CONFIG_ADDR;
|
||||
static const uint32_t SR_FREQ_ADDR;
|
||||
static const uint32_t SR_SCALE_IQ_ADDR;
|
||||
static const uint32_t SR_DECIM_ADDR;
|
||||
static const uint32_t SR_MUX_ADDR;
|
||||
static const uint32_t SR_COEFFS_ADDR;
|
||||
static const uint32_t SR_TIME_INCR_ADDR;
|
||||
//! Register map for the single-sample (legacy) DDC (compat major 0)
|
||||
static const reg_addrs_t REG_ADDRS_V0;
|
||||
//! Register map for the multisample DDC (compat major 1)
|
||||
static const reg_addrs_t REG_ADDRS_V1;
|
||||
|
||||
/*! Set the DDS frequency
|
||||
*
|
||||
|
||||
@ -35,20 +35,28 @@ class UHD_API duc_block_control : public noc_block_base
|
||||
public:
|
||||
RFNOC_DECLARE_BLOCK(duc_block_control)
|
||||
|
||||
static const uint16_t MAJOR_COMPAT;
|
||||
static const uint16_t MINOR_COMPAT;
|
||||
// Readback addresses
|
||||
static const uint32_t RB_COMPAT_NUM;
|
||||
static const uint32_t RB_NUM_HB;
|
||||
static const uint32_t RB_CIC_MAX_INTERP;
|
||||
// Write addresses
|
||||
static const uint32_t SR_N_ADDR;
|
||||
static const uint32_t SR_M_ADDR;
|
||||
static const uint32_t SR_CONFIG_ADDR;
|
||||
static const uint32_t SR_FREQ_ADDR;
|
||||
static const uint32_t SR_SCALE_IQ_ADDR;
|
||||
static const uint32_t SR_INTERP_ADDR;
|
||||
static const uint32_t SR_TIME_INCR_ADDR;
|
||||
//! Version-specific register addresses
|
||||
struct reg_addrs_t
|
||||
{
|
||||
uint16_t major_compat;
|
||||
uint16_t minor_compat;
|
||||
uint32_t num_hb;
|
||||
uint32_t cic_max_interp;
|
||||
std::optional<uint32_t> n_addr;
|
||||
std::optional<uint32_t> m_addr;
|
||||
std::optional<uint32_t> config_addr;
|
||||
uint32_t interp_addr;
|
||||
uint32_t freq_addr;
|
||||
uint32_t scale_iq_addr;
|
||||
uint32_t time_incr_addr;
|
||||
};
|
||||
|
||||
// Compat register address (same across all versions)
|
||||
static const uint32_t REG_COMPAT_NUM;
|
||||
// Register addresses for version 0.x
|
||||
static const reg_addrs_t REG_ADDRS_V0;
|
||||
// Register addresses for version 1.x
|
||||
static const reg_addrs_t REG_ADDRS_V1;
|
||||
|
||||
/*! Set the DDS frequency
|
||||
*
|
||||
|
||||
@ -27,53 +27,109 @@ 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;
|
||||
// Multisample DDC (V1) register address building blocks
|
||||
constexpr uint32_t MS_DDC_BASE = 0x000;
|
||||
constexpr uint32_t MS_PORT_BASE = 0x100;
|
||||
constexpr uint32_t MS_PORT_AXI_RATE = MS_PORT_BASE + 0x000;
|
||||
constexpr uint32_t MS_PORT_SR = MS_PORT_BASE + 0x100;
|
||||
constexpr uint32_t MS_PORT_DDS = MS_PORT_BASE + 0x200;
|
||||
|
||||
constexpr uint32_t MS_PORT_ADDR_W = 11; // 2^11 = 2048 bytes between channels
|
||||
|
||||
// MS shared registers
|
||||
constexpr uint32_t MS_NUM_HB_ADDR = 0x04;
|
||||
constexpr uint32_t MS_CIC_MAX_DECIM_ADDR = 0x08;
|
||||
constexpr uint32_t MS_SPC_ADDR = 0x0C;
|
||||
|
||||
// MS Sample rate conversion registers (per-channel)
|
||||
constexpr uint32_t MS_DECIM_ADDR = 0x00;
|
||||
constexpr uint32_t MS_SCALE_IQ_ADDR = 0x08;
|
||||
|
||||
// MS DDS registers (per-channel)
|
||||
constexpr uint32_t MS_FREQ_ADDR = 0x00;
|
||||
|
||||
// MS AXI rate registers (per-channel)
|
||||
constexpr uint32_t MS_TIME_INCR_ADDR = 0x08;
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
using namespace uhd::rfnoc;
|
||||
|
||||
const uint16_t ddc_block_control::MINOR_COMPAT = 1;
|
||||
const uint16_t ddc_block_control::MAJOR_COMPAT = 0;
|
||||
const uint32_t ddc_block_control::RB_COMPAT_NUM = 0;
|
||||
|
||||
const uint32_t ddc_block_control::RB_COMPAT_NUM = 0; // read this first
|
||||
const uint32_t ddc_block_control::RB_NUM_HB = 8;
|
||||
const uint32_t ddc_block_control::RB_CIC_MAX_DECIM = 16;
|
||||
const ddc_block_control::reg_addrs_t ddc_block_control::REG_ADDRS_V0 = {
|
||||
.major_compat = 0,
|
||||
.minor_compat = 1,
|
||||
.rb_num_hb = 8,
|
||||
.rb_cic_max_decim = 16,
|
||||
.rb_spc = std::nullopt,
|
||||
.sr_decim_addr = 134 * 8,
|
||||
.sr_freq_addr = 132 * 8,
|
||||
.sr_scale_iq_addr = 133 * 8,
|
||||
.sr_n_addr = 128 * 8,
|
||||
.sr_m_addr = 129 * 8,
|
||||
.sr_time_incr_addr = 137 * 8,
|
||||
.sr_config_addr = 130 * 8,
|
||||
.sr_mux_addr = 135 * 8,
|
||||
.sr_coeffs_addr = 136 * 8,
|
||||
.reg_chan_offset = 2048, // legacy SS register bank spacing
|
||||
};
|
||||
|
||||
const uint32_t ddc_block_control::SR_N_ADDR = 128 * 8;
|
||||
const uint32_t ddc_block_control::SR_M_ADDR = 129 * 8;
|
||||
const uint32_t ddc_block_control::SR_CONFIG_ADDR = 130 * 8;
|
||||
const uint32_t ddc_block_control::SR_FREQ_ADDR = 132 * 8;
|
||||
const uint32_t ddc_block_control::SR_SCALE_IQ_ADDR = 133 * 8;
|
||||
const uint32_t ddc_block_control::SR_DECIM_ADDR = 134 * 8;
|
||||
const uint32_t ddc_block_control::SR_MUX_ADDR = 135 * 8;
|
||||
const uint32_t ddc_block_control::SR_COEFFS_ADDR = 136 * 8;
|
||||
const uint32_t ddc_block_control::SR_TIME_INCR_ADDR = 137 * 8;
|
||||
const ddc_block_control::reg_addrs_t ddc_block_control::REG_ADDRS_V1 = {
|
||||
.major_compat = 1,
|
||||
.minor_compat = 0,
|
||||
.rb_num_hb = MS_DDC_BASE + MS_NUM_HB_ADDR,
|
||||
.rb_cic_max_decim = MS_DDC_BASE + MS_CIC_MAX_DECIM_ADDR,
|
||||
.rb_spc = MS_DDC_BASE + MS_SPC_ADDR,
|
||||
.sr_decim_addr = MS_PORT_SR + MS_DECIM_ADDR,
|
||||
.sr_freq_addr = MS_PORT_DDS + MS_FREQ_ADDR,
|
||||
.sr_scale_iq_addr = MS_PORT_SR + MS_SCALE_IQ_ADDR,
|
||||
.sr_n_addr = std::nullopt,
|
||||
.sr_m_addr = std::nullopt,
|
||||
.sr_time_incr_addr = MS_PORT_AXI_RATE + MS_TIME_INCR_ADDR,
|
||||
.sr_config_addr = std::nullopt,
|
||||
.sr_mux_addr = std::nullopt,
|
||||
.sr_coeffs_addr = std::nullopt,
|
||||
.reg_chan_offset = 1 << MS_PORT_ADDR_W,
|
||||
};
|
||||
|
||||
class ddc_block_control_impl : public ddc_block_control
|
||||
{
|
||||
public:
|
||||
RFNOC_BLOCK_CONSTRUCTOR(ddc_block_control)
|
||||
, _ddc_reg_iface(*this, 0, REG_CHAN_OFFSET),
|
||||
_fpga_compat(regs().peek32(RB_COMPAT_NUM)),
|
||||
_num_halfbands(regs().peek32(RB_NUM_HB)),
|
||||
_cic_max_decim(regs().peek32(RB_CIC_MAX_DECIM)),
|
||||
, _fpga_compat(regs().peek32(RB_COMPAT_NUM)),
|
||||
_reg_addrs(_fpga_compat.get_major() == REG_ADDRS_V1.major_compat ? REG_ADDRS_V1
|
||||
: REG_ADDRS_V0),
|
||||
_ddc_reg_iface(*this, 0, _reg_addrs.reg_chan_offset),
|
||||
_num_halfbands(regs().peek32(_reg_addrs.rb_num_hb)),
|
||||
_cic_max_decim(regs().peek32(_reg_addrs.rb_cic_max_decim)),
|
||||
_spc(_reg_addrs.rb_spc ? regs().peek32(*_reg_addrs.rb_spc) : 1),
|
||||
_residual_scaling(get_num_input_ports(), DEFAULT_SCALING)
|
||||
{
|
||||
if (_fpga_compat.get_major() > REG_ADDRS_V1.major_compat) {
|
||||
UHD_LOG_THROW(uhd::runtime_error,
|
||||
get_unique_id(),
|
||||
": Unsupported DDC compat major version: "
|
||||
+ std::to_string(_fpga_compat.get_major()));
|
||||
}
|
||||
|
||||
UHD_ASSERT_THROW(get_num_input_ports() == get_num_output_ports());
|
||||
UHD_ASSERT_THROW(_cic_max_decim > 0 && _cic_max_decim <= 0xFF);
|
||||
uhd::assert_fpga_compat(MAJOR_COMPAT,
|
||||
MINOR_COMPAT,
|
||||
_fpga_compat,
|
||||
uhd::assert_fpga_compat(_reg_addrs.major_compat,
|
||||
_reg_addrs.minor_compat,
|
||||
_fpga_compat.get(),
|
||||
get_unique_id(),
|
||||
get_unique_id(),
|
||||
false /* Let it slide if minors mismatch */
|
||||
);
|
||||
RFNOC_LOG_DEBUG("Loading DDC with " << _num_halfbands
|
||||
<< " halfbands and "
|
||||
"max CIC decimation "
|
||||
<< _cic_max_decim);
|
||||
RFNOC_LOG_DEBUG("Loading DDC with "
|
||||
<< _num_halfbands
|
||||
<< " halfbands and "
|
||||
"max CIC decimation "
|
||||
<< _cic_max_decim
|
||||
<< ", map=" << (_reg_addrs.rb_spc ? "multisample" : "legacy")
|
||||
<< ", SPC=" << _spc);
|
||||
// This line is not strictly necessary, as ONE_TO_ONE is the default.
|
||||
// We set it make it explicit how this block works.
|
||||
set_mtu_forwarding_policy(forwarding_policy_t::ONE_TO_ONE);
|
||||
@ -108,6 +164,35 @@ public:
|
||||
register_issue_tune_request();
|
||||
}
|
||||
|
||||
inline void _poke_decim_word(const uint32_t decim_word, const size_t chan)
|
||||
{
|
||||
_ddc_reg_iface.poke32(_reg_addrs.sr_decim_addr, decim_word, chan);
|
||||
}
|
||||
|
||||
inline void _poke_rate_n_m(const uint32_t n, const uint32_t m, const size_t chan)
|
||||
{
|
||||
if (_reg_addrs.sr_n_addr && _reg_addrs.sr_m_addr) {
|
||||
_ddc_reg_iface.poke32(*_reg_addrs.sr_n_addr, n, chan);
|
||||
_ddc_reg_iface.poke32(*_reg_addrs.sr_m_addr, m, chan);
|
||||
}
|
||||
}
|
||||
|
||||
inline void _poke_time_incr(const uint32_t time_incr, const size_t chan)
|
||||
{
|
||||
_ddc_reg_iface.poke32(_reg_addrs.sr_time_incr_addr, time_incr, chan);
|
||||
}
|
||||
|
||||
inline void _poke_scale_iq(const uint32_t actual_factor, const size_t chan)
|
||||
{
|
||||
_ddc_reg_iface.poke32(_reg_addrs.sr_scale_iq_addr, actual_factor, chan);
|
||||
}
|
||||
|
||||
inline void _poke_freq(
|
||||
const uint32_t freq_word, const size_t chan, const uhd::time_spec_t& cmd_time)
|
||||
{
|
||||
_ddc_reg_iface.poke32(_reg_addrs.sr_freq_addr, freq_word, chan, cmd_time);
|
||||
}
|
||||
|
||||
double set_freq(const double freq,
|
||||
const size_t chan,
|
||||
const std::optional<uhd::time_spec_t> time) override
|
||||
@ -197,6 +282,14 @@ public:
|
||||
issue_stream_cmd_action_handler(dst_edge, new_action);
|
||||
}
|
||||
|
||||
private:
|
||||
// Declared before the protected _ddc_reg_iface so C++ initializes these first,
|
||||
// allowing _reg_addrs.reg_chan_offset to be used in _ddc_reg_iface's construction.
|
||||
//! Block compat number
|
||||
const uhd::compat_num32 _fpga_compat;
|
||||
//! Selected register address map (V0 = legacy, V1 = multisample)
|
||||
const reg_addrs_t _reg_addrs;
|
||||
|
||||
protected:
|
||||
//! Block-specific register interface
|
||||
multichan_register_iface _ddc_reg_iface;
|
||||
@ -210,7 +303,7 @@ private:
|
||||
|
||||
/**************************************************************************
|
||||
* Initialization
|
||||
*************************************************************************/
|
||||
************************************************************************/
|
||||
void _register_props(const size_t chan)
|
||||
{
|
||||
// Create actual properties and store them
|
||||
@ -584,15 +677,13 @@ private:
|
||||
UHD_ASSERT_THROW(hb_enable <= _num_halfbands);
|
||||
UHD_ASSERT_THROW(cic_decim > 0 and cic_decim <= _cic_max_decim);
|
||||
const uint32_t decim_word = (hb_enable << 8) | cic_decim;
|
||||
_ddc_reg_iface.poke32(SR_DECIM_ADDR, decim_word, chan);
|
||||
_poke_decim_word(decim_word, chan);
|
||||
|
||||
// Rate change = M/N
|
||||
_ddc_reg_iface.poke32(SR_N_ADDR, decim, chan);
|
||||
_ddc_reg_iface.poke32(SR_M_ADDR, 1, chan);
|
||||
_poke_rate_n_m(uint32_t(decim), 1, chan);
|
||||
|
||||
// Configure time increment in ticks per M output samples
|
||||
_ddc_reg_iface.poke32(
|
||||
SR_TIME_INCR_ADDR, uint32_t(get_tick_rate() / get_output_rate(chan)), chan);
|
||||
// Configure the time increment for each output word containing _spc samples.
|
||||
_poke_time_incr(uint32_t(get_tick_rate() / get_output_rate(chan) * _spc), chan);
|
||||
|
||||
if (cic_decim > 1 and hb_enable == 0) {
|
||||
RFNOC_LOG_WARNING(
|
||||
@ -606,16 +697,17 @@ private:
|
||||
<< decim);
|
||||
}
|
||||
|
||||
constexpr double DDS_GAIN = 2.0;
|
||||
// The multisample DDS normalizes its NCO internally; the legacy DDS
|
||||
// requires the historical factor-of-two gain correction.
|
||||
const double dds_gain = _reg_addrs.rb_spc ? 1.0 : 2.0;
|
||||
// Calculate algorithmic gain of CIC for a given decimation.
|
||||
// For Ettus CIC R=decim, M=1, N=4. Gain = (R * M) ^ N
|
||||
// The Ettus CIC also tries its best to compensate for the gain by
|
||||
// shifting the CIC output. This reduces the gain by a factor of
|
||||
// 2**ceil(log2(cic_gain))
|
||||
const double cic_gain = std::pow(double(cic_decim * 1), 4);
|
||||
// DDS gain:
|
||||
const double total_gain =
|
||||
DDS_GAIN * cic_gain / std::pow(2, uhd::math::ceil_log2(cic_gain));
|
||||
dds_gain * cic_gain / std::pow(2, uhd::math::ceil_log2(cic_gain));
|
||||
update_scaling(total_gain, chan);
|
||||
}
|
||||
|
||||
@ -633,7 +725,7 @@ private:
|
||||
const int32_t actual_factor = std::lround(target_factor);
|
||||
// Write DDC with scaling correction for CIC and DDS that maximizes
|
||||
// dynamic range
|
||||
_ddc_reg_iface.poke32(SR_SCALE_IQ_ADDR, actual_factor, chan);
|
||||
_poke_scale_iq(uint32_t(actual_factor), chan);
|
||||
|
||||
// Calculate the error introduced by using fixedpoint representation for
|
||||
// the scaler, can be corrected in host later.
|
||||
@ -656,21 +748,23 @@ private:
|
||||
int32_t freq_word;
|
||||
std::tie(actual_freq, freq_word) =
|
||||
get_freq_and_freq_word(requested_freq, dds_rate);
|
||||
|
||||
_ddc_reg_iface.poke32(
|
||||
SR_FREQ_ADDR, uint32_t(freq_word), chan, get_command_time(chan));
|
||||
// Write freq_word directly (not scaled by SPC). The hardware
|
||||
// dds_multisample uses freq_word as the per-sample phase
|
||||
// increment and multiplies internally by SPC for the per-clock
|
||||
// accumulator advance.
|
||||
_poke_freq(uint32_t(freq_word), chan, get_command_time(chan));
|
||||
return actual_freq;
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
* Attributes
|
||||
*************************************************************************/
|
||||
//! Block compat number
|
||||
const uint32_t _fpga_compat;
|
||||
//! Number of halfbands
|
||||
const size_t _num_halfbands;
|
||||
//! Max CIC decim
|
||||
const size_t _cic_max_decim;
|
||||
//! Number of samples per clock as reported by hardware
|
||||
const uint32_t _spc;
|
||||
|
||||
//! List of valid decimation values
|
||||
uhd::meta_range_t _valid_decims;
|
||||
|
||||
@ -28,43 +28,86 @@ constexpr double DEFAULT_FREQ = 0.0;
|
||||
const uhd::rfnoc::io_type_t DEFAULT_TYPE = uhd::rfnoc::IO_TYPE_SC16;
|
||||
constexpr double TX_SIGN = -1.0;
|
||||
|
||||
//! Space (in bytes) between register banks per channel
|
||||
constexpr uint32_t REG_CHAN_OFFSET = 2048;
|
||||
// Multisample DUC (V1) register address building blocks
|
||||
constexpr uint32_t MS_DUC_BASE = 0x000;
|
||||
constexpr uint32_t MS_PORT_BASE = 0x100;
|
||||
// space in bytes between register banks per channel
|
||||
constexpr uint32_t MS_PORT_ADDR_W = 11;
|
||||
constexpr uint32_t MS_CHAN_OFFSET = 1 << MS_PORT_ADDR_W;
|
||||
constexpr uint32_t MS_PORT_AXI_RATE = MS_PORT_BASE + 0x000;
|
||||
constexpr uint32_t MS_PORT_SR = MS_PORT_BASE + 0x100;
|
||||
constexpr uint32_t MS_PORT_DDS = MS_PORT_BASE + 0x200;
|
||||
// MS shared registers (same for all channels)
|
||||
constexpr uint32_t MS_NUM_HB_ADDR = 0x04;
|
||||
constexpr uint32_t MS_CIC_MAX_INTERP_ADDR = 0x08;
|
||||
// MS Sample rate conversion registers (per-channel)
|
||||
constexpr uint32_t MS_INTERP_ADDR = 0x00;
|
||||
constexpr uint32_t MS_SCALE_IQ_ADDR = 0x08;
|
||||
// MS DDS registers (per-channel)
|
||||
constexpr uint32_t MS_FREQ_ADDR = 0x00;
|
||||
// MS AXI rate registers (per-channel)
|
||||
constexpr uint32_t MS_TIME_INCR_ADDR = 0x08;
|
||||
|
||||
} // namespace
|
||||
|
||||
using namespace uhd::rfnoc;
|
||||
|
||||
const uint16_t duc_block_control::MINOR_COMPAT = 1;
|
||||
const uint16_t duc_block_control::MAJOR_COMPAT = 0;
|
||||
// Compat register address (shared across all versions)
|
||||
const uint32_t duc_block_control::REG_COMPAT_NUM = 0;
|
||||
|
||||
const uint32_t duc_block_control::RB_COMPAT_NUM = 0; // read this first
|
||||
const uint32_t duc_block_control::RB_NUM_HB = 8;
|
||||
const uint32_t duc_block_control::RB_CIC_MAX_INTERP = 16;
|
||||
// Version 0.x register addresses
|
||||
const duc_block_control::reg_addrs_t duc_block_control::REG_ADDRS_V0 = {
|
||||
.major_compat = 0,
|
||||
.minor_compat = 1,
|
||||
.num_hb = 8,
|
||||
.cic_max_interp = 16,
|
||||
.n_addr = 128 * 8,
|
||||
.m_addr = 129 * 8,
|
||||
.config_addr = 130 * 8,
|
||||
.interp_addr = 131 * 8,
|
||||
.freq_addr = 132 * 8,
|
||||
.scale_iq_addr = 133 * 8,
|
||||
.time_incr_addr = 137 * 8,
|
||||
};
|
||||
|
||||
const uint32_t duc_block_control::SR_N_ADDR = 128 * 8;
|
||||
const uint32_t duc_block_control::SR_M_ADDR = 129 * 8;
|
||||
const uint32_t duc_block_control::SR_CONFIG_ADDR = 130 * 8;
|
||||
const uint32_t duc_block_control::SR_INTERP_ADDR = 131 * 8;
|
||||
const uint32_t duc_block_control::SR_FREQ_ADDR = 132 * 8;
|
||||
const uint32_t duc_block_control::SR_SCALE_IQ_ADDR = 133 * 8;
|
||||
const uint32_t duc_block_control::SR_TIME_INCR_ADDR = 137 * 8;
|
||||
// Version 1.x register addresses (multisample DUC)
|
||||
const duc_block_control::reg_addrs_t duc_block_control::REG_ADDRS_V1 = {
|
||||
.major_compat = 1,
|
||||
.minor_compat = 0,
|
||||
.num_hb = MS_DUC_BASE + MS_NUM_HB_ADDR,
|
||||
.cic_max_interp = MS_DUC_BASE + MS_CIC_MAX_INTERP_ADDR,
|
||||
.n_addr = std::nullopt,
|
||||
.m_addr = std::nullopt,
|
||||
.config_addr = std::nullopt, // not present in V1
|
||||
.interp_addr = MS_PORT_SR + MS_INTERP_ADDR,
|
||||
.freq_addr = MS_PORT_DDS + MS_FREQ_ADDR,
|
||||
.scale_iq_addr = MS_PORT_SR + MS_SCALE_IQ_ADDR,
|
||||
.time_incr_addr = MS_PORT_AXI_RATE + MS_TIME_INCR_ADDR,
|
||||
};
|
||||
|
||||
class duc_block_control_impl : public duc_block_control
|
||||
{
|
||||
public:
|
||||
RFNOC_BLOCK_CONSTRUCTOR(duc_block_control)
|
||||
, _duc_reg_iface(*this, 0, REG_CHAN_OFFSET),
|
||||
_fpga_compat(regs().peek32(RB_COMPAT_NUM)),
|
||||
_num_halfbands(regs().peek32(RB_NUM_HB)),
|
||||
_cic_max_interp(regs().peek32(RB_CIC_MAX_INTERP)),
|
||||
, _duc_reg_iface(*this, 0, MS_CHAN_OFFSET),
|
||||
_fpga_compat(regs().peek32(REG_COMPAT_NUM)),
|
||||
_reg_addrs(_fpga_compat.get_major() == 1 ? REG_ADDRS_V1 : REG_ADDRS_V0),
|
||||
_num_halfbands(regs().peek32(_reg_addrs.num_hb)),
|
||||
_cic_max_interp(regs().peek32(_reg_addrs.cic_max_interp)),
|
||||
_residual_scaling(get_num_input_ports(), DEFAULT_SCALING)
|
||||
{
|
||||
UHD_ASSERT_THROW(get_num_input_ports() == get_num_output_ports());
|
||||
UHD_ASSERT_THROW(_cic_max_interp > 0 && _cic_max_interp <= 0xFF);
|
||||
uhd::assert_fpga_compat(MAJOR_COMPAT,
|
||||
MINOR_COMPAT,
|
||||
_fpga_compat,
|
||||
RFNOC_LOG_DEBUG("Compat number: " << _fpga_compat.to_string());
|
||||
if (_fpga_compat.get_major() > REG_ADDRS_V1.major_compat) {
|
||||
UHD_LOG_THROW(uhd::runtime_error,
|
||||
get_unique_id(),
|
||||
": Unsupported DUC compat major version: "
|
||||
+ std::to_string(_fpga_compat.get_major()));
|
||||
}
|
||||
uhd::assert_fpga_compat(_reg_addrs.major_compat,
|
||||
_reg_addrs.minor_compat,
|
||||
_fpga_compat.get(),
|
||||
get_unique_id(),
|
||||
get_unique_id(),
|
||||
false /* Let it slide if minors mismatch */
|
||||
@ -580,15 +623,18 @@ private:
|
||||
UHD_ASSERT_THROW(hb_enable <= _num_halfbands);
|
||||
UHD_ASSERT_THROW(cic_interp > 0 and cic_interp <= _cic_max_interp);
|
||||
const uint32_t interp_word = (hb_enable << 8) | cic_interp;
|
||||
_duc_reg_iface.poke32(SR_INTERP_ADDR, interp_word, chan);
|
||||
_duc_reg_iface.poke32(_reg_addrs.interp_addr, interp_word, chan);
|
||||
|
||||
// Rate change = M/N, where N = 1
|
||||
_duc_reg_iface.poke32(SR_M_ADDR, interp, chan);
|
||||
_duc_reg_iface.poke32(SR_N_ADDR, 1, chan);
|
||||
// Legacy rate change = M/N, where N = 1
|
||||
if (_reg_addrs.m_addr && _reg_addrs.n_addr) {
|
||||
_duc_reg_iface.poke32(*_reg_addrs.m_addr, interp, chan);
|
||||
_duc_reg_iface.poke32(*_reg_addrs.n_addr, 1, chan);
|
||||
}
|
||||
|
||||
// Configure time increment in ticks per M output samples
|
||||
_duc_reg_iface.poke32(
|
||||
SR_TIME_INCR_ADDR, uint32_t(get_tick_rate() / get_output_rate(chan)), chan);
|
||||
// Configure the time increment for each output sample
|
||||
_duc_reg_iface.poke32(_reg_addrs.time_incr_addr,
|
||||
uint32_t(get_tick_rate() / get_output_rate(chan)),
|
||||
chan);
|
||||
|
||||
if (cic_interp > 1 and hb_enable == 0) {
|
||||
RFNOC_LOG_WARNING(
|
||||
@ -625,7 +671,7 @@ private:
|
||||
const int32_t actual_factor = static_cast<int32_t>(std::lround(target_factor));
|
||||
// Write DUC with scaling correction for CIC and DDS that maximizes
|
||||
// dynamic range
|
||||
_duc_reg_iface.poke32(SR_SCALE_IQ_ADDR, actual_factor, chan);
|
||||
_duc_reg_iface.poke32(_reg_addrs.scale_iq_addr, actual_factor, chan);
|
||||
|
||||
// Calculate the error introduced by using fixedpoint representation for
|
||||
// the scaler, can be corrected in host later.
|
||||
@ -650,7 +696,7 @@ private:
|
||||
get_freq_and_freq_word(requested_freq, dds_rate);
|
||||
|
||||
_duc_reg_iface.poke32(
|
||||
SR_FREQ_ADDR, uint32_t(freq_word), chan, get_command_time(chan));
|
||||
_reg_addrs.freq_addr, uint32_t(freq_word), chan, get_command_time(chan));
|
||||
return actual_freq;
|
||||
}
|
||||
|
||||
@ -658,7 +704,9 @@ private:
|
||||
* Attributes
|
||||
*************************************************************************/
|
||||
//! Block compat number
|
||||
const uint32_t _fpga_compat;
|
||||
const uhd::compat_num32 _fpga_compat;
|
||||
//! Version-specific register addresses
|
||||
const reg_addrs_t& _reg_addrs;
|
||||
//! Number of halfbands
|
||||
const size_t _num_halfbands;
|
||||
//! Max CIC interpolation
|
||||
|
||||
@ -26,7 +26,7 @@ constexpr size_t DEFAULT_MTU = 8000;
|
||||
|
||||
} // namespace
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_ddc_block)
|
||||
void run_ddc_block_test(const ddc_block_control::reg_addrs_t& reg_addrs)
|
||||
{
|
||||
node_accessor_t node_accessor{};
|
||||
constexpr uint32_t num_hb = 2;
|
||||
@ -36,13 +36,24 @@ BOOST_AUTO_TEST_CASE(test_ddc_block)
|
||||
constexpr int TEST_DECIM = 20;
|
||||
constexpr double DEFAULT_RATE = 200e6; // Matches typical MCR of X310
|
||||
|
||||
if (reg_addrs.major_compat == 0) {
|
||||
BOOST_REQUIRE(reg_addrs.sr_n_addr);
|
||||
BOOST_REQUIRE(reg_addrs.sr_m_addr);
|
||||
} else {
|
||||
BOOST_CHECK(!reg_addrs.sr_n_addr);
|
||||
BOOST_CHECK(!reg_addrs.sr_m_addr);
|
||||
}
|
||||
|
||||
auto block_container =
|
||||
get_mock_block(noc_id, num_chans, num_chans, uhd::device_addr_t("foo=bar"));
|
||||
auto& ddc_reg_iface = block_container.reg_iface;
|
||||
ddc_reg_iface->read_memory[ddc_block_control::RB_COMPAT_NUM] =
|
||||
(ddc_block_control::MAJOR_COMPAT << 16) | ddc_block_control::MINOR_COMPAT;
|
||||
ddc_reg_iface->read_memory[ddc_block_control::RB_NUM_HB] = num_hb;
|
||||
ddc_reg_iface->read_memory[ddc_block_control::RB_CIC_MAX_DECIM] = max_cic;
|
||||
uint32_t(reg_addrs.major_compat) << 16 | reg_addrs.minor_compat;
|
||||
ddc_reg_iface->read_memory[reg_addrs.rb_num_hb] = num_hb;
|
||||
ddc_reg_iface->read_memory[reg_addrs.rb_cic_max_decim] = max_cic;
|
||||
if (reg_addrs.rb_spc) {
|
||||
ddc_reg_iface->read_memory[*reg_addrs.rb_spc] = 1;
|
||||
}
|
||||
auto test_ddc = block_container.get_block<ddc_block_control>();
|
||||
BOOST_REQUIRE(test_ddc);
|
||||
BOOST_CHECK_EQUAL(test_ddc->get_block_args().get("foo"), "bar");
|
||||
@ -51,9 +62,13 @@ BOOST_AUTO_TEST_CASE(test_ddc_block)
|
||||
UHD_LOG_DEBUG("TEST", "Init done.");
|
||||
test_ddc->set_property<int>("decim", TEST_DECIM, 0);
|
||||
|
||||
BOOST_REQUIRE(ddc_reg_iface->write_memory.count(ddc_block_control::SR_DECIM_ADDR));
|
||||
BOOST_REQUIRE(ddc_reg_iface->write_memory.count(reg_addrs.sr_decim_addr));
|
||||
BOOST_CHECK_EQUAL(
|
||||
ddc_reg_iface->write_memory.at(ddc_block_control::SR_DECIM_ADDR), 2 << 8 | 5);
|
||||
ddc_reg_iface->write_memory.at(reg_addrs.sr_decim_addr), 2 << 8 | 5);
|
||||
BOOST_REQUIRE(ddc_reg_iface->write_memory.count(reg_addrs.sr_scale_iq_addr));
|
||||
const uint32_t expected_scale = reg_addrs.rb_spc ? 53687 : 26844;
|
||||
BOOST_CHECK_EQUAL(
|
||||
ddc_reg_iface->write_memory.at(reg_addrs.sr_scale_iq_addr), expected_scale);
|
||||
BOOST_CHECK_EQUAL(test_ddc->get_mtu({res_source_info::INPUT_EDGE, 0}), DEFAULT_MTU);
|
||||
|
||||
// Now plop it in a graph
|
||||
@ -78,11 +93,12 @@ BOOST_AUTO_TEST_CASE(test_ddc_block)
|
||||
mock_source_term.set_edge_property<size_t>(
|
||||
"mtu", NEW_MTU, {res_source_info::OUTPUT_EDGE, 0});
|
||||
|
||||
#define CHECK_INPUT_RATE(req_rate) \
|
||||
BOOST_REQUIRE_CLOSE(mock_source_term.get_edge_property<double>( \
|
||||
"samp_rate", {res_source_info::OUTPUT_EDGE, 0}), \
|
||||
req_rate, \
|
||||
1e-6);
|
||||
auto check_input_rate = [&mock_source_term](double req_rate) {
|
||||
BOOST_REQUIRE_CLOSE(mock_source_term.get_edge_property<double>(
|
||||
"samp_rate", {res_source_info::OUTPUT_EDGE, 0}),
|
||||
req_rate,
|
||||
1e-6);
|
||||
};
|
||||
|
||||
UHD_LOG_INFO("TEST", "Creating graph...");
|
||||
graph.connect(&mock_source_term, test_ddc.get(), edge_info);
|
||||
@ -90,7 +106,7 @@ BOOST_AUTO_TEST_CASE(test_ddc_block)
|
||||
UHD_LOG_INFO("TEST", "Committing graph...");
|
||||
graph.commit();
|
||||
UHD_LOG_INFO("TEST", "Commit complete.");
|
||||
CHECK_INPUT_RATE(DEFAULT_RATE);
|
||||
check_input_rate(DEFAULT_RATE);
|
||||
// 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);
|
||||
@ -101,7 +117,7 @@ BOOST_AUTO_TEST_CASE(test_ddc_block)
|
||||
"samp_rate", {res_source_info::INPUT_EDGE, 0})
|
||||
* TEST_DECIM);
|
||||
// Input rate should remain unchanged
|
||||
CHECK_INPUT_RATE(DEFAULT_RATE);
|
||||
check_input_rate(DEFAULT_RATE);
|
||||
BOOST_CHECK(mock_sink_term.get_edge_property<double>(
|
||||
"scaling", {res_source_info::INPUT_EDGE, 0})
|
||||
!= 1.0);
|
||||
@ -117,16 +133,14 @@ BOOST_AUTO_TEST_CASE(test_ddc_block)
|
||||
"Setting freq to 1/8 of input rate (to " << (DEFAULT_RATE / 8) / 1e6 << " MHz)");
|
||||
constexpr double TEST_FREQ = DEFAULT_RATE / 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);
|
||||
const uint32_t freq_word_1 = ddc_reg_iface->write_memory.at(reg_addrs.sr_freq_addr);
|
||||
BOOST_REQUIRE(freq_word_1 != 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_source_term.set_edge_property<double>(
|
||||
"samp_rate", DEFAULT_RATE * 2, {res_source_info::OUTPUT_EDGE, 0});
|
||||
const double freq_word_2 =
|
||||
ddc_reg_iface->write_memory.at(ddc_block_control::SR_FREQ_ADDR);
|
||||
const double freq_word_2 = ddc_reg_iface->write_memory.at(reg_addrs.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);
|
||||
@ -166,3 +180,29 @@ BOOST_AUTO_TEST_CASE(test_ddc_block)
|
||||
tune_req_received->tune_result.actual_dsp_freq,
|
||||
1e-5);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_ddc_compat_mismatch)
|
||||
{
|
||||
constexpr size_t num_chans = 1;
|
||||
constexpr noc_id_t noc_id = DDC_BLOCK;
|
||||
constexpr uint16_t BAD_MAJOR = 2; // above REG_ADDRS_V1.major_compat (1)
|
||||
|
||||
auto block_container =
|
||||
get_mock_block(noc_id, num_chans, num_chans, uhd::device_addr_t());
|
||||
auto& ddc_reg_iface = block_container.reg_iface;
|
||||
ddc_reg_iface->read_memory[ddc_block_control::RB_COMPAT_NUM] = (BAD_MAJOR << 16) | 0;
|
||||
ddc_reg_iface->read_memory[ddc_block_control::REG_ADDRS_V0.rb_num_hb] = 2;
|
||||
ddc_reg_iface->read_memory[ddc_block_control::REG_ADDRS_V0.rb_cic_max_decim] = 128;
|
||||
|
||||
BOOST_CHECK_THROW(block_container.get_block<ddc_block_control>(), uhd::runtime_error);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_ddc_block)
|
||||
{
|
||||
run_ddc_block_test(ddc_block_control::REG_ADDRS_V0);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_ddc_block_ms)
|
||||
{
|
||||
run_ddc_block_test(ddc_block_control::REG_ADDRS_V1);
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ constexpr size_t DEFAULT_MTU = 8000;
|
||||
|
||||
} // namespace
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_duc_block)
|
||||
void test_duc_block(const duc_block_control::reg_addrs_t& ver)
|
||||
{
|
||||
node_accessor_t node_accessor{};
|
||||
constexpr uint32_t num_hb = 2;
|
||||
@ -35,12 +35,20 @@ BOOST_AUTO_TEST_CASE(test_duc_block)
|
||||
constexpr int TEST_INTERP = 20; // 2 halfbands, CIC==5
|
||||
constexpr double DEFAULT_RATE = 200e6; // Matches typical MCR of X310
|
||||
|
||||
if (ver.major_compat == 0) {
|
||||
BOOST_REQUIRE(ver.n_addr);
|
||||
BOOST_REQUIRE(ver.m_addr);
|
||||
} else {
|
||||
BOOST_CHECK(!ver.n_addr);
|
||||
BOOST_CHECK(!ver.m_addr);
|
||||
}
|
||||
|
||||
auto block_container = get_mock_block(noc_id, num_chans, num_chans);
|
||||
auto& duc_reg_iface = block_container.reg_iface;
|
||||
duc_reg_iface->read_memory[duc_block_control::RB_COMPAT_NUM] =
|
||||
(duc_block_control::MAJOR_COMPAT << 16) | duc_block_control::MINOR_COMPAT;
|
||||
duc_reg_iface->read_memory[duc_block_control::RB_NUM_HB] = num_hb;
|
||||
duc_reg_iface->read_memory[duc_block_control::RB_CIC_MAX_INTERP] = max_cic;
|
||||
duc_reg_iface->read_memory[duc_block_control::REG_COMPAT_NUM] =
|
||||
(ver.major_compat << 16) | ver.minor_compat;
|
||||
duc_reg_iface->read_memory[ver.num_hb] = num_hb;
|
||||
duc_reg_iface->read_memory[ver.cic_max_interp] = max_cic;
|
||||
auto test_duc = block_container.get_block<duc_block_control>();
|
||||
BOOST_REQUIRE(test_duc);
|
||||
|
||||
@ -48,9 +56,13 @@ BOOST_AUTO_TEST_CASE(test_duc_block)
|
||||
UHD_LOG_DEBUG("TEST", "Init done.");
|
||||
test_duc->set_property<int>("interp", TEST_INTERP, 0);
|
||||
|
||||
BOOST_REQUIRE(duc_reg_iface->write_memory.count(duc_block_control::SR_INTERP_ADDR));
|
||||
BOOST_REQUIRE(duc_reg_iface->write_memory.count(ver.interp_addr));
|
||||
BOOST_CHECK_EQUAL(duc_reg_iface->write_memory.at(ver.interp_addr), 2 << 8 | 5);
|
||||
BOOST_REQUIRE(duc_reg_iface->write_memory.count(ver.scale_iq_addr));
|
||||
// CIC gain is 5^(4-1)=125, normalized by 128 in the FPGA.
|
||||
constexpr uint32_t EXPECTED_SCALE_IQ = 33554;
|
||||
BOOST_CHECK_EQUAL(
|
||||
duc_reg_iface->write_memory.at(duc_block_control::SR_INTERP_ADDR), 2 << 8 | 5);
|
||||
duc_reg_iface->write_memory.at(ver.scale_iq_addr), EXPECTED_SCALE_IQ);
|
||||
BOOST_CHECK_EQUAL(test_duc->get_mtu({res_source_info::INPUT_EDGE, 0}), DEFAULT_MTU);
|
||||
|
||||
// Now plop it in a graph
|
||||
@ -144,8 +156,7 @@ BOOST_AUTO_TEST_CASE(test_duc_block)
|
||||
"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);
|
||||
const uint32_t freq_word_1 = duc_reg_iface->write_memory.at(ver.freq_addr);
|
||||
BOOST_REQUIRE(freq_word_1 != 0);
|
||||
UHD_LOG_INFO(
|
||||
"TEST", "Doubling input rate (to " << (DEFAULT_RATE / 4) / 1e6 << " MHz)");
|
||||
@ -155,8 +166,7 @@ BOOST_AUTO_TEST_CASE(test_duc_block)
|
||||
* mock_sink_term.get_edge_property<double>(
|
||||
"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);
|
||||
const double freq_word_2 = duc_reg_iface->write_memory.at(ver.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);
|
||||
@ -209,3 +219,13 @@ BOOST_AUTO_TEST_CASE(test_duc_block)
|
||||
tune_req_received->tune_result.actual_dsp_freq,
|
||||
1e-5);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_duc_block_v0)
|
||||
{
|
||||
test_duc_block(duc_block_control::REG_ADDRS_V0);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_duc_block_v1)
|
||||
{
|
||||
test_duc_block(duc_block_control::REG_ADDRS_V1);
|
||||
}
|
||||
|
||||
@ -871,9 +871,11 @@ BOOST_FIXTURE_TEST_CASE(replay_test_graph_loop, replay_block_fixture)
|
||||
get_mock_block(noc_id, num_chans, num_chans, uhd::device_addr_t(""));
|
||||
auto& ddc_reg_iface = block_container.reg_iface;
|
||||
ddc_reg_iface->read_memory[ddc_block_control::RB_COMPAT_NUM] =
|
||||
(ddc_block_control::MAJOR_COMPAT << 16) | ddc_block_control::MINOR_COMPAT;
|
||||
ddc_reg_iface->read_memory[ddc_block_control::RB_NUM_HB] = num_hb;
|
||||
ddc_reg_iface->read_memory[ddc_block_control::RB_CIC_MAX_DECIM] = max_cic;
|
||||
uint32_t(ddc_block_control::REG_ADDRS_V0.major_compat) << 16
|
||||
| ddc_block_control::REG_ADDRS_V0.minor_compat;
|
||||
ddc_reg_iface->read_memory[ddc_block_control::REG_ADDRS_V0.rb_num_hb] = num_hb;
|
||||
ddc_reg_iface->read_memory[ddc_block_control::REG_ADDRS_V0.rb_cic_max_decim] =
|
||||
max_cic;
|
||||
auto test_ddc = block_container.get_block<ddc_block_control>();
|
||||
|
||||
node_accessor.init_props(test_ddc.get());
|
||||
|
||||
Loading…
Reference in New Issue
Block a user