aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib/include
diff options
context:
space:
mode:
authorLars Amsel <lars.amsel@ni.com>2021-06-04 08:27:50 +0200
committerAaron Rossetto <aaron.rossetto@ni.com>2021-06-10 12:01:53 -0500
commit2a575bf9b5a4942f60e979161764b9e942699e1e (patch)
tree2f0535625c30025559ebd7494a4b9e7122550a73 /host/lib/include
parente17916220cc955fa219ae37f607626ba88c4afe3 (diff)
downloaduhd-2a575bf9b5a4942f60e979161764b9e942699e1e.tar.gz
uhd-2a575bf9b5a4942f60e979161764b9e942699e1e.tar.bz2
uhd-2a575bf9b5a4942f60e979161764b9e942699e1e.zip
uhd: Add support for the USRP X410
Co-authored-by: Lars Amsel <lars.amsel@ni.com> Co-authored-by: Michael Auchter <michael.auchter@ni.com> Co-authored-by: Martin Braun <martin.braun@ettus.com> Co-authored-by: Paul Butler <paul.butler@ni.com> Co-authored-by: Cristina Fuentes <cristina.fuentes-curiel@ni.com> Co-authored-by: Humberto Jimenez <humberto.jimenez@ni.com> Co-authored-by: Virendra Kakade <virendra.kakade@ni.com> Co-authored-by: Lane Kolbly <lane.kolbly@ni.com> Co-authored-by: Max Köhler <max.koehler@ni.com> Co-authored-by: Andrew Lynch <andrew.lynch@ni.com> Co-authored-by: Grant Meyerhoff <grant.meyerhoff@ni.com> Co-authored-by: Ciro Nishiguchi <ciro.nishiguchi@ni.com> Co-authored-by: Thomas Vogel <thomas.vogel@ni.com>
Diffstat (limited to 'host/lib/include')
-rw-r--r--host/lib/include/uhdlib/features/fpga_load_notification_iface.hpp40
-rw-r--r--host/lib/include/uhdlib/rfnoc/rf_control/dboard_iface.hpp8
-rw-r--r--host/lib/include/uhdlib/rfnoc/rf_control/gain_profile_iface.hpp21
-rw-r--r--host/lib/include/uhdlib/usrp/common/lmx2572.hpp102
-rw-r--r--host/lib/include/uhdlib/usrp/common/mpmd_mb_controller.hpp42
-rw-r--r--host/lib/include/uhdlib/usrp/common/rpc.py51
-rw-r--r--host/lib/include/uhdlib/usrp/common/x400_rfdc_control.hpp85
-rw-r--r--host/lib/include/uhdlib/usrp/dboard/debug_dboard.hpp592
-rw-r--r--host/lib/include/uhdlib/usrp/dboard/null_dboard.hpp361
-rw-r--r--host/lib/include/uhdlib/usrp/dboard/x400_dboard_iface.hpp42
-rw-r--r--host/lib/include/uhdlib/usrp/dboard/zbx/zbx_constants.hpp269
-rw-r--r--host/lib/include/uhdlib/usrp/dboard/zbx/zbx_cpld_ctrl.hpp487
-rw-r--r--host/lib/include/uhdlib/usrp/dboard/zbx/zbx_dboard.hpp416
-rw-r--r--host/lib/include/uhdlib/usrp/dboard/zbx/zbx_expert.hpp837
-rw-r--r--host/lib/include/uhdlib/usrp/dboard/zbx/zbx_lo_ctrl.hpp86
15 files changed, 3432 insertions, 7 deletions
diff --git a/host/lib/include/uhdlib/features/fpga_load_notification_iface.hpp b/host/lib/include/uhdlib/features/fpga_load_notification_iface.hpp
new file mode 100644
index 000000000..bd88db222
--- /dev/null
+++ b/host/lib/include/uhdlib/features/fpga_load_notification_iface.hpp
@@ -0,0 +1,40 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include <uhd/features/discoverable_feature.hpp>
+#include <memory>
+#include <string>
+
+namespace uhd { namespace features {
+
+/*! This is the mechanism by which USRPs can perform actions whenever the FPGA
+ * is loaded.
+ */
+class fpga_load_notification_iface : public discoverable_feature
+{
+public:
+ using sptr = std::shared_ptr<fpga_load_notification_iface>;
+
+ static discoverable_feature::feature_id_t get_feature_id()
+ {
+ return discoverable_feature::FPGA_LOAD_NOTIFICATION;
+ }
+
+ std::string get_feature_name() const
+ {
+ return "FPGA Load Notification";
+ }
+
+ virtual ~fpga_load_notification_iface() = default;
+
+ /*! Called after the FPGA has finished loading.
+ */
+ virtual void onload() = 0;
+};
+
+}} // namespace uhd::features
diff --git a/host/lib/include/uhdlib/rfnoc/rf_control/dboard_iface.hpp b/host/lib/include/uhdlib/rfnoc/rf_control/dboard_iface.hpp
index 1651a1580..b7759098a 100644
--- a/host/lib/include/uhdlib/rfnoc/rf_control/dboard_iface.hpp
+++ b/host/lib/include/uhdlib/rfnoc/rf_control/dboard_iface.hpp
@@ -7,6 +7,9 @@
#pragma once
#include <uhd/rfnoc/rf_control/core_iface.hpp>
+#include <uhd/types/direction.hpp>
+#include <uhd/types/time_spec.hpp>
+#include <uhd/types/eeprom.hpp>
#include <uhdlib/rfnoc/rf_control/gain_profile_iface.hpp>
#include <uhdlib/usrp/common/pwr_cal_mgr.hpp>
#include <memory>
@@ -36,6 +39,11 @@ public:
virtual std::vector<uhd::usrp::pwr_cal_mgr::sptr>& get_pwr_mgr(
uhd::direction_t trx) = 0;
+
+ virtual uhd::eeprom_map_t get_db_eeprom() = 0;
+
+ //! See radio_control::set_command_time()
+ virtual void set_command_time(uhd::time_spec_t time, const size_t chan) = 0;
};
}}} // namespace uhd::rfnoc::rf_control
diff --git a/host/lib/include/uhdlib/rfnoc/rf_control/gain_profile_iface.hpp b/host/lib/include/uhdlib/rfnoc/rf_control/gain_profile_iface.hpp
index f93a42936..c65b6bd25 100644
--- a/host/lib/include/uhdlib/rfnoc/rf_control/gain_profile_iface.hpp
+++ b/host/lib/include/uhdlib/rfnoc/rf_control/gain_profile_iface.hpp
@@ -6,6 +6,7 @@
#pragma once
+#include <functional>
#include <memory>
#include <string>
#include <vector>
@@ -22,6 +23,8 @@ class gain_profile_iface
{
public:
using sptr = std::shared_ptr<gain_profile_iface>;
+ using subscriber_type =
+ std::function<void(const std::string& profile, const size_t chan)>;
virtual ~gain_profile_iface() = default;
@@ -36,6 +39,13 @@ public:
/*! Return the gain profile
*/
virtual std::string get_gain_profile(const size_t chan) const = 0;
+
+ /*! Register a subscriber to a property tree node
+ *
+ * This is useful for all those cases where the gain profile is also a
+ * property in the tree, and setting it here requires also updating the tree.
+ */
+ virtual void add_subscriber(subscriber_type&& sub) = 0;
};
/*! "Default" implementation for gain_profile_iface
@@ -52,9 +62,11 @@ public:
void set_gain_profile(const std::string& profile, const size_t chan) override;
std::string get_gain_profile(const size_t chan) const override;
+ void add_subscriber(subscriber_type&& sub) override;
private:
static const std::string DEFAULT_GAIN_PROFILE;
+ subscriber_type _sub = nullptr;
};
/*! "Enumerated" implementation for gain_profile_iface
@@ -77,10 +89,19 @@ public:
std::vector<std::string> get_gain_profile_names(const size_t) const override;
+ /*! Register a subscriber to a property tree node
+ *
+ * This is useful for all those cases where the gain profile is also a
+ * property in the tree, and setting it here requires also updating the tree.
+ */
+ void add_subscriber(subscriber_type&& sub) override;
+
private:
std::vector<std::string> _possible_profiles;
std::vector<std::string> _gain_profile;
+
+ subscriber_type _sub = nullptr;
};
}}} // namespace uhd::rfnoc::rf_control
diff --git a/host/lib/include/uhdlib/usrp/common/lmx2572.hpp b/host/lib/include/uhdlib/usrp/common/lmx2572.hpp
new file mode 100644
index 000000000..600153f1a
--- /dev/null
+++ b/host/lib/include/uhdlib/usrp/common/lmx2572.hpp
@@ -0,0 +1,102 @@
+//
+// Copyright 2020 Ettus Research, A National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include <uhd/types/time_spec.hpp>
+#include <functional>
+#include <memory>
+
+//! Control interface for an LMX2572 synthesizer
+class lmx2572_iface
+{
+public:
+ using sptr = std::shared_ptr<lmx2572_iface>;
+
+ virtual ~lmx2572_iface() = default;
+
+ enum output_t { RF_OUTPUT_A, RF_OUTPUT_B };
+
+ enum mux_in_t { DIVIDER, VCO, HIGH_IMPEDANCE, SYSREF };
+
+ //! Category of phase sync procedure. See Section 8.1.6 ("Application for
+ // SYNC") in the datasheet. Category NONE applies when no phase
+ // synchronization is required.
+ enum sync_cat { CAT1A, CAT1B, CAT2, CAT3, CAT4, NONE };
+
+ //! Write functor: Take address / data pair, craft SPI transaction
+ using write_fn_t = std::function<void(uint8_t, uint16_t)>;
+
+ //! Read functor: Return value given address
+ using read_fn_t = std::function<uint16_t(uint8_t)>;
+
+ //! Sleep functor: sleep for the specified time
+ using sleep_fn_t = std::function<void(const uhd::time_spec_t&)>;
+
+ //! Factory
+ //
+ // \param write SPI write function object
+ // \param read SPI read function object
+ // \param sleep sleep function object
+ static sptr make(write_fn_t&& poke16, read_fn_t&& peek16, sleep_fn_t&& sleep);
+
+ //! Save state to chip
+ virtual void commit() = 0;
+
+ //! Get enabled status
+ virtual bool get_enabled() = 0;
+
+ //! Enable/disable
+ virtual void set_enabled(const bool enabled = true) = 0;
+
+ //! Performs a reset of the LMX2572 by using the software reset register
+ virtual void reset() = 0;
+
+ //! Returns True if the PLL is locked, False otherwise.
+ virtual bool get_lock_status() = 0;
+
+ //! Enables or disables the phase synchronization
+ //
+ // NOTE: This does not write anything to the device, it just sets the
+ // VCO_PHASE_SYNC_EN high.
+ virtual void set_sync_mode(const bool enable) = 0;
+
+ //! Returns the enabled/disabled state of the phase synchronization
+ virtual bool get_sync_mode() = 0;
+
+ //! Enables or disables the output on both ports
+ virtual void set_output_enable_all(const bool enable) = 0;
+
+ //! Sets output A or B (OUTA_PD or OUTB_PD)
+ virtual void set_output_enable(const output_t output, const bool enable) = 0;
+
+ //! Sets the output power
+ //
+ // \param output Choose which output to control
+ // \param power Power control bits. Higher values mean more power, but the
+ // function that maps power control bits to power is non-linear,
+ // and it is also frequency-dependent. For more detail, check
+ // the data sheet, section 8.1.5.1. Ballpark numbers: 0 dBm is
+ // at about power==27, over 35 the increase becomes "not obvious".
+ virtual void set_output_power(const output_t output, const uint8_t power) = 0;
+
+ //! Sets the OUTA_MUX or OUTB_MUX input
+ virtual void set_mux_input(const output_t output, const mux_in_t input) = 0;
+
+ //! Set the output frequency
+ //
+ // A note on phase synchronization: If set_sync_mode(true) was called
+ // previously, then this method will set up the PLL in a phase-sync mode.
+ // However, this specific implementation assumes that the SYNC pin is
+ // populated, and will be pulsed after calling this command.
+ //
+ // \param target_freq The target frequency
+ // \param ref_freq The input reference frequency
+ // \param spur_dodging Set to true to enable spur dodging
+ virtual double set_frequency(const double target_freq,
+ const double ref_freq,
+ const bool spur_dodging) = 0;
+};
diff --git a/host/lib/include/uhdlib/usrp/common/mpmd_mb_controller.hpp b/host/lib/include/uhdlib/usrp/common/mpmd_mb_controller.hpp
index 98e7f2ac4..a40398991 100644
--- a/host/lib/include/uhdlib/usrp/common/mpmd_mb_controller.hpp
+++ b/host/lib/include/uhdlib/usrp/common/mpmd_mb_controller.hpp
@@ -6,8 +6,11 @@
#pragma once
+#include <uhd/features/ref_clk_calibration_iface.hpp>
#include <uhd/rfnoc/mb_controller.hpp>
#include <uhdlib/usrp/common/rpc.hpp>
+#include <uhdlib/features/discoverable_feature_registry.hpp>
+#include <uhdlib/features/fpga_load_notification_iface.hpp>
#include <uhdlib/utils/rpc.hpp>
#include <memory>
@@ -19,7 +22,8 @@ namespace uhd { namespace rfnoc {
*
* This motherboard controller abstracts out a bunch of RPC calls.
*/
-class mpmd_mb_controller : public mb_controller
+class mpmd_mb_controller : public mb_controller,
+ public ::uhd::features::discoverable_feature_registry
{
public:
using sptr = std::shared_ptr<mpmd_mb_controller>;
@@ -113,6 +117,42 @@ private:
//! Cache of available GPIO sources
std::vector<std::string> _gpio_banks;
std::unordered_map<std::string, std::vector<std::string>> _gpio_srcs;
+
+public:
+ /*! When the FPGA is reloaded, pass the notification to every Radio block
+ * Public to allow other classes to register for notifications.
+ */
+ class fpga_onload : public uhd::features::fpga_load_notification_iface {
+ public:
+ using sptr = std::shared_ptr<fpga_onload>;
+
+ fpga_onload();
+
+ void onload() override;
+
+ void request_cb(uhd::features::fpga_load_notification_iface::sptr handler);
+
+ private:
+ std::vector<std::weak_ptr<uhd::features::fpga_load_notification_iface>> _cbs;
+ };
+
+ //! Class to expose the ref_clk_calibration discoverable feature functions.
+ class ref_clk_calibration : public uhd::features::ref_clk_calibration_iface {
+ public:
+ using sptr = std::shared_ptr<ref_clk_calibration>;
+
+ ref_clk_calibration(uhd::usrp::mpmd_rpc_iface::sptr rpcc);
+
+ void set_ref_clk_tuning_word(uint32_t tuning_word) override;
+ uint32_t get_ref_clk_tuning_word() override;
+ void store_ref_clk_tuning_word(uint32_t tuning_word) override;
+
+ private:
+ uhd::usrp::mpmd_rpc_iface::sptr _rpcc;
+ };
+
+ fpga_onload::sptr _fpga_onload;
+ ref_clk_calibration::sptr _ref_clk_cal;
};
}} // namespace uhd::rfnoc
diff --git a/host/lib/include/uhdlib/usrp/common/rpc.py b/host/lib/include/uhdlib/usrp/common/rpc.py
index 04a43ebce..4ca30b07d 100644
--- a/host/lib/include/uhdlib/usrp/common/rpc.py
+++ b/host/lib/include/uhdlib/usrp/common/rpc.py
@@ -9,7 +9,7 @@ import sys
from mako.template import Template
class Function:
- def __init__(self, return_type, function_name, args):
+ def __init__(self, return_type, function_name, args, no_claim=False):
self.name = function_name
self.does_return = return_type != "void"
self.return_type = return_type
@@ -17,6 +17,7 @@ class Function:
self.rpcname = f"\"{function_name}\""
self.args = [" ".join(arg) for arg in args]
self.has_rpcprefix = False
+ self.no_claim = no_claim
def enable_rpcprefix(self):
self.rpcname = f"_rpc_prefix + \"{self.name}\""
@@ -31,7 +32,7 @@ class Interface:
for fn in self.functions:
fn.enable_rpcprefix()
-def fn_from_string(function_string):
+def fn_from_string(function_string, no_claim=False):
m = re.match(r"^([a-zA-Z:<>,_0-9 ]+)\s+([a-zA-Z0-9_]+)\(([a-zA-Z0-9,_:&<> ]*)\)$", function_string)
return_type = m.group(1)
function_name = m.group(2)
@@ -39,7 +40,7 @@ def fn_from_string(function_string):
args = [arg.strip() for arg in args.split(",")]
args = [arg.split(" ") for arg in args if len(arg) > 0]
args = [(" ".join(arg[:-1]), arg[-1]) for arg in args]
- return Function(return_type, function_name, args)
+ return Function(return_type, function_name, args, no_claim)
IFACES = [
Interface("mpmd_rpc", [
@@ -66,7 +67,36 @@ IFACES = [
fn_from_string("std::map<std::string, std::string> get_mb_eeprom()"),
fn_from_string("std::vector<std::string> get_gpio_src(const std::string& bank)"),
fn_from_string("void set_gpio_src(const std::string& bank, const std::vector<std::string>& src)"),
+
+ # ref_clk_calibration
+ fn_from_string("void set_ref_clk_tuning_word(uint32_t tuning_word)"),
+ fn_from_string("uint32_t get_ref_clk_tuning_word()"),
+ fn_from_string("void store_ref_clk_tuning_word(uint32_t tuning_word)"),
+ ]),
+ Interface("x400_rpc", [
+ fn_from_string("std::vector<std::map<std::string, std::string>> get_dboard_info()", no_claim=True),
+ fn_from_string("void set_cal_frozen(bool state, size_t block_count, size_t chan)"),
+ fn_from_string("std::vector<int> get_cal_frozen(size_t block_count, size_t chan)"),
+ fn_from_string("double rfdc_set_nco_freq(const std::string& trx, size_t block_count, size_t chan, double freq)"),
+ fn_from_string("double rfdc_get_nco_freq(const std::string& trx, size_t block_count, size_t chan)"),
+ fn_from_string("double get_master_clock_rate()"),
+ fn_from_string("std::map<std::string, std::vector<uint8_t>> get_db_eeprom(size_t db_idx)"),
+ fn_from_string("bool get_threshold_status(size_t db_number, size_t chan, size_t threshold_block)"),
+ fn_from_string("void set_dac_mux_enable(size_t motherboard_channel_number, int enable)"),
+ fn_from_string("void set_dac_mux_data(size_t i, size_t q)"),
+ fn_from_string("double get_spll_freq()"),
+ fn_from_string("void setup_threshold(size_t db_number, size_t chan, size_t threshold_block, const std::string& mode, size_t delay, size_t under, size_t over)"),
+ fn_from_string("bool is_db_gpio_ifc_present(size_t db_idx)"),
]),
+ Interface("dboard_base_rpc", [
+ fn_from_string("std::vector<std::string> get_sensors(const std::string& trx)"),
+ fn_from_string("sensor_value_t::sensor_map_t get_sensor(const std::string& trx, const std::string& sensor, size_t chan)"),
+ ], has_rpcprefix=True),
+ Interface("zbx_rpc", [
+ fn_from_string("double get_dboard_prc_rate()"),
+ fn_from_string("double get_dboard_sample_rate()"),
+ fn_from_string("void enable_iq_swap(bool is_band_inverted, const std::string& trx, size_t chan)"),
+ ], has_rpcprefix=True),
]
COMMON_TMPL = """<% import time %>\
@@ -117,11 +147,20 @@ namespace uhd { namespace usrp {
%for function in iface.functions:
${function.return_type} ${function.name}(${",".join(function.args)}) override
{
- %if function.does_return:
- return _rpcc->request_with_token<${function.return_type}>(${",".join([function.rpcname] + function.arg_names)});
+ %if function.no_claim:
+ %if function.does_return:
+ return _rpcc->request<${function.return_type}>
+ %else:
+ _rpcc->notify
+ %endif
%else:
- _rpcc->notify_with_token(${",".join([function.rpcname] + function.arg_names)});
+ %if function.does_return:
+ return _rpcc->request_with_token<${function.return_type}>
+ %else:
+ _rpcc->notify_with_token
+ %endif
%endif
+ (${",".join([function.rpcname] + function.arg_names)});
}
%endfor
diff --git a/host/lib/include/uhdlib/usrp/common/x400_rfdc_control.hpp b/host/lib/include/uhdlib/usrp/common/x400_rfdc_control.hpp
new file mode 100644
index 000000000..8d2923436
--- /dev/null
+++ b/host/lib/include/uhdlib/usrp/common/x400_rfdc_control.hpp
@@ -0,0 +1,85 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include <uhd/types/memmap_iface.hpp>
+#include <uhd/types/time_spec.hpp>
+#include <cstdint>
+#include <memory>
+#include <vector>
+#include <string>
+
+namespace uhd { namespace rfnoc { namespace x400 {
+
+//! Control class for the RFDC components of a single daughterboard
+//
+// This class controls the NCOs and other RFDC settings. The corresponding FPGA
+// module is rfdc_timing_control.v.
+class rfdc_control
+{
+public:
+ using sptr = std::shared_ptr<rfdc_control>;
+
+ struct regmap
+ {
+ //! Address of the NCO reset register
+ static constexpr uint32_t NCO_RESET = 0;
+ //! Bit position of reset-start bit (w)
+ static constexpr uint32_t NCO_RESET_START_MSB = 0;
+ //! Bit position of reset-done bit (r)
+ static constexpr uint32_t NCO_RESET_DONE_MSB = 1;
+ //! Address of the gearbox reset register
+ static constexpr uint32_t GEARBOX_RESET = 4;
+ //! Bit position of ADC gearbox reset
+ static constexpr uint32_t ADC_RESET_MSB = 0;
+ //! Bit position of DAC gearbox reset
+ static constexpr uint32_t DAC_RESET_MSB = 1;
+ };
+
+ //! Identify the NCOs/ADCs/DACs available to this radio control
+ enum class rfdc_type { RX0, RX1, TX0, TX1 };
+
+ rfdc_control(uhd::memmap32_iface_timed&& iface, const std::string& log_id);
+
+ //! Reset the listed NCOs
+ //
+ // All NCOs that are listed in \p ncos are reset synchronously.
+ //
+ // \param ncos A list of NCOs that shall be reset at the given time
+ // \param time The time at which the reset shall occur
+ void reset_ncos(const std::vector<rfdc_type>& ncos, const uhd::time_spec_t& time);
+
+ //! Reset the listed gearboxes
+ //
+ // All gearboxes that are listed in \p gearboxes are reset synchronously.
+ //
+ // \param gearboxes A list of gearboxes that shall be reset at the given time
+ // \param time The time at which the reset shall occur. Note: If \p time is
+ // set to ASAP, the resets will still occur synchronously, but
+ // at a non-deterministic time. This will suffice for synchronizing
+ // gearboxes on a single device.
+ void reset_gearboxes(
+ const std::vector<rfdc_type>& gearboxes, const uhd::time_spec_t& time);
+
+ //! Return true if the NCO is out of reset
+ bool get_nco_reset_done();
+
+ //! Set an NCO to a specific frequency
+ //
+ // \param nco Which NCO to re-tune
+ // \param freq the new frequency to tune it to (in Hz)
+ double set_nco_freq(const rfdc_type nco, const double freq);
+
+private:
+ //! Peek/poke interface
+ memmap32_iface_timed _iface;
+
+ //! Prefix for log messages
+ const std::string _log_id;
+};
+
+}}} // namespace uhd::rfnoc::x400
diff --git a/host/lib/include/uhdlib/usrp/dboard/debug_dboard.hpp b/host/lib/include/uhdlib/usrp/dboard/debug_dboard.hpp
new file mode 100644
index 000000000..72ac16e28
--- /dev/null
+++ b/host/lib/include/uhdlib/usrp/dboard/debug_dboard.hpp
@@ -0,0 +1,592 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include "x400_dboard_iface.hpp"
+#include <uhd/exception.hpp>
+#include <uhd/property_tree.hpp>
+#include <uhd/types/eeprom.hpp>
+#include <uhdlib/rfnoc/rf_control/dboard_iface.hpp>
+#include <string>
+
+#define UHD_LOG_SKIP_CFG() \
+ UHD_LOG_TRACE( \
+ "RFNOC::DEBUG_DB", "Skipping unsupported debug db config for " << __FUNCTION__);
+
+namespace uhd { namespace rfnoc {
+
+const static uint16_t EMPTY_DB_PID = 0x0;
+const static uint16_t DEBUG_DB_PID = 0x4001;
+const static uint16_t IF_TEST_DBOARD_PID = 0x4006;
+
+/*! \brief Implementation of common dboard_iface for IF Test and Debug dboards.
+ */
+class debug_dboard_common_impl : public uhd::usrp::x400::x400_dboard_iface
+{
+public:
+ using sptr = std::shared_ptr<debug_dboard_common_impl>;
+
+ rf_control::gain_profile_iface::sptr get_tx_gain_profile_api() override
+ {
+ return rf_control::gain_profile_iface::sptr();
+ }
+
+ rf_control::gain_profile_iface::sptr get_rx_gain_profile_api() override
+ {
+ return rf_control::gain_profile_iface::sptr();
+ }
+
+ bool is_adc_self_cal_supported() override
+ {
+ return false;
+ }
+
+ uhd::usrp::x400::adc_self_cal_params_t get_adc_self_cal_params(double) override
+ {
+ return {
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ };
+ }
+
+ size_t get_chan_from_dboard_fe(const std::string& fe, direction_t) const override
+ {
+ if (fe == "0") {
+ return 0;
+ }
+ if (fe == "1") {
+ return 1;
+ }
+ throw uhd::key_error(std::string("[X400] Invalid frontend: ") + fe);
+ }
+
+ std::string get_dboard_fe_from_chan(size_t chan, direction_t) const override
+ {
+ if (chan == 0) {
+ return "0";
+ }
+ if (chan == 1) {
+ return "1";
+ }
+ throw uhd::lookup_error(
+ std::string("[X400] Invalid channel: ") + std::to_string(chan));
+ }
+
+ std::vector<usrp::pwr_cal_mgr::sptr>& get_pwr_mgr(direction_t) override
+ {
+ static std::vector<usrp::pwr_cal_mgr::sptr> empty_vtr;
+ return empty_vtr;
+ }
+
+ eeprom_map_t get_db_eeprom() override
+ {
+ return {};
+ }
+
+ std::string get_tx_antenna(const size_t) const override
+ {
+ return "";
+ }
+
+ std::vector<std::string> get_tx_antennas(const size_t) const override
+ {
+ return {};
+ }
+
+ void set_tx_antenna(const std::string&, const size_t) override{UHD_LOG_SKIP_CFG()}
+
+ std::string get_rx_antenna(const size_t) const override
+ {
+ return "";
+ }
+
+ std::vector<std::string> get_rx_antennas(const size_t) const override
+ {
+ return {};
+ }
+
+ void set_rx_antenna(const std::string&, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ }
+
+ double get_tx_frequency(const size_t) override
+ {
+ return 0;
+ }
+
+ double set_tx_frequency(const double, size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ return 0;
+ }
+
+ void set_tx_tune_args(const device_addr_t&, const size_t) override{UHD_LOG_SKIP_CFG()}
+
+ freq_range_t get_tx_frequency_range(const size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ double get_rx_frequency(const size_t) override
+ {
+ return 0;
+ }
+
+ double set_rx_frequency(const double, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ return 0;
+ }
+
+ void set_rx_tune_args(const device_addr_t&, const size_t) override{UHD_LOG_SKIP_CFG()}
+
+ freq_range_t get_rx_frequency_range(const size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ std::vector<std::string> get_tx_gain_names(const size_t) const override
+ {
+ return {};
+ }
+
+ gain_range_t get_tx_gain_range(const size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ gain_range_t get_tx_gain_range(const std::string&, const size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ double get_tx_gain(const size_t) override
+ {
+ return 0;
+ }
+
+ double get_tx_gain(const std::string&, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ return 0;
+ }
+
+ double set_tx_gain(const double, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ return 0;
+ }
+
+ double set_tx_gain(const double, const std::string&, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ return 0;
+ }
+
+ std::vector<std::string> get_rx_gain_names(const size_t) const override
+ {
+ return {};
+ }
+
+ gain_range_t get_rx_gain_range(const size_t) const override
+ {
+ UHD_LOG_SKIP_CFG()
+ return meta_range_t(0.0, 0.0);
+ }
+
+ gain_range_t get_rx_gain_range(const std::string&, const size_t) const override
+ {
+ UHD_LOG_SKIP_CFG()
+ return meta_range_t(0.0, 0.0);
+ }
+
+ double get_rx_gain(const size_t) override
+ {
+ return 0;
+ }
+
+ double get_rx_gain(const std::string&, const size_t) override
+ {
+ return 0;
+ }
+
+ double set_rx_gain(const double, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ return 0;
+ }
+
+ double set_rx_gain(const double, const std::string&, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ return 0;
+ }
+
+ void set_rx_agc(const bool, const size_t) override{UHD_LOG_SKIP_CFG()}
+
+ meta_range_t get_tx_bandwidth_range(size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ double get_tx_bandwidth(const size_t) override
+ {
+ return 0;
+ }
+
+ double set_tx_bandwidth(const double, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ return 0;
+ }
+
+ meta_range_t get_rx_bandwidth_range(size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ double get_rx_bandwidth(const size_t) override
+ {
+ return 0;
+ }
+
+ double set_rx_bandwidth(const double, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ return 0;
+ }
+
+ std::vector<std::string> get_rx_lo_names(const size_t) const override
+ {
+ return {};
+ }
+
+ std::vector<std::string> get_rx_lo_sources(
+ const std::string&, const size_t) const override
+ {
+ UHD_LOG_SKIP_CFG()
+ return {};
+ }
+
+ freq_range_t get_rx_lo_freq_range(const std::string&, const size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ void set_rx_lo_source(const std::string&, const std::string&, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ }
+
+ const std::string get_rx_lo_source(const std::string&, const size_t) override
+ {
+ return "";
+ }
+
+ void set_rx_lo_export_enabled(bool, const std::string&, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ }
+
+ bool get_rx_lo_export_enabled(const std::string&, const size_t) override
+ {
+ return false;
+ }
+
+ double set_rx_lo_freq(double, const std::string&, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ return 0;
+ }
+
+ double get_rx_lo_freq(const std::string&, const size_t) override
+ {
+ return 0;
+ }
+
+ std::vector<std::string> get_tx_lo_names(const size_t) const override
+ {
+ return {};
+ }
+
+ std::vector<std::string> get_tx_lo_sources(const std::string&, const size_t) const override
+ {
+ return {};
+ }
+
+ freq_range_t get_tx_lo_freq_range(const std::string&, const size_t) override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ void set_tx_lo_source(const std::string&, const std::string&, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ }
+
+ const std::string get_tx_lo_source(const std::string&, const size_t) override
+ {
+ return "";
+ }
+
+ void set_tx_lo_export_enabled(const bool, const std::string&, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ }
+
+ bool get_tx_lo_export_enabled(const std::string&, const size_t) override
+ {
+ return false;
+ }
+
+ double set_tx_lo_freq(const double, const std::string&, const size_t) override
+ {
+ UHD_LOG_SKIP_CFG()
+ return 0;
+ }
+
+ double get_tx_lo_freq(const std::string&, const size_t) override
+ {
+ return 0;
+ }
+
+ void set_command_time(uhd::time_spec_t, const size_t) override
+ {
+ // nop
+ }
+};
+
+/*! \brief Implementation of dboard_iface for debug_db.
+ */
+class debug_dboard_impl : public debug_dboard_common_impl
+{
+ // Just an empty class for conveniently organizing class hierarchy.
+};
+
+/*! \brief Fake dboard implementation for an empty slot
+ */
+class empty_slot_dboard_impl : public debug_dboard_common_impl
+{
+ // Just an empty class for conveniently organizing class hierarchy.
+};
+
+/*! \brief Implementation of dboard_iface for IF Test dboard.
+ */
+class if_test_dboard_impl : public debug_dboard_common_impl
+{
+public:
+ /******************************************************************************
+ * Structors
+ *****************************************************************************/
+ if_test_dboard_impl(const size_t db_idx,
+ const std::string& rpc_prefix,
+ const std::string& unique_id,
+ std::shared_ptr<mpmd_mb_controller> mb_controller,
+ uhd::property_tree::sptr tree)
+ : _unique_id(unique_id)
+ , _db_idx(db_idx)
+ , _rpc_prefix(rpc_prefix)
+ , _mb_control(mb_controller)
+ , _tree(tree)
+ {
+ RFNOC_LOG_TRACE("Entering " << __FUNCTION__);
+ RFNOC_LOG_TRACE("DB ID: " << _db_idx);
+ UHD_ASSERT_THROW(_mb_control);
+ _rpcc = _mb_control->get_rpc_client();
+ UHD_ASSERT_THROW(_rpcc);
+ _init_frontend_subtree();
+ }
+
+ ~if_test_dboard_impl()
+ {
+ RFNOC_LOG_TRACE(__FUNCTION__);
+ }
+
+ // The IF Test dboard muxes a single SMA port (for each of RX and TX) like so:
+ // /---> dac0
+ // /----> dac1
+ // TX SMA port -- [mux] -----> dac2
+ // \----> dac3
+ //
+ // (and similarly with the RX SMA port and the adcs)
+
+ std::vector<std::string> get_tx_muxes(void)
+ {
+ return {"DAC0", "DAC1", "DAC2", "DAC3"};
+ }
+
+ void set_tx_mux(const std::string& mux)
+ {
+ RFNOC_LOG_TRACE("Setting TX mux to " << mux);
+ _rpcc->notify_with_token(
+ _rpc_prefix + "config_tx_path", _get_tx_path_from_mux(mux));
+ }
+
+ std::string get_tx_mux(void)
+ {
+ return _rpcc->request_with_token<std::string>(_rpc_prefix + "get_tx_path");
+ }
+
+ std::vector<std::string> get_rx_muxes(void)
+ {
+ return {"ADC0", "ADC1", "ADC2", "ADC3"};
+ }
+
+ void set_rx_mux(const std::string& mux)
+ {
+ RFNOC_LOG_TRACE("Setting RX mux to " << mux);
+ _rpcc->notify_with_token(
+ _rpc_prefix + "config_rx_path", _get_rx_path_from_mux(mux));
+ }
+
+ std::string get_rx_mux(void)
+ {
+ return _rpcc->request_with_token<std::string>(_rpc_prefix + "get_rx_path");
+ }
+
+ eeprom_map_t get_db_eeprom() override
+ {
+ return _rpcc->request_with_token<eeprom_map_t>("get_db_eeprom", _db_idx);
+ }
+
+
+private:
+ //! Used by the RFNOC_LOG_* macros.
+ const std::string _unique_id;
+ std::string get_unique_id() const
+ {
+ return _unique_id;
+ }
+
+ //! Index of this daughterboard
+ const size_t _db_idx;
+
+ //! Prepended for all dboard RPC calls
+ const std::string _rpc_prefix;
+
+ //! Reference to the MB controller
+ uhd::rfnoc::mpmd_mb_controller::sptr _mb_control;
+
+ //! Reference to the RPC client
+ uhd::rpc_client::sptr _rpcc;
+
+ //! Reference to this block's subtree
+ //
+ // It is mutable because _tree->access<>(..).get() is not const, but we
+ // need to do just that in some const contexts
+ mutable uhd::property_tree::sptr _tree;
+
+ std::string _get_tx_path_from_mux(const std::string mux)
+ {
+ if (mux == "DAC0") {
+ return "dac0";
+ } else if (mux == "DAC1") {
+ return "dac1";
+ } else if (mux == "DAC2") {
+ return "dac2";
+ } else if (mux == "DAC3") {
+ return "dac3";
+ } else {
+ throw uhd::value_error(
+ std::string("[RFNOC::IF_TEST_DBOARD] Invalid TX Mux Name: ") + mux);
+ }
+ }
+
+ std::string _get_rx_path_from_mux(const std::string mux)
+ {
+ if (mux == "ADC0") {
+ return "adc0";
+ } else if (mux == "ADC1") {
+ return "adc1";
+ } else if (mux == "ADC2") {
+ return "adc2";
+ } else if (mux == "ADC3") {
+ return "adc3";
+ } else {
+ throw uhd::value_error(
+ std::string("[RFNOC::IF_TEST_DBOARD] Invalid RX Mux Name: ") + mux);
+ }
+ }
+
+ void _init_frontend_subtree()
+ {
+ auto subtree = _tree->subtree(fs_path("dboard"));
+
+ // DB EEPROM
+ subtree->create<eeprom_map_t>("eeprom")
+ .add_coerced_subscriber([this](const eeprom_map_t&) {
+ throw uhd::runtime_error("Attempting to update daughterboard eeprom!");
+ })
+ .set_publisher([this]() { return get_db_eeprom(); });
+
+ static const char IF_TEST_FE_NAME[] = "IF_TEST";
+
+ const fs_path tx_fe_path = fs_path("tx_frontends/0");
+ const fs_path rx_fe_path = fs_path("rx_frontends/0");
+ RFNOC_LOG_TRACE("Adding non-RFNoC block properties"
+ << " to prop tree path " << tx_fe_path << " and " << rx_fe_path);
+
+ subtree->create<std::string>(tx_fe_path / "name").set(IF_TEST_FE_NAME);
+ subtree->create<std::string>(rx_fe_path / "name").set(IF_TEST_FE_NAME);
+
+ // TX Mux
+ subtree->create<std::string>(tx_fe_path / "mux" / "value")
+ .add_coerced_subscriber(
+ [this](const std::string& mux) { this->set_tx_mux(mux); })
+ .set_publisher([this]() { return this->get_tx_mux(); });
+ subtree->create<std::vector<std::string>>(tx_fe_path / "mux" / "options")
+ .set(get_tx_muxes())
+ .add_coerced_subscriber([](const std::vector<std::string>&) {
+ throw uhd::runtime_error("Attempting to update mux options!");
+ });
+
+ // RX Mux
+ subtree->create<std::string>(rx_fe_path / "mux" / "value")
+ .add_coerced_subscriber(
+ [this](const std::string& mux) { this->set_rx_mux(mux); })
+ .set_publisher([this]() { return this->get_rx_mux(); });
+ subtree->create<std::vector<std::string>>(rx_fe_path / "mux" / "options")
+ .set(get_rx_muxes())
+ .add_coerced_subscriber([](const std::vector<std::string>&) {
+ throw uhd::runtime_error("Attempting to update mux options!");
+ });
+
+ for (auto fe_path : {tx_fe_path, rx_fe_path}) {
+ // Antennas
+ const std::vector<std::string> antenna_options = {"SMA"};
+ subtree->create<std::vector<std::string>>(fe_path / "antenna" / "options")
+ .set(antenna_options)
+ .add_coerced_subscriber([](const std::vector<std::string>&) {
+ throw uhd::runtime_error("Attempting to update antenna options!");
+ });
+
+ // Frequency range
+ const uhd::freq_range_t freq_range(0.0, 0.0);
+ subtree->create<meta_range_t>(fe_path / "freq" / "range")
+ .set(freq_range)
+ .add_coerced_subscriber([](const meta_range_t&) {
+ throw uhd::runtime_error("Attempting to update freq range!");
+ });
+
+ // Gains
+ const uhd::gain_range_t gain_range(0.0, 0.0, 1.0);
+ subtree->create<meta_range_t>(fe_path / "gains" / "all" / "range")
+ .set(gain_range)
+ .add_coerced_subscriber([](const meta_range_t&) {
+ throw uhd::runtime_error("Attempting to update gain range!");
+ });
+
+ // Connection
+ subtree->create<std::string>(fe_path / "connection").set("IQ");
+ }
+ }
+};
+
+}} // namespace uhd::rfnoc
diff --git a/host/lib/include/uhdlib/usrp/dboard/null_dboard.hpp b/host/lib/include/uhdlib/usrp/dboard/null_dboard.hpp
new file mode 100644
index 000000000..107ccdeb0
--- /dev/null
+++ b/host/lib/include/uhdlib/usrp/dboard/null_dboard.hpp
@@ -0,0 +1,361 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include "x400_dboard_iface.hpp"
+#include <uhd/exception.hpp>
+#include <uhdlib/rfnoc/rf_control/dboard_iface.hpp>
+#include <string>
+
+namespace uhd { namespace rfnoc {
+
+/*! \brief Implementation of dboard_iface for unpopulated or unsupported daughterboards
+ */
+class null_dboard_impl : public uhd::usrp::x400::x400_dboard_iface
+{
+public:
+ using sptr = std::shared_ptr<null_dboard_impl>;
+
+ rf_control::gain_profile_iface::sptr get_tx_gain_profile_api() override
+ {
+ return rf_control::gain_profile_iface::sptr();
+ }
+
+ rf_control::gain_profile_iface::sptr get_rx_gain_profile_api() override
+ {
+ return rf_control::gain_profile_iface::sptr();
+ }
+
+ bool is_adc_self_cal_supported() override
+ {
+ return false;
+ }
+
+ uhd::usrp::x400::adc_self_cal_params_t get_adc_self_cal_params(double) override
+ {
+ return {
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ };
+ }
+
+ size_t get_chan_from_dboard_fe(const std::string& fe, direction_t) const override
+ {
+ if (fe == "0") {
+ return 0;
+ }
+ if (fe == "1") {
+ return 1;
+ }
+ throw uhd::key_error(std::string("[X400] Invalid frontend: ") + fe);
+ }
+
+ std::string get_dboard_fe_from_chan(size_t chan, direction_t) const override
+ {
+ if (chan == 0) {
+ return "0";
+ }
+ if (chan == 1) {
+ return "1";
+ }
+ throw uhd::lookup_error(
+ std::string("[X400] Invalid channel: ") + std::to_string(chan));
+ }
+
+ std::vector<usrp::pwr_cal_mgr::sptr>& get_pwr_mgr(direction_t) override
+ {
+ static std::vector<usrp::pwr_cal_mgr::sptr> empty_vtr;
+ return empty_vtr;
+ }
+
+ eeprom_map_t get_db_eeprom() override
+ {
+ return {};
+ }
+
+ std::string get_tx_antenna(const size_t) const override
+ {
+ return "";
+ }
+
+ std::vector<std::string> get_tx_antennas(const size_t) const override
+ {
+ return {};
+ }
+
+ void set_tx_antenna(const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ std::string get_rx_antenna(const size_t) const override
+ {
+ return "";
+ }
+
+ std::vector<std::string> get_rx_antennas(const size_t) const override
+ {
+ return {};
+ }
+
+ void set_rx_antenna(const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ double get_tx_frequency(const size_t) override
+ {
+ return 0;
+ }
+
+ double set_tx_frequency(const double, size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ void set_tx_tune_args(const device_addr_t&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ freq_range_t get_tx_frequency_range(const size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ double get_rx_frequency(const size_t) override
+ {
+ return 0;
+ }
+
+ double set_rx_frequency(const double, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ void set_rx_tune_args(const device_addr_t&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ freq_range_t get_rx_frequency_range(const size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ std::vector<std::string> get_tx_gain_names(const size_t) const override
+ {
+ return {};
+ }
+
+ gain_range_t get_tx_gain_range(const size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ gain_range_t get_tx_gain_range(const std::string&, const size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ double get_tx_gain(const size_t) override
+ {
+ return 0;
+ }
+
+ double get_tx_gain(const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ double set_tx_gain(const double, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ double set_tx_gain(const double, const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ std::vector<std::string> get_rx_gain_names(const size_t) const override
+ {
+ return {};
+ }
+
+ gain_range_t get_rx_gain_range(const size_t) const override
+ {
+ throw _no_dboard_exception();
+ }
+
+ gain_range_t get_rx_gain_range(const std::string&, const size_t) const override
+ {
+ throw _no_dboard_exception();
+ }
+
+ double get_rx_gain(const size_t) override
+ {
+ return 0;
+ }
+
+ double get_rx_gain(const std::string&, const size_t) override
+ {
+ return 0;
+ }
+
+ double set_rx_gain(const double, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ double set_rx_gain(const double, const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ void set_rx_agc(const bool, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ meta_range_t get_tx_bandwidth_range(size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ double get_tx_bandwidth(const size_t) override
+ {
+ return 0;
+ }
+
+ double set_tx_bandwidth(const double, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ meta_range_t get_rx_bandwidth_range(size_t) const override
+ {
+ return meta_range_t(0.0, 0.0);
+ }
+
+ double get_rx_bandwidth(const size_t) override
+ {
+ return 0;
+ }
+
+ double set_rx_bandwidth(const double, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ std::vector<std::string> get_rx_lo_names(const size_t) const override
+ {
+ return {};
+ }
+
+ std::vector<std::string> get_rx_lo_sources(
+ const std::string&, const size_t) const override
+ {
+ throw _no_dboard_exception();
+ }
+
+ freq_range_t get_rx_lo_freq_range(const std::string&, const size_t) const override
+ {
+ throw _no_dboard_exception();
+ }
+
+ void set_rx_lo_source(const std::string&, const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ const std::string get_rx_lo_source(const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ void set_rx_lo_export_enabled(bool, const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ bool get_rx_lo_export_enabled(const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ double set_rx_lo_freq(double, const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ double get_rx_lo_freq(const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ std::vector<std::string> get_tx_lo_names(const size_t) const override
+ {
+ return {};
+ }
+
+ std::vector<std::string> get_tx_lo_sources(const std::string&, const size_t) const override
+ {
+ throw _no_dboard_exception();
+ }
+
+ freq_range_t get_tx_lo_freq_range(const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ void set_tx_lo_source(const std::string&, const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ const std::string get_tx_lo_source(const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ void set_tx_lo_export_enabled(const bool, const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ bool get_tx_lo_export_enabled(const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ double set_tx_lo_freq(const double, const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ double get_tx_lo_freq(const std::string&, const size_t) override
+ {
+ throw _no_dboard_exception();
+ }
+
+ void set_command_time(uhd::time_spec_t, const size_t) override
+ {
+ // nop
+ }
+
+private:
+ uhd::runtime_error _no_dboard_exception() const
+ {
+ const std::string msg("No daughterboard or daughterboard with unrecognized PID.");
+ return uhd::runtime_error(msg);
+ }
+};
+
+}} // namespace uhd::rfnoc
diff --git a/host/lib/include/uhdlib/usrp/dboard/x400_dboard_iface.hpp b/host/lib/include/uhdlib/usrp/dboard/x400_dboard_iface.hpp
new file mode 100644
index 000000000..a6d52cbc0
--- /dev/null
+++ b/host/lib/include/uhdlib/usrp/dboard/x400_dboard_iface.hpp
@@ -0,0 +1,42 @@
+//
+// Copyright 2021 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include <uhdlib/rfnoc/rf_control/dboard_iface.hpp>
+#include <memory>
+
+namespace uhd { namespace usrp { namespace x400 {
+
+/*! Parameters used for ADC self cal on the X400.
+ *
+ * If the daughterboard supports ADC self-cal, min_gain and max_gain will be the
+ * gains used in the gain auto detection algorithm.
+ */
+struct adc_self_cal_params_t
+{
+ double min_gain;
+ double max_gain;
+ double rx_freq;
+ double tx_freq;
+};
+
+/*! Interface for daughterboards which support being plugged into a X400 motherboard.
+ */
+class x400_dboard_iface : public uhd::rfnoc::rf_control::dboard_iface
+{
+public:
+ using sptr = std::shared_ptr<x400_dboard_iface>;
+
+ //! Returns whether this dboard supports ADC self cal
+ virtual bool is_adc_self_cal_supported() = 0;
+
+ //! Returns the parameters required to generate a suitable loopback tone at
+ //! tone_freq to perform ADC self cal.
+ virtual adc_self_cal_params_t get_adc_self_cal_params(double tone_freq) = 0;
+};
+
+}}} // namespace uhd::usrp::x400
diff --git a/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_constants.hpp b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_constants.hpp
new file mode 100644
index 000000000..0d1d7af7c
--- /dev/null
+++ b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_constants.hpp
@@ -0,0 +1,269 @@
+//
+// Copyright 2019 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+#pragma once
+
+#include <uhd/exception.hpp>
+#include <uhd/types/ranges.hpp>
+#include <unordered_map>
+#include <array>
+#include <cstddef>
+#include <list>
+#include <map>
+#include <string>
+#include <vector>
+
+namespace uhd { namespace usrp { namespace zbx {
+
+//! Which LO to address when peeking/poking
+// This must match the LO_SELECT values in gen_zbx_cpld_regs.py
+enum class zbx_lo_t {
+ TX0_LO1 = 0,
+ TX0_LO2 = 1,
+ TX1_LO1 = 2,
+ TX1_LO2 = 3,
+ RX0_LO1 = 4,
+ RX0_LO2 = 5,
+ RX1_LO1 = 6,
+ RX1_LO2 = 7
+};
+
+static const std::map<zbx_lo_t, std::string> ZBX_LO_LOG_ID = {
+ {zbx_lo_t::TX0_LO1, "ZBX TX0 LO1"},
+ {zbx_lo_t::TX0_LO2, "ZBX TX0 LO2"},
+ {zbx_lo_t::TX1_LO1, "ZBX TX1 LO1"},
+ {zbx_lo_t::TX1_LO2, "ZBX TX1 LO2"},
+ {zbx_lo_t::RX0_LO1, "ZBX RX0 LO1"},
+ {zbx_lo_t::RX0_LO2, "ZBX RX0 LO2"},
+ {zbx_lo_t::RX1_LO1, "ZBX RX1 LO1"},
+ {zbx_lo_t::RX1_LO2, "ZBX RX1 LO2"}};
+
+static constexpr std::array<zbx_lo_t, 8> ZBX_ALL_LO = {zbx_lo_t::TX0_LO1,
+ zbx_lo_t::TX0_LO2,
+ zbx_lo_t::TX1_LO1,
+ zbx_lo_t::TX1_LO2,
+ zbx_lo_t::RX0_LO1,
+ zbx_lo_t::RX0_LO2,
+ zbx_lo_t::RX1_LO1,
+ zbx_lo_t::RX1_LO2};
+
+
+/******************************************************************************
+ * Important: When changing values here, check if that also requires updating
+ * the manual (host/docs/zbx.dox). If it also requires changing the website or
+ * other sales/marketing material, make sure to let the appropriate people know!
+ *****************************************************************************/
+
+enum class zbx_lo_source_t { internal, external };
+static constexpr zbx_lo_source_t ZBX_DEFAULT_LO_SOURCE = zbx_lo_source_t::internal;
+
+// The ZBX has a non-configurable analog bandwidth of 400 MHz. At lower frequency,
+// the usable bandwidth may be smaller though. For those smaller bandwidths, see
+// the tune maps.
+static constexpr double ZBX_DEFAULT_BANDWIDTH = 400e6; // Hz
+
+static constexpr double LMX2572_MAX_FREQ = 6.4e9; // Hz
+// LMX2572 can go lower, but on the ZBX, the analog paths limit frequencies down
+// to 3.2 GHz
+static constexpr double LMX2572_MIN_FREQ = 3.2e9; // Hz
+static constexpr double LMX2572_DEFAULT_FREQ = 4e9; // Hz
+static constexpr uint32_t ZBX_LO_LOCK_TIMEOUT_MS = 20; // milliseconds
+// This is the step size for the LO tuning relative to the PRC rate:
+static constexpr int ZBX_RELATIVE_LO_STEP_SIZE = 6;
+
+static constexpr double ZBX_MIN_FREQ = 1e6; // Hz
+static constexpr double ZBX_MAX_FREQ = 8e9; // Hz
+static constexpr double ZBX_DEFAULT_FREQ = 1e9; // Hz
+static const uhd::freq_range_t ZBX_FREQ_RANGE(ZBX_MIN_FREQ, ZBX_MAX_FREQ);
+static constexpr double ZBX_LOWBAND_FREQ = 3e9; // Hz
+
+constexpr char HW_GAIN_STAGE[] = "hw";
+
+static constexpr double RX_MIN_GAIN = 0;
+static constexpr double RX_MAX_GAIN = 60;
+static constexpr double RX_GAIN_STEP = 1;
+static constexpr double ZBX_DEFAULT_RX_GAIN = RX_MIN_GAIN;
+static const uhd::gain_range_t ZBX_RX_GAIN_RANGE(RX_MIN_GAIN, RX_MAX_GAIN, RX_GAIN_STEP);
+// Rx gain is limited to [0, 38] for frequency <= 500 MHz
+static constexpr double RX_LOW_FREQ_MAX_GAIN = 38;
+static constexpr double RX_LOW_FREQ_MAX_GAIN_CUTOFF = 500e6; // Hz
+static const uhd::gain_range_t ZBX_RX_LOW_FREQ_GAIN_RANGE(
+ RX_MIN_GAIN, RX_LOW_FREQ_MAX_GAIN, RX_GAIN_STEP);
+static constexpr double TX_MIN_GAIN = 0;
+static constexpr double TX_MAX_GAIN = 60;
+static constexpr double TX_GAIN_STEP = 1;
+static constexpr double ZBX_DEFAULT_TX_GAIN = TX_MIN_GAIN;
+static const uhd::gain_range_t ZBX_TX_GAIN_RANGE(TX_MIN_GAIN, TX_MAX_GAIN, TX_GAIN_STEP);
+
+static constexpr char ZBX_GAIN_PROFILE_DEFAULT[] = "default";
+static constexpr char ZBX_GAIN_PROFILE_MANUAL[] = "manual";
+static constexpr char ZBX_GAIN_PROFILE_CPLD[] = "table";
+static constexpr char ZBX_GAIN_PROFILE_CPLD_NOATR[] = "table_noatr";
+static const std::vector<std::string> ZBX_GAIN_PROFILES = {ZBX_GAIN_PROFILE_DEFAULT,
+ ZBX_GAIN_PROFILE_MANUAL,
+ ZBX_GAIN_PROFILE_CPLD,
+ ZBX_GAIN_PROFILE_CPLD_NOATR};
+
+// Maximum attenuation of the TX DSAs
+static constexpr uint8_t ZBX_TX_DSA_MAX_ATT = 31;
+// Maximum attenuation of the RX DSAs
+static constexpr uint8_t ZBX_RX_DSA_MAX_ATT = 15;
+
+static constexpr char ZBX_GAIN_STAGE_DSA1[] = "DSA1";
+static constexpr char ZBX_GAIN_STAGE_DSA2[] = "DSA2";
+static constexpr char ZBX_GAIN_STAGE_DSA3A[] = "DSA3A";
+static constexpr char ZBX_GAIN_STAGE_DSA3B[] = "DSA3B";
+static constexpr char ZBX_GAIN_STAGE_AMP[] = "AMP";
+static constexpr char ZBX_GAIN_STAGE_ALL[] = "all";
+// Not technically a gain stage, but we'll keep it
+static constexpr char ZBX_GAIN_STAGE_TABLE[] = "TABLE";
+
+static const std::vector<std::string> ZBX_RX_GAIN_STAGES = {
+ ZBX_GAIN_STAGE_DSA1, ZBX_GAIN_STAGE_DSA2, ZBX_GAIN_STAGE_DSA3A, ZBX_GAIN_STAGE_DSA3B};
+
+static const std::vector<std::string> ZBX_TX_GAIN_STAGES = {
+ ZBX_GAIN_STAGE_DSA1, ZBX_GAIN_STAGE_DSA2, ZBX_GAIN_STAGE_AMP};
+
+enum class tx_amp { BYPASS = 0, LOWBAND = 1, HIGHBAND = 2 };
+
+static constexpr double ZBX_TX_BYPASS_GAIN = 0.0;
+static constexpr double ZBX_TX_LOWBAND_GAIN = 14.0;
+static constexpr double ZBX_TX_HIGHBAND_GAIN = 21.0;
+
+// The amplifier gain varies wildly across frequency, temperature.... but we
+// need some kind of mapping for querying/setting individual gain stages by
+// dB value.
+static const std::map<tx_amp, double> ZBX_TX_AMP_GAIN_MAP = {
+ {tx_amp::BYPASS, ZBX_TX_BYPASS_GAIN},
+ {tx_amp::LOWBAND, ZBX_TX_LOWBAND_GAIN},
+ {tx_amp::HIGHBAND, ZBX_TX_HIGHBAND_GAIN}};
+static const std::map<double, tx_amp> ZBX_TX_GAIN_AMP_MAP = {
+ {ZBX_TX_BYPASS_GAIN, tx_amp::BYPASS},
+ {ZBX_TX_LOWBAND_GAIN, tx_amp::LOWBAND},
+ {ZBX_TX_HIGHBAND_GAIN, tx_amp::HIGHBAND}};
+
+
+/*** Antenna-related constants ***********************************************/
+// TX and RX SMA connectors on the front panel
+constexpr char ANTENNA_TXRX[] = "TX/RX0";
+constexpr char ANTENNA_RX[] = "RX1";
+// Internal "antenna" ports
+constexpr char ANTENNA_CAL_LOOPBACK[] = "CAL_LOOPBACK";
+constexpr char ANTENNA_TERMINATION[] = "TERMINATION"; // Only RX path
+// Default antennas (which are selected at init)
+constexpr auto DEFAULT_TX_ANTENNA = ANTENNA_TXRX;
+constexpr auto DEFAULT_RX_ANTENNA = ANTENNA_RX;
+// Helper lists
+static const std::vector<std::string> RX_ANTENNAS = {
+ ANTENNA_TXRX, ANTENNA_RX, ANTENNA_CAL_LOOPBACK, ANTENNA_TERMINATION};
+static const std::vector<std::string> TX_ANTENNAS = {ANTENNA_TXRX, ANTENNA_CAL_LOOPBACK};
+// For branding purposes, ZBX changed the antenna names around. For existing
+// software, we still accept the old antenna names, but map them to the new ones
+static const std::unordered_map<std::string, std::string> TX_ANTENNA_NAME_COMPAT_MAP{
+ {"TX/RX", ANTENNA_TXRX}};
+static const std::unordered_map<std::string, std::string> RX_ANTENNA_NAME_COMPAT_MAP{
+ {"TX/RX", ANTENNA_TXRX}, {"RX2", ANTENNA_RX}};
+
+/*** LO-related constants ****************************************************/
+//! Low-band LO
+static constexpr char ZBX_LO1[] = "LO1";
+//! LO at 2nd mixer
+static constexpr char ZBX_LO2[] = "LO2";
+
+static constexpr char RFDC_NCO[] = "rfdc";
+
+static const std::vector<std::string> ZBX_LOS = {ZBX_LO1, ZBX_LO2, RFDC_NCO};
+
+static constexpr size_t ZBX_NUM_CHANS = 2;
+static constexpr std::array<size_t, 2> ZBX_CHANNELS{0, 1};
+
+static constexpr double ZBX_MIX1_MN_THRESHOLD = 4e9;
+
+// Struct for holding band information, used by zbx_radio_control_impl.
+// This information should be selected base on requested tune frequency, and should not be
+// changed once initialized.
+struct tune_map_item_t
+{
+ double min_band_freq;
+ double max_band_freq;
+ uint8_t rf_fir;
+ uint8_t if1_fir;
+ uint8_t if2_fir;
+ int mix1_m;
+ int mix1_n;
+ int mix2_m;
+ int mix2_n;
+ double if1_freq_min;
+ double if1_freq_max;
+ double if2_freq_min;
+ double if2_freq_max;
+};
+
+// These are addresses for the various table-based registers
+static constexpr uint32_t ATR_ADDR_0X = 0;
+static constexpr uint32_t ATR_ADDR_RX = 1;
+static constexpr uint32_t ATR_ADDR_TX = 2;
+static constexpr uint32_t ATR_ADDR_XX = 3; // Full-duplex
+// Helper for looping
+static constexpr std::array<uint32_t, 4> ATR_ADDRS{0, 1, 2, 3};
+
+// Turn clang-formatting off so it doesn't compress these tables into a mess.
+// clang-format off
+static const std::vector<tune_map_item_t> rx_tune_map = {
+// | min_band_freq | max_band_freq | rf_fir | if1_fir | if2_fir | mix1 m, n | mix2 m, n | if1_freq_min | if1_freq_max | if2_freq_min | if2_freq_max |
+ { 1e6, 200e6, 1, 1, 2, -1, 1, -1, 1, 4100e6, 4100e6, 1850e6, 1850e6 },
+ { 200e6, 400e6, 1, 1, 2, -1, 1, -1, 1, 4100e6, 4100e6, 1850e6, 1850e6 },
+ { 400e6, 500e6, 1, 1, 2, -1, 1, -1, 1, 4100e6, 4100e6, 1850e6, 1850e6 },
+ { 500e6, 900e6, 1, 1, 2, -1, 1, -1, 1, 4100e6, 4100e6, 1850e6, 1850e6 },
+ { 900e6, 1800e6, 1, 1, 2, -1, 1, -1, 1, 4100e6, 4100e6, 2150e6, 2150e6 },
+ { 1800e6, 2300e6, 2, 1, 1, -1, 1, -1, 1, 4100e6, 4100e6, 1060e6, 1060e6 },
+ { 2300e6, 2700e6, 3, 1, 1, -1, 1, -1, 1, 4100e6, 3700e6, 1060e6, 1060e6 },
+ { 2700e6, 3000e6, 3, 4, 2, 1, -1, 1, -1, 7000e6, 7100e6, 2050e6, 2080e6 },
+ { 3000e6, 4200e6, 0, 1, 2, 0, 0, -1, 1, 0, 0, 1850e6, 1850e6 },
+ { 4200e6, 4500e6, 0, 2, 2, 0, 0, -1, 1, 0, 0, 1850e6, 1850e6 },
+ { 4500e6, 4700e6, 0, 2, 1, 0, 0, -1, 1, 0, 0, 1060e6, 1060e6 },
+ { 4700e6, 5300e6, 0, 2, 1, 0, 0, -1, 1, 0, 0, 1060e6, 1060e6 },
+ { 5300e6, 5600e6, 0, 2, 1, 0, 0, 1, -1, 0, 0, 1060e6, 1060e6 },
+ { 5600e6, 6800e6, 0, 3, 1, 0, 0, 1, -1, 0, 0, 1060e6, 1060e6 },
+ { 6800e6, 7400e6, 0, 4, 1, 0, 0, 1, -1, 0, 0, 1060e6, 1060e6 },
+ { 7400e6, 8000e6, 0, 4, 2, 0, 0, 1, -1, 0, 0, 1850e6, 1850e6 },
+};
+
+static const std::vector<tune_map_item_t> tx_tune_map = {
+// | min_band_freq | max_band_freq | rf_fir | if1_fir | if2_fir | mix1 m, n | mix2 m, n | if1_freq_min | if1_freq_max | if2_freq_min | if2_freq_max |
+ { 1e6, 200e6, 1, 2, 1, -1, 1, 1, -1, 4600e6, 4600e6, 1060e6, 1060e6 },
+ { 200e6, 300e6, 1, 2, 1, -1, 1, 1, -1, 4600e6, 4600e6, 1060e6, 1060e6 },
+ { 300e6, 400e6, 1, 2, 1, -1, 1, 1, -1, 4600e6, 4600e6, 1060e6, 1060e6 },
+ { 400e6, 600e6, 1, 2, 1, -1, 1, 1, -1, 4600e6, 4600e6, 1060e6, 1060e6 },
+ { 600e6, 800e6, 1, 2, 1, -1, 1, 1, -1, 4600e6, 4600e6, 1060e6, 1060e6 },
+ { 800e6, 1300e6, 1, 2, 1, -1, 1, 1, -1, 4600e6, 4600e6, 1060e6, 1060e6 },
+ { 1300e6, 1800e6, 1, 2, 1, -1, 1, 1, -1, 4600e6, 4600e6, 1060e6, 1060e6 },
+ { 1800e6, 2300e6, 2, 1, 1, -1, 1, -1, 1, 4100e6, 4100e6, 1060e6, 1060e6 },
+ { 2300e6, 2700e6, 3, 1, 2, -1, 1, -1, 1, 3700e6, 3700e6, 2070e6, 2200e6 },
+ { 2700e6, 3000e6, 3, 5, 2, 1, -1, 1, -1, 6800e6, 7100e6, 2000e6, 2000e6 },
+ { 3000e6, 4030e6, 0, 1, 2, 0, 0, -1, 1, 0, 0, 2050e6, 2370e6 },
+ { 4030e6, 4500e6, 0, 1, 1, 0, 0, -1, 1, 0, 0, 1060e6, 1060e6 },
+ { 4500e6, 4900e6, 0, 2, 1, 0, 0, 1, -1, 0, 0, 1060e6, 1060e6 },
+ { 4900e6, 5100e6, 0, 2, 1, 0, 0, 1, -1, 0, 0, 1060e6, 1060e6 },
+ { 5100e6, 5700e6, 0, 3, 2, 0, 0, 1, -1, 0, 0, 1900e6, 2300e6 },
+ { 5700e6, 6100e6, 0, 4, 2, 0, 0, 1, -1, 0, 0, 2300e6, 2500e6 },
+ { 6100e6, 6400e6, 0, 4, 2, 0, 0, 1, -1, 0, 0, 2400e6, 2500e6 },
+ { 6400e6, 7000e6, 0, 5, 2, 0, 0, 1, -1, 0, 0, 1900e6, 1950e6 },
+ { 7000e6, 7400e6, 0, 6, 1, 0, 0, 1, -1, 0, 0, 1060e6, 1060e6 },
+ { 7400e6, 8000e6, 0, 6, 2, 0, 0, 1, -1, 0, 0, 1950e6, 2050e6 },
+};
+
+// Turn clang-format back on just for posterity
+// clang-format on
+
+}}} // namespace uhd::usrp::zbx
+
+
+namespace uhd { namespace experts {
+// << Operator overload for expert's node printing (zbx_lo_source_t property)
+// Any added expert nodes of type enum class will have to define this
+std::ostream& operator<<(
+ std::ostream& os, const ::uhd::usrp::zbx::zbx_lo_source_t& lo_source);
+}} // namespace uhd::experts
diff --git a/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_cpld_ctrl.hpp b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_cpld_ctrl.hpp
new file mode 100644
index 000000000..03f0fa5b7
--- /dev/null
+++ b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_cpld_ctrl.hpp
@@ -0,0 +1,487 @@
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include "zbx_constants.hpp"
+#include "zbx_lo_ctrl.hpp"
+#include <uhd/types/direction.hpp>
+#include <uhd/types/serial.hpp>
+#include <uhd/types/time_spec.hpp>
+#include <unordered_map>
+#include <zbx_cpld_regs.hpp>
+#include <array>
+#include <functional>
+#include <mutex>
+
+namespace uhd { namespace usrp { namespace zbx {
+
+/*! ZBX CPLD Control Class
+ *
+ * A note on table indexing: Many settings take an index paramater, usually
+ * called 'idx'. These settings can be configured in 256 different ways. The
+ * configuration that is chosen out of those 256 different ones depends on the
+ * current config register (see get_current_config()). This register itself
+ * depends on the ATR mode the CPLD is in.
+ * When the ATR mode is atr_mode::CLASSIC_ATR, then only the first four indexes
+ * are used, and depend on the current ATR state of the radio (RX, TX, full duplex,
+ * or idle). If the mode is atr_mode::FPGA_STATE, then only the first 16 indexes
+ * are used, and the setting that is applied follows the four ATR pins with no
+ * specific mapping to RX or TX states. If the mode is atr_mode::SW_DEFINED,
+ * then the ATR pins are ignored, and the state is configured by set_sw_config().
+ */
+class zbx_cpld_ctrl
+{
+public:
+ enum chan_t { CHAN0, CHAN1, BOTH_CHANS, NO_CHAN };
+ enum spi_xact_t { READ, WRITE };
+ // Note: The values in this enum must match the values in the CPLD regmaps.
+ enum class atr_mode { SW_DEFINED = 0, CLASSIC_ATR = 1, FPGA_STATE = 2 };
+ enum class dsa_type { DSA1, DSA2, DSA3A, DSA3B };
+ enum class atr_mode_target { DSA, PATH_LED };
+
+ // The RX gain settings have four DSAs
+ using rx_dsa_type = std::array<uint32_t, 4>;
+ // The TX gain settings have two DSAs and one amp-path with 3 possible
+ // settings.
+ using tx_dsa_type = std::array<uint32_t, 3>;
+
+ using poke_fn_type =
+ std::function<void(const uint32_t, const uint32_t, const chan_t)>;
+ using peek_fn_type = std::function<uint32_t(const uint32_t)>;
+ using sleep_fn_type = std::function<void(const uhd::time_spec_t&)>;
+
+ //! Maps a DSA name ("DSA1", "DSA2", etc.) to its equivalent dsa_type
+ static const std::unordered_map<std::string, dsa_type> dsa_map;
+
+ zbx_cpld_ctrl(poke_fn_type&& poke_fn,
+ peek_fn_type&& peek_fn,
+ sleep_fn_type&& sleep_fn,
+ const std::string& log_id);
+
+ ~zbx_cpld_ctrl(void) = default;
+
+ //! Write a value to the scratch register
+ void set_scratch(const uint32_t value);
+
+ //! Read back the value from the scratch register
+ uint32_t get_scratch();
+
+ /*! Configure the ATR mode of a channel
+ *
+ * This configures how the DSAs, LEDs, and switches are controlled by the
+ * ATR pins going into the CPLD.
+ *
+ * See the CPLD register map for more information. In a nutshell, this will
+ * define how the current config register is populated (ATR pins or
+ * set_sw_config()).
+ *
+ * \param channel The channel for which this setting applies (either 0 or 1)
+ * \param target The target for this setting. With atr_mode_target::DSA, it
+ * will change the mode for the attenuators. With PATH_LED, it
+ * will change the mode for the RF path and LED controls.
+ * \param mode The ATR mode for this channel and target.
+ */
+ void set_atr_mode(
+ const size_t channel, const atr_mode_target target, const atr_mode mode);
+
+ /*! Choose the SW configuration of the CPLD
+ *
+ * When the ATR mode is anything other than SW_DEFINED, this has no effect.
+ * When the ATR mode is SW_DEFINED, this will choose which DSA/LED/switch
+ * configuration to apply to hardware.
+ *
+ * \param channel The RF channel for which this applies (0 or 1)
+ * \param target The target for this setting. With atr_mode_target::DSA, it
+ * will change the mode for the attenuators. With PATH_LED, it
+ * will change the mode for the RF path and LED controls.
+ * \param rf_config The selected RF configuration
+ */
+ void set_sw_config(
+ const size_t channel, const atr_mode_target target, const uint8_t rf_config);
+
+ /*! Read back the current config register
+ *
+ * \param channel The RF channel for which this applies (0 or 1)
+ * \param target The target for this setting. With atr_mode_target::DSA, it
+ * will change the mode for the attenuators. With PATH_LED, it
+ * will change the mode for the RF path and LED controls.
+ */
+ uint8_t get_current_config(const size_t channel, const atr_mode_target target);
+
+ /*! Set all RX DSAs directly
+ *
+ * This will directly update the DSA tables at the given index. In other
+ * words, this setting will directly be applied to hardware.
+ *
+ * \param channel daughterboard channel to program
+ * \param idx Table index
+ * \param dsa_steps DSA step values
+ */
+ void set_tx_gain_switches(
+ const size_t channel, const uint8_t idx, const tx_dsa_type& dsa_steps);
+
+ /*! Set all TX DSAs directly
+ *
+ * This will directly update the DSA tables at the given index. In other
+ * words, this setting will directly be applied to hardware.
+ *
+ * \param channel daughterboard channel to program
+ * \param idx Table index
+ * \param dsa_steps DSA step values
+ */
+ void set_rx_gain_switches(
+ const size_t channel, const uint8_t idx, const rx_dsa_type& dsa_steps);
+
+ /*! Set all RX DSAs using the CPLD table
+ *
+ * This will read DSA settings from the lookup table at position \p table_idx
+ * and write them to the DSAs at position \p idx.
+ *
+ * \param channel daughterboard channel to program
+ * \param idx DSA table index
+ * \param table_idx Lookup table index
+ */
+ void set_rx_gain_switches(
+ const size_t channel, const uint8_t idx, const uint8_t table_idx);
+
+ /*! Set all TX DSAs using the CPLD table
+ *
+ * This will read DSA settings from the lookup table at position \p table_idx
+ * and write them to the DSAs at position \p idx.
+ *
+ * \param channel daughterboard channel to program
+ * \param idx DSA table index
+ * \param table_idx Lookup table index
+ */
+ void set_tx_gain_switches(
+ const size_t channel, const uint8_t idx, const uint8_t table_idx);
+
+ /*! Set a specific TX DSA
+ *
+ * \returns the coerced value that's written to the DSA
+ */
+ uint8_t set_tx_dsa(const size_t channel,
+ const uint8_t idx,
+ const dsa_type tx_dsa,
+ const uint8_t att);
+
+ /*! Set a specific RX DSA
+ *
+ * \returns the coerced value that's written to the DSA
+ */
+ uint8_t set_rx_dsa(const size_t channel,
+ const uint8_t idx,
+ const dsa_type rx_dsa,
+ const uint8_t att);
+
+ /*! Set a specific TX DSA
+ *
+ * \returns the coerced value that's written to the DSA
+ */
+ uint8_t get_tx_dsa(const size_t channel,
+ const uint8_t idx,
+ const dsa_type tx_dsa,
+ const bool update_cache = false);
+
+ /*! Set a specific RX DSA
+ *
+ * \returns the coerced value that's written to the DSA
+ */
+ uint8_t get_rx_dsa(const size_t channel,
+ const uint8_t idx,
+ const dsa_type rx_dsa,
+ const bool update_cache = false);
+
+ /*! Setting switches required for antenna mode switching, transmitting side
+ *
+ * Note: If the antenna is set to TX/RX, this also configures the TX
+ * amplifier. This unfortunate API coupling is due to the fact that the
+ * same switch that chooses the antenna path also switches the amplifier in
+ * and out.
+ *
+ * \param channel daughterboard channel to program
+ * \param idx Table index
+ * \param amp The amplifier configuration
+ * \param antenna desired antenna mode
+ */
+ void set_tx_antenna_switches(const size_t channel,
+ const uint8_t idx,
+ const std::string& antenna,
+ const tx_amp amp);
+
+ /*! Setting switches required for antenna mode switching, receiving side
+ *
+ * \param channel daughterboard channel to program
+ * \param idx Table index
+ * \param gain desired antenna mode
+ * \param is_highband highband or lowband settings
+ */
+ void set_rx_antenna_switches(
+ const size_t channel, const uint8_t idx, const std::string& antenna);
+
+ /*! Return the current amp settings
+ */
+ tx_amp get_tx_amp_settings(
+ const size_t channel, const uint8_t idx, const bool update_cache);
+
+ /*! Setting switches required for rf filter changes, receiving side
+ *
+ * \param channel daughterboard channel to program
+ * \param idx Table index
+ * \param rf_fir rf filter value
+ */
+ void set_rx_rf_filter(
+ const size_t channel, const uint8_t idx, const uint8_t rf_fir);
+
+ /*! Setting switches required for if1 filter changes, receiving side
+ *
+ * \param channel daughterboard channel to program
+ * \param idx Table index
+ * \param if1_fir if1 filter value
+ */
+ void set_rx_if1_filter(
+ const size_t channel, const uint8_t idx, const uint8_t if1_fir);
+
+ /*! Setting switches required for if2 filter changes, receiving side
+ *
+ * \param channel daughterboard channel to program
+ * \param idx Table index
+ * \param if2_fir if2 filter value
+ */
+ void set_rx_if2_filter(
+ const size_t channel, const uint8_t idx, const uint8_t if2_fir);
+
+ /*! Setting switches required for rf filter changes, transmitting side
+ *
+ * \param channel daughterboard channel to program
+ * \param idx Table index
+ * \param rf_fir rf filter value
+ */
+ void set_tx_rf_filter(
+ const size_t channel, const uint8_t idx, const uint8_t rf_fir);
+
+ /*! Setting switches required for if1 filter changes, transmitting side
+ *
+ * \param channel daughterboard channel to program
+ * \param idx Table index
+ * \param if1_fir if1 filter value
+ */
+ void set_tx_if1_filter(
+ const size_t channel, const uint8_t idx, const uint8_t if1_fir);
+
+ /*! Setting switches required for if2 filter changes, transmitting side
+ *
+ * \param channel daughterboard channel to program
+ * \param idx Table index
+ * \param if2_fir if2 filter value
+ */
+ void set_tx_if2_filter(
+ const size_t channel, const uint8_t idx, const uint8_t if2_fir);
+
+ /**************************************************************************
+ * LED controls
+ *************************************************************************/
+ /*! Turn the LEDs on or off
+ *
+ * There are two LEDs on ZBX, the TRX LED has a red and green component.
+ *
+ * Note that toggling any of the LED settings to 'true' won't necessarily
+ * turn on the LED. The current CPLD config register for this channel must
+ * also match \p idx in order for this state to be applied.
+ *
+ * \param channel The channel for which these settings apply
+ * \param idx The LED table index that is configured
+ * \param rx On-state of the green RX2 LED
+ * \param trx_rx On-state of the green TX/RX LED
+ * \param trx_tx On-state of the red TX/RX LED
+ */
+ void set_leds(const size_t channel,
+ const uint8_t idx,
+ const bool rx,
+ const bool trx_rx,
+ const bool trx_tx);
+
+ /**************************************************************************
+ * LO controls
+ *************************************************************************/
+ //! Write to a register on an LO
+ //
+ // Note: All eight LOs are accessed through the same CPLD register. For
+ // timed commands to the LOs, it is up to the call site to ensure that SPI
+ // writes/reads do not get interleaved.
+ //
+ // Note: This will not poll the ready bit of the CPLD. To ensure valid
+ // transactions, either manually call lo_spi_ready(), or make sure that SPI
+ // commands are timed appropriately, i.e., new SPI transaction requests reach
+ // the CPLD only after the previous transaction is comppleted.
+ //
+ // \param lo Which LO to write to.
+ // \param addr The address of the LO register (see the LMX2572 datasheet)
+ // \param data The data to write to the LO register (see the LMX2572 datasheet)
+ void lo_poke16(const zbx_lo_t lo, const uint8_t addr, const uint16_t data);
+
+ //! Read back from the LO
+ //
+ // Note: The LMX2572 has a MUXout pin, not just an SDO pin. This means the
+ // call site needs to ensure that MUXout configuration is in the correct
+ // state before calling this function (to either read back the lock status,
+ // or the SPI read return value).
+ //
+ // Note: This will not poll the ready bit of the CPLD. To ensure valid
+ // transactions, either manually call lo_spi_ready(), or make sure that SPI
+ // commands are timed appropriately, i.e., new SPI transaction requests reach
+ // the CPLD only after the previous transaction is comppleted.
+ //
+ // \param lo Which LO to read from
+ // \param addr Which address on the LO to read from (see LMX2572 datasheet)
+ // \param valid_timeout_ms After triggering the transaction, the function will
+ // wait for this many ms before throwing an exception. A zero timeout
+ // is possible, which means the first read to the LO_SPI_STATUS
+ // register must already have the ready bit high.
+ uint16_t lo_peek16(const zbx_lo_t lo, const uint8_t addr);
+
+ //! Returns true if the LO_SPI_READY bit is high, i.e., the LO SPI is ready
+ // for a transaction
+ bool lo_spi_ready();
+
+ //! LO's incoming source control (external/internal) is actually found in
+ // the CPLD path control register spaces
+ //
+ // \param idx Table index
+ // \param lo Which LO to read from
+ // \param lo_source Set LO source to internal/external
+ void set_lo_source(
+ const size_t idx, const zbx_lo_t lo, const zbx_lo_source_t lo_source);
+
+ //! Retrieve lo source
+ // \param idx Table index to read from
+ // \param lo Which LO to read from
+ zbx_lo_source_t get_lo_source(const size_t idx, zbx_lo_t lo);
+
+ //! Synchronize LOs
+ //
+ // This will assert a SYNC pulse on all the LOs listed in \p los.
+ //
+ // Note: This function will throw an exception if LO sync bypass is enabled
+ // (see set_lo_sync_bypass()).
+ //
+ // A note on timing: Like most CPLD controls, the time is inherited from the
+ // underlying register interface. That is to say, the APIs don't take a time
+ // as an argument, but assume the command time is correctly applied.
+ // The different channels of the ZBX (channel 0/1) may have different command
+ // times. Because this API potentially affects both channels at once, the
+ // channel index must be provided to determine which channel's time should
+ // be used.
+ //
+ // \param ref_chan The channel that is used as a timing reference.
+ // \param los A list of LOs to synchronize
+ // \throws uhd::runtime_error if LO sync bypass is enabled.
+ void pulse_lo_sync(const size_t ref_chan, const std::vector<zbx_lo_t>& los);
+
+ //! Enable/disable LO sync bypass
+ //
+ // This is a ZBX-specific option, which will allow synchronizing the LOs via
+ // the MB_SYNTH_SYNC pin instead of using a register. Enabling this will
+ // disable the ability to call pulse_lo_sync().
+ //
+ // \param enable If true, enables the bypass. When false, disables the bypass
+ // and pulse_lo_sync() can be called.
+ void set_lo_sync_bypass(const bool enable);
+
+ /*! Write DSA table for TX frequency to DB CPLD
+ */
+ void update_tx_dsa_settings(
+ const std::vector<uint32_t>& dsa1_table, const std::vector<uint32_t>& dsa2_table);
+
+ /*! Write DSA table for RX frequency to DB CPLD
+ */
+ void update_rx_dsa_settings(const std::vector<uint32_t>& dsa1_table,
+ const std::vector<uint32_t>& dsa2_table,
+ const std::vector<uint32_t>& dsa3a_table,
+ const std::vector<uint32_t>& dsa3b_table);
+
+private:
+ /*! Dump the state of the registers into the CPLD
+ *
+ * \param chan Which channel does this change pertain to? This is forwarded
+ * to _poke32().
+ * \param save_all If true, save all registers. If false, only change those
+ * that changed since last save_state() call.
+ * Note that if save_all is true, the chan parameter does not
+ * really apply, because all registers (for all channels) are
+ * written to. Therefore, only use save_all==true in
+ * combination with NO_CHAN.
+ */
+ void commit(const chan_t chan = NO_CHAN, const bool save_all = false);
+
+ /*! Update a register field by peeking the corresponding CPLD register
+ *
+ * This will synchronize the state of the internal register cache with the
+ * actual value from the CPLD. This will incur a single peek (non-timed) to
+ * the chip before returning.
+ */
+ void update_field(const zbx_cpld_regs_t::zbx_cpld_field_t field, const size_t idx);
+
+ /*! Perform an LO SPI transaction (interact with the LO_SPI_STATUS register)
+ *
+ * Note: This has the ability to throttle the SPI transactions. The reason
+ * is that the peek/poke interface from UHD to the CPLD is faster than the
+ * SPI interface from the CPLD to the LO. If two SPI writes were to be
+ * sent without a throttle, the second one would clobber the first. Never
+ * call this with throttle == false if another SPI transaction is following!
+ *
+ * \param lo Which LO to address
+ * \param addr 7-bit address of the LO's register
+ * \param data 16-bit data to write (can be empty for reads)
+ * \param write If true, write, else read
+ * \param throttle If true, wait after writing, so that a following SPI
+ * transaction won't clobber the previous one
+ */
+ void _lo_spi_transact(const zbx_lo_t lo,
+ const uint8_t addr,
+ const uint16_t data,
+ const spi_xact_t xact_type,
+ const bool throttle = true);
+
+ /*! Write a list of values to a register.
+ * The list start address is searched using reg_addr_name.
+ * The method will raise an exception if values is longer than
+ * the register size.
+ * The caller is responsible to commit the data once values are written.
+ * This allows multiple vector writes with a single commit.
+ */
+ void write_register_vector(
+ const std::string& reg_addr_name, const std::vector<uint32_t>& values);
+
+ //! Poker object
+ poke_fn_type _poke32;
+
+ //! Peeker object
+ peek_fn_type _peek32;
+
+ //! Hardware-timed sleep, used to throttle pokes
+ sleep_fn_type _sleep;
+
+ // Address offset (on top of _db_cpld_offset) where the LO SPI register is
+ const uint32_t _lo_spi_offset;
+
+ // infos about the daughtherboard revision
+ std::string _db_rev_info;
+
+ // Cached register state
+ zbx_cpld_regs_t _regs = zbx_cpld_regs_t();
+
+ const std::string _log_id;
+};
+
+}}} // namespace uhd::usrp::zbx
+
+namespace uhd { namespace experts {
+// << Operator overload for expert's node printing (zbx_lo_source_t property)
+// Any added expert nodes of type enum class will have to define this
+std::ostream& operator<<(
+ std::ostream& os, const ::uhd::usrp::zbx::zbx_cpld_ctrl::atr_mode& lo_source);
+}} // namespace uhd::experts
diff --git a/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_dboard.hpp b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_dboard.hpp
new file mode 100644
index 000000000..619c4a05f
--- /dev/null
+++ b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_dboard.hpp
@@ -0,0 +1,416 @@
+//
+// Copyright 2019 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include "zbx_constants.hpp"
+#include "zbx_cpld_ctrl.hpp"
+#include "zbx_expert.hpp"
+#include "zbx_lo_ctrl.hpp"
+#include <uhd/cal/dsa_cal.hpp>
+#include <uhd/property_tree.hpp>
+#include <uhd/rfnoc/register_iface.hpp>
+#include <uhd/rfnoc/registry.hpp>
+#include <uhd/types/device_addr.hpp>
+#include <uhd/types/direction.hpp>
+#include <uhd/types/eeprom.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/types/wb_iface.hpp>
+#include <uhdlib/experts/expert_factory.hpp>
+#include <uhdlib/rfnoc/rf_control/dboard_iface.hpp>
+#include <uhdlib/usrp/common/mpmd_mb_controller.hpp>
+#include <uhdlib/usrp/common/pwr_cal_mgr.hpp>
+#include <uhdlib/usrp/common/rpc.hpp>
+#include <uhdlib/usrp/common/x400_rfdc_control.hpp>
+#include <uhdlib/usrp/dboard/x400_dboard_iface.hpp>
+#include <uhdlib/utils/rpc.hpp>
+#include <stddef.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+using namespace uhd::rfnoc;
+
+namespace uhd { namespace usrp { namespace zbx {
+
+const static uint16_t ZBX_PID = 0x4002;
+
+/*! Provide access to a ZBX radio.
+ */
+class zbx_dboard_impl : public uhd::usrp::x400::x400_dboard_iface
+{
+public:
+ using sptr = std::shared_ptr<zbx_dboard_impl>;
+ using time_accessor_fn_type = std::function<uhd::time_spec_t(size_t)>;
+
+ /************************************************************************
+ * Structors
+ ***********************************************************************/
+ zbx_dboard_impl(register_iface& reg_iface,
+ const size_t reg_base_address,
+ time_accessor_fn_type&& time_accessor,
+ const size_t db_idx,
+ const std::string& radio_slot,
+ const std::string& rpc_prefix,
+ const std::string& unique_id,
+ uhd::usrp::x400_rpc_iface::sptr mb_rpcc,
+ uhd::usrp::zbx_rpc_iface::sptr rpcc,
+ uhd::rfnoc::x400::rfdc_control::sptr rfdcc,
+ uhd::property_tree::sptr tree);
+ virtual ~zbx_dboard_impl();
+
+ size_t get_chan_from_dboard_fe(
+ const std::string& fe, const uhd::direction_t) const override;
+ std::string get_dboard_fe_from_chan(
+ const size_t chan, const uhd::direction_t) const override;
+
+ /************************************************************************
+ * node_t && noc_block_base API calls
+ ***********************************************************************/
+ void deinit();
+
+ void set_command_time(uhd::time_spec_t time, const size_t chan) override;
+
+ /************************************************************************
+ * API calls
+ ***********************************************************************/
+
+ bool is_adc_self_cal_supported() override
+ {
+ return true;
+ }
+
+ uhd::usrp::x400::adc_self_cal_params_t get_adc_self_cal_params(const double tone_freq) override
+ {
+ // This is chosen such that the IF2 frequency is 1.06G
+ const double rx_freq = 4.7e9 - 5.12e6;
+ const double if2_freq = 1.06e9;
+ const double offset = tone_freq - if2_freq;
+
+ // Minus because this zone is inverted
+ const double tx_freq = rx_freq - offset;
+ return {
+ 10.0, // min_gain
+ 50.0, // max_gain
+ rx_freq, // rx_freq
+ tx_freq, // tx_freq
+ };
+ }
+
+ rf_control::gain_profile_iface::sptr get_tx_gain_profile_api() override
+ {
+ return _tx_gain_profile_api;
+ }
+ rf_control::gain_profile_iface::sptr get_rx_gain_profile_api() override
+ {
+ return _rx_gain_profile_api;
+ }
+
+ void set_tx_antenna(const std::string& ant, const size_t chan) override;
+ void set_rx_antenna(const std::string& ant, const size_t chan) override;
+ std::vector<std::string> get_tx_antennas(const size_t /*chan*/) const override
+ {
+ return TX_ANTENNAS;
+ }
+ std::vector<std::string> get_rx_antennas(const size_t /*chan*/) const override
+ {
+ return RX_ANTENNAS;
+ }
+
+ double set_tx_frequency(const double freq, const size_t chan) override;
+ double set_rx_frequency(const double freq, const size_t chan) override;
+ uhd::freq_range_t get_tx_frequency_range(const size_t /*chan*/) const override
+ {
+ return ZBX_FREQ_RANGE;
+ }
+ uhd::freq_range_t get_rx_frequency_range(const size_t /*chan*/) const override
+ {
+ return ZBX_FREQ_RANGE;
+ }
+
+ double set_tx_bandwidth(const double bandwidth, const size_t chan) override;
+ double set_rx_bandwidth(const double bandwidth, const size_t chan) override;
+ uhd::meta_range_t get_tx_bandwidth_range(size_t chan) const override
+ {
+ return _tree
+ ->access<uhd::meta_range_t>(
+ _get_frontend_path(TX_DIRECTION, chan) / "bandwidth" / "range")
+ .get();
+ }
+ uhd::meta_range_t get_rx_bandwidth_range(size_t chan) const override
+ {
+ return _tree
+ ->access<uhd::meta_range_t>(
+ _get_frontend_path(RX_DIRECTION, chan) / "bandwidth" / "range")
+ .get();
+ }
+
+ double set_tx_gain(const double gain, const size_t chan) override;
+ double set_tx_gain(
+ const double gain, const std::string& name, const size_t chan) override;
+ double set_rx_gain(const double gain, const size_t chan) override;
+ double set_rx_gain(
+ const double gain, const std::string& name, const size_t chan) override;
+ double get_rx_gain(const size_t chan) override;
+ double get_tx_gain(const size_t chan) override;
+ double get_rx_gain(const std::string& name, const size_t chan) override;
+ double get_tx_gain(const std::string& name, const size_t chan) override;
+
+ uhd::gain_range_t get_tx_gain_range(const size_t /*chan*/) const override
+ {
+ return ZBX_TX_GAIN_RANGE;
+ }
+ uhd::gain_range_t get_rx_gain_range(const size_t /*chan*/) const override
+ {
+ // FIXME This should return a ZBX_RX_LOW_FREQ_GAIN_RANGE when freq is
+ // low, but this function is const
+ return ZBX_RX_GAIN_RANGE;
+ }
+
+ // LO Property Getters
+ std::vector<std::string> get_tx_lo_names(const size_t /*chan*/) const
+ {
+ return ZBX_LOS;
+ }
+ std::vector<std::string> get_rx_lo_names(const size_t /*chan*/) const
+ {
+ return ZBX_LOS;
+ }
+ std::vector<std::string> get_tx_lo_sources(
+ const std::string& /*name*/, const size_t /*chan*/) const
+ {
+ return std::vector<std::string>{"internal", "external"};
+ }
+ std::vector<std::string> get_rx_lo_sources(
+ const std::string& /*name*/, const size_t /*chan*/) const
+ {
+ return std::vector<std::string>{"internal", "external"};
+ }
+
+ // LO Frequency Control
+ double set_tx_lo_freq(
+ const double freq, const std::string& name, const size_t chan) override;
+ double set_rx_lo_freq(
+ const double freq, const std::string& name, const size_t chan) override;
+ double get_tx_lo_freq(const std::string& name, const size_t chan) override;
+ double get_rx_lo_freq(const std::string& name, size_t chan) override;
+
+ // LO Source Control
+ void set_tx_lo_source(
+ const std::string& src, const std::string& name, const size_t chan) override;
+ void set_rx_lo_source(
+ const std::string& src, const std::string& name, const size_t chan) override;
+ const std::string get_tx_lo_source(
+ const std::string& name, const size_t chan) override;
+ const std::string get_rx_lo_source(
+ const std::string& name, const size_t chan) override;
+
+ uhd::freq_range_t get_rx_lo_freq_range(
+ const std::string& name, const size_t chan) const override
+ {
+ return _get_lo_freq_range(name, chan);
+ }
+
+ // TODO: Why is this not const?
+ uhd::freq_range_t get_tx_lo_freq_range(
+ const std::string& name, const size_t chan) override
+ {
+ return _get_lo_freq_range(name, chan);
+ }
+
+ void set_rx_lo_export_enabled(
+ const bool enabled, const std::string& name, const size_t chan) override;
+ bool get_rx_lo_export_enabled(
+ const std::string& name, const size_t chan) override;
+ void set_tx_lo_export_enabled(
+ const bool enabled, const std::string& name, const size_t chan) override;
+ bool get_tx_lo_export_enabled(const std::string& name, const size_t chan) override;
+
+
+ /******************************************************************************
+ * EEPROM API
+ *****************************************************************************/
+ eeprom_map_t get_db_eeprom() override;
+
+ /**************************************************************************
+ * Radio Identification API Calls
+ *************************************************************************/
+
+ std::string get_tx_antenna(size_t chan) const override;
+ std::string get_rx_antenna(size_t chan) const override;
+ double get_tx_frequency(size_t chan) override;
+ double get_rx_frequency(size_t chan) override;
+ double get_rx_bandwidth(size_t chan) override;
+ double get_tx_bandwidth(size_t chan) override;
+ void set_tx_tune_args(const uhd::device_addr_t&, const size_t) override;
+ void set_rx_tune_args(const uhd::device_addr_t&, const size_t) override;
+ std::vector<std::string> get_tx_gain_names(size_t) const override;
+ std::vector<std::string> get_rx_gain_names(size_t) const override;
+
+ uhd::gain_range_t get_tx_gain_range(
+ const std::string& name, const size_t chan) const override;
+
+ uhd::gain_range_t get_rx_gain_range(
+ const std::string& name, const size_t chan) const override;
+
+ void set_rx_agc(const bool, const size_t) override;
+
+ std::vector<uhd::usrp::pwr_cal_mgr::sptr>& get_pwr_mgr(uhd::direction_t trx) override;
+
+private:
+ uhd::property_tree::sptr get_tree()
+ {
+ return _tree;
+ }
+
+ // Expert map, keyed by the pair of tx/rx and channel
+ uhd::experts::expert_container::sptr _expert_container;
+
+ /**************************************************************************
+ * Helpers
+ *************************************************************************/
+ //! Initialize DB-CPLD
+ void _init_cpld();
+
+ //! Initialize all the peripherals connected to this block
+ void _init_peripherals();
+
+ //! Init a subtree for the RF frontends
+ void _init_frontend_subtree(uhd::property_tree::sptr subtree,
+ const uhd::direction_t trx,
+ const size_t chan_idx,
+ const fs_path fe_path);
+ //! Initializing the expert properties
+ void _init_frequency_prop_tree(uhd::property_tree::sptr subtree,
+ uhd::experts::expert_container::sptr expert,
+ const fs_path fe_path);
+ void _init_gain_prop_tree(uhd::property_tree::sptr subtree,
+ uhd::experts::expert_container::sptr,
+ const uhd::direction_t trx,
+ const size_t chan_idx,
+ const fs_path fe_path);
+ void _init_antenna_prop_tree(uhd::property_tree::sptr subtree,
+ uhd::experts::expert_container::sptr,
+ const uhd::direction_t trx,
+ const size_t chan_idx,
+ const fs_path fe_path);
+ void _init_programming_prop_tree(uhd::property_tree::sptr subtree,
+ uhd::experts::expert_container::sptr,
+ const fs_path fe_path);
+ void _init_lo_prop_tree(uhd::property_tree::sptr subtree,
+ uhd::experts::expert_container::sptr,
+ const uhd::direction_t trx,
+ const size_t chan_idx,
+ const fs_path fe_path);
+ //! Init all experts, bind to properties created above
+ void _init_experts(uhd::property_tree::sptr subtree,
+ uhd::experts::expert_container::sptr,
+ const uhd::direction_t trx,
+ const size_t chan_idx,
+ const fs_path fe_path);
+
+ uhd::usrp::pwr_cal_mgr::sptr _init_power_cal(uhd::property_tree::sptr subtree,
+ const uhd::direction_t trx,
+ const size_t chan_idx,
+ const fs_path fe_path);
+
+ //! Initialize property tree
+ void _init_prop_tree();
+ //! Init RPC interaction
+ void _init_mpm();
+
+ //! Set up sensor property nodes
+ void _init_mpm_sensors(const direction_t dir, const size_t chan_idx);
+
+ //! Get subtree path for a given direction/channel
+ fs_path _get_frontend_path(const direction_t dir, const size_t chan_idx) const;
+
+ // Get all los "lock status", per enabled && locked individual LOs
+ bool _get_all_los_locked(const direction_t dir, const size_t chan);
+
+ const std::string _unique_id;
+ std::string get_unique_id() const;
+
+ freq_range_t _get_lo_freq_range(const std::string& name, const size_t chan) const;
+
+ /**************************************************************************
+ * Private attributes
+ *************************************************************************/
+
+ static constexpr size_t _num_rx_chans = 2;
+ static constexpr size_t _num_tx_chans = 2;
+
+ //! Interface to the registers
+ uhd::rfnoc::register_iface& _regs;
+ const size_t _reg_base_address;
+
+ //! Interface to get the command time
+ time_accessor_fn_type _time_accessor;
+
+ //! Letter representation of the radio we're currently running
+ const std::string _radio_slot;
+
+ //! Index of this daughterboard
+ const size_t _db_idx;
+
+ // infos about the daughtherboard
+ std::vector<std::map<std::string, std::string>> _all_dboard_info;
+
+ //! Prepended for all dboard RPC calls
+ const std::string _rpc_prefix;
+
+ //! Reference to the MB controller
+ uhd::rfnoc::mpmd_mb_controller::sptr _mb_control;
+
+ //! Reference to wb_iface adapters
+ std::vector<uhd::timed_wb_iface::sptr> _wb_ifaces;
+
+ //! Reference to the RPC client
+ uhd::usrp::x400_rpc_iface::sptr _mb_rpcc;
+ uhd::usrp::zbx_rpc_iface::sptr _rpcc;
+
+ //! Reference to the RFDC controller
+ uhd::rfnoc::x400::rfdc_control::sptr _rfdcc;
+
+ //! Reference to the CPLD controls
+ std::shared_ptr<zbx_cpld_ctrl> _cpld;
+
+ //! Reference to all LO controls
+ std::map<zbx_lo_t, std::shared_ptr<zbx_lo_ctrl>> _lo_ctrl_map;
+
+ //! Reference to the TX Cal data
+ std::shared_ptr<uhd::usrp::cal::zbx_tx_dsa_cal> _tx_dsa_cal;
+
+ //! Reference to the RX Cal data
+ std::shared_ptr<uhd::usrp::cal::zbx_rx_dsa_cal> _rx_dsa_cal;
+
+ //! Reference to this block's subtree
+ //
+ // It is mutable because _tree->access<>(..).get() is not const, but we
+ // need to do just that in some const contexts
+ mutable uhd::property_tree::sptr _tree;
+
+ std::vector<uhd::usrp::pwr_cal_mgr::sptr> _rx_pwr_mgr;
+ std::vector<uhd::usrp::pwr_cal_mgr::sptr> _tx_pwr_mgr;
+
+ rf_control::gain_profile_iface::sptr _tx_gain_profile_api;
+ rf_control::gain_profile_iface::sptr _rx_gain_profile_api;
+
+ //! Store the current RX gain profile
+ std::vector<std::string> _rx_gain_profile = {
+ ZBX_GAIN_PROFILE_DEFAULT, ZBX_GAIN_PROFILE_DEFAULT};
+ //! Store the current TX gain profile
+ std::vector<std::string> _tx_gain_profile = {
+ ZBX_GAIN_PROFILE_DEFAULT, ZBX_GAIN_PROFILE_DEFAULT};
+
+ //! The sampling rate of the RFdc, typically something close to 3 GHz
+ const double _rfdc_rate;
+
+ //! The PLL reference rate, typically something in the 50 - 64 MHz range
+ const double _prc_rate;
+};
+
+}}} // namespace uhd::usrp::zbx
diff --git a/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_expert.hpp b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_expert.hpp
new file mode 100644
index 000000000..f386a4fdb
--- /dev/null
+++ b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_expert.hpp
@@ -0,0 +1,837 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include "zbx_constants.hpp"
+#include "zbx_cpld_ctrl.hpp"
+#include "zbx_lo_ctrl.hpp"
+#include <uhd/cal/container.hpp>
+#include <uhd/cal/dsa_cal.hpp>
+#include <uhd/property_tree.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhdlib/experts/expert_nodes.hpp>
+#include <uhdlib/rfnoc/rf_control/gain_profile_iface.hpp>
+#include <uhdlib/usrp/common/pwr_cal_mgr.hpp>
+#include <uhdlib/usrp/common/rpc.hpp>
+#include <uhdlib/usrp/common/x400_rfdc_control.hpp>
+#include <uhdlib/utils/rpc.hpp>
+#include <cmath>
+#include <memory>
+
+namespace uhd { namespace usrp { namespace zbx {
+
+namespace {
+
+//! Depending on the given \p lo_step_size, this will return a valid frequency
+// range on a quantized grid for the the LOs. The lower limit of this range will
+// never be smaller than LMX2572_MIN_FREQ and the upper frequency will never be
+// larger than LMX2572_MAX_FREQ. All frequencies will be integer multiples of
+// the given \p lo_step_size.
+uhd::freq_range_t _get_quantized_lo_range(const double lo_step_size)
+{
+ const double start = std::ceil(LMX2572_MIN_FREQ / lo_step_size) * lo_step_size;
+ const double stop = std::floor(LMX2572_MAX_FREQ / lo_step_size) * lo_step_size;
+ UHD_ASSERT_THROW(start >= LMX2572_MIN_FREQ);
+ UHD_ASSERT_THROW(stop <= LMX2572_MAX_FREQ);
+ return uhd::freq_range_t(start, stop, lo_step_size);
+}
+
+} // namespace
+
+/*!---------------------------------------------------------
+ * zbx_scheduling_expert
+ *
+ * This expert is responsible for scheduling time sensitive actions
+ * in other experts. It responds to changes in the command time and
+ * selectively causes experts to run in order to ensure a synchronized
+ * system.
+ *
+ * There is one scheduling expert per channel, they are shared between RX and TX.
+ * So, 2 scheduling experts total per radio block.
+ * ---------------------------------------------------------
+ */
+class zbx_scheduling_expert : public experts::worker_node_t
+{
+public:
+ zbx_scheduling_expert(const experts::node_retriever_t& db, const uhd::fs_path fe_path)
+ : experts::worker_node_t(fe_path / "zbx_scheduling_expert")
+ , _command_time(db, fe_path / "time/cmd")
+ , _frontend_time(db, fe_path / "time/fe")
+ {
+ bind_accessor(_command_time);
+ bind_accessor(_frontend_time);
+ }
+
+private:
+ virtual void resolve();
+
+ // Inputs
+ experts::data_reader_t<time_spec_t> _command_time;
+
+ // Outputs
+ experts::data_writer_t<time_spec_t> _frontend_time;
+};
+
+/*!---------------------------------------------------------
+ * zbx_freq_fe_expert (Frequency Front-end Expert)
+ *
+ * This expert is responsible for responding to user requests for center frequency tuning
+ *
+ * This should trigger:
+ * - relevant LO experts
+ * - adjacent MPM expert
+ * - adjacent CPLD (tx/rx) Programming expert
+ * After all of the above, the Frequency Backend expert should be triggered to returned
+ * the coerced center frequency
+ *
+ * One instance of this expert is required for each combination of Direction (TX/RX) and
+ * Channel (0,1); four total
+ * --------------------------------------------------------
+ */
+class zbx_freq_fe_expert : public uhd::experts::worker_node_t
+{
+public:
+ zbx_freq_fe_expert(const uhd::experts::node_retriever_t& db,
+ const uhd::fs_path fe_path,
+ const uhd::direction_t trx,
+ const size_t chan,
+ const double rfdc_rate,
+ const double lo_step_size)
+ : experts::worker_node_t(fe_path / "zbx_freq_fe_expert")
+ , _desired_frequency(db, fe_path / "freq" / "desired")
+ , _desired_lo1_frequency(db, fe_path / "los" / ZBX_LO1 / "freq" / "value" / "desired")
+ , _desired_lo2_frequency(db, fe_path / "los" / ZBX_LO2 / "freq" / "value" / "desired")
+ , _lo1_enabled(db, fe_path / ZBX_LO1 / "enabled")
+ , _lo2_enabled(db, fe_path / ZBX_LO2 / "enabled")
+ , _desired_if2_frequency(db, fe_path / "if_freq" / "desired")
+ , _band_inverted(db, fe_path / "band_inverted")
+ , _is_highband(db, fe_path / "is_highband")
+ , _mixer1_m(db, fe_path / "mixer1_m")
+ , _mixer1_n(db, fe_path / "mixer1_n")
+ , _mixer2_m(db, fe_path / "mixer2_m")
+ , _mixer2_n(db, fe_path / "mixer2_n")
+ , _rf_filter(db, fe_path / "rf" / "filter")
+ , _if1_filter(db, fe_path / "if1" / "filter")
+ , _if2_filter(db, fe_path / "if2" / "filter")
+ , _rfdc_rate(rfdc_rate)
+ , _lo_freq_range(_get_quantized_lo_range(lo_step_size))
+ , _trx(trx)
+ , _chan(chan)
+ {
+ // Inputs
+ bind_accessor(_desired_frequency);
+
+ // Outputs
+ bind_accessor(_desired_lo1_frequency);
+ bind_accessor(_desired_lo2_frequency);
+ bind_accessor(_lo1_enabled);
+ bind_accessor(_lo2_enabled);
+ bind_accessor(_desired_if2_frequency);
+ bind_accessor(_band_inverted);
+ bind_accessor(_is_highband);
+ bind_accessor(_mixer1_m);
+ bind_accessor(_mixer1_n);
+ bind_accessor(_mixer2_m);
+ bind_accessor(_mixer2_n);
+ bind_accessor(_rf_filter);
+ bind_accessor(_if1_filter);
+ bind_accessor(_if2_filter);
+ }
+
+private:
+ void resolve() override;
+
+ // Inputs from user/API
+ uhd::experts::data_reader_t<double> _desired_frequency;
+
+ // Outputs
+ // From calculation, to LO expert
+ uhd::experts::data_writer_t<double> _desired_lo1_frequency;
+ uhd::experts::data_writer_t<double> _desired_lo2_frequency;
+ uhd::experts::data_writer_t<bool> _lo1_enabled;
+ uhd::experts::data_writer_t<bool> _lo2_enabled;
+ // From calculation, to MPM/RPC expert
+ uhd::experts::data_writer_t<double> _desired_if2_frequency;
+ uhd::experts::data_writer_t<bool> _band_inverted;
+ // From calculation, to Frequency Backend expert
+ uhd::experts::data_writer_t<bool> _is_highband;
+ uhd::experts::data_writer_t<int> _mixer1_m;
+ uhd::experts::data_writer_t<int> _mixer1_n;
+ uhd::experts::data_writer_t<int> _mixer2_m;
+ uhd::experts::data_writer_t<int> _mixer2_n;
+ // From calculation, to CPLD Programming expert
+ uhd::experts::data_writer_t<int> _rf_filter;
+ uhd::experts::data_writer_t<int> _if1_filter;
+ uhd::experts::data_writer_t<int> _if2_filter;
+
+ const double _rfdc_rate;
+ const uhd::freq_range_t _lo_freq_range;
+ tune_map_item_t _tune_settings;
+ // Channel properties
+ const uhd::direction_t _trx;
+ const size_t _chan;
+};
+
+/*!---------------------------------------------------------
+ * zbx_freq_be_expert (Frequency Back-end Expert)
+ *
+ * This expert is responsible for calculating the final coerced frequency and returning it
+ * to the user
+ *
+ * This should trigger:
+ * - adjacent gain expert
+ *
+ * One instance of this expert is required for each combination of Direction (TX/RX) and
+ * Channel (0,1); four total
+ * --------------------------------------------------------
+ */
+class zbx_freq_be_expert : public uhd::experts::worker_node_t
+{
+public:
+ zbx_freq_be_expert(const uhd::experts::node_retriever_t& db,
+ const uhd::fs_path fe_path,
+ const uhd::direction_t trx,
+ const size_t chan)
+ : uhd::experts::worker_node_t(fe_path / "zbx_freq_be_expert")
+ , _coerced_lo1_frequency(db, fe_path / "los" / ZBX_LO1 / "freq" / "value" / "coerced")
+ , _coerced_lo2_frequency(db, fe_path / "los" / ZBX_LO2 / "freq" / "value" / "coerced")
+ , _coerced_if2_frequency(db, fe_path / "if_freq" / "coerced")
+ , _is_highband(db, fe_path / "is_highband")
+ , _mixer1_m(db, fe_path / "mixer1_m")
+ , _mixer1_n(db, fe_path / "mixer1_n")
+ , _mixer2_m(db, fe_path / "mixer2_m")
+ , _mixer2_n(db, fe_path / "mixer2_n")
+ , _coerced_frequency(db, fe_path / "freq" / "coerced")
+ , _trx(trx)
+ , _chan(chan)
+ {
+ // Inputs
+ bind_accessor(_coerced_lo1_frequency);
+ bind_accessor(_coerced_lo2_frequency);
+ bind_accessor(_coerced_if2_frequency);
+ bind_accessor(_is_highband);
+ bind_accessor(_mixer1_m);
+ bind_accessor(_mixer1_n);
+ bind_accessor(_mixer2_m);
+ bind_accessor(_mixer2_n);
+
+ // Outputs
+ bind_accessor(_coerced_frequency);
+ }
+
+private:
+ void resolve() override;
+
+ // Inputs from LO expert(s)
+ uhd::experts::data_reader_t<double> _coerced_lo1_frequency;
+ uhd::experts::data_reader_t<double> _coerced_lo2_frequency;
+ // Input from MPM/RPC expert
+ uhd::experts::data_reader_t<double> _coerced_if2_frequency;
+ uhd::experts::data_reader_t<bool> _is_highband;
+ // Input from Frequency FE
+ uhd::experts::data_reader_t<int> _mixer1_m;
+ uhd::experts::data_reader_t<int> _mixer1_n;
+ uhd::experts::data_reader_t<int> _mixer2_m;
+ uhd::experts::data_reader_t<int> _mixer2_n;
+
+ // Output to user/API
+ uhd::experts::data_writer_t<double> _coerced_frequency;
+
+ // Channel properties
+ const uhd::direction_t _trx;
+ const size_t _chan;
+};
+
+/*!---------------------------------------------------------
+ * zbx_lo_expert
+ *
+ * This expert is responsible for controlling one LO on the zbx
+ * note: LO source control is handled by the CPLD Programming Expert
+ *
+ * This should trigger:
+ * - Relevant (tx/rx, channel) Frequency Back-end Expert
+ *
+ * One instance of this expert is required for each LO (lo1, lo2) per Direction (TX/RX)
+ * and Channel (0,1); eight total
+ * --------------------------------------------------------
+ */
+class zbx_lo_expert : public uhd::experts::worker_node_t
+{
+public:
+ zbx_lo_expert(const uhd::experts::node_retriever_t& db,
+ const uhd::fs_path fe_path,
+ const uhd::direction_t trx,
+ const size_t chan,
+ const std::string lo,
+ std::shared_ptr<zbx_lo_ctrl> zbx_lo_ctrl)
+ : uhd::experts::worker_node_t(fe_path / "zbx_" + lo + "_expert")
+ , _desired_lo_frequency(db, fe_path / "los" / lo / "freq" / "value" / "desired")
+ , _set_is_enabled(db, fe_path / lo / "enabled")
+ , _test_mode_enabled(db, fe_path / lo / "test_mode")
+ , _coerced_lo_frequency(db, fe_path / "los" / lo / "freq" / "value" / "coerced")
+ , _lo_ctrl(zbx_lo_ctrl)
+ , _trx(trx)
+ , _chan(chan)
+ {
+ bind_accessor(_desired_lo_frequency);
+ bind_accessor(_test_mode_enabled);
+ bind_accessor(_set_is_enabled);
+ bind_accessor(_coerced_lo_frequency);
+ }
+
+private:
+ void resolve() override;
+
+ // Inputs from Frequency FE expert or user/API
+ uhd::experts::data_reader_t<double> _desired_lo_frequency;
+ uhd::experts::data_reader_t<bool> _set_is_enabled;
+ // Inputs from user/API
+ uhd::experts::data_reader_t<bool> _test_mode_enabled;
+
+ // Outputs to Frequency BE expert or user/API
+ uhd::experts::data_writer_t<double> _coerced_lo_frequency;
+
+ std::shared_ptr<zbx_lo_ctrl> _lo_ctrl;
+ const uhd::direction_t _trx;
+ const size_t _chan;
+};
+
+
+/*! DSA coercer expert
+ *
+ * Knows how to coerce a DSA value.
+ */
+class zbx_gain_coercer_expert : public uhd::experts::worker_node_t
+{
+public:
+ zbx_gain_coercer_expert(const uhd::experts::node_retriever_t& db,
+ const uhd::fs_path gain_path,
+ const uhd::meta_range_t valid_range)
+ : uhd::experts::worker_node_t(gain_path / "zbx_gain_coercer_expert")
+ , _gain_desired(db, gain_path / "desired")
+ , _gain_coerced(db, gain_path / "coerced")
+ , _valid_range(valid_range)
+ {
+ bind_accessor(_gain_desired);
+ bind_accessor(_gain_coerced);
+ }
+
+private:
+ void resolve() override;
+ // Input
+ uhd::experts::data_reader_t<double> _gain_desired;
+ // Output
+ uhd::experts::data_writer_t<double> _gain_coerced;
+ // Attributes
+ const uhd::meta_range_t _valid_range;
+};
+
+/*!---------------------------------------------------------
+ * zbx_tx_gain_expert (TX Gain Expert)
+ *
+ * This expert is responsible for controlling the gain of each TX channel.
+ * If the gain profile is set to default, then it will look up the corresponding
+ * amp and DSA values and write them to those nodes.
+ *
+ * This should trigger:
+ * - Adjacent CPLD TX Programming Expert
+ *
+ * One instance of this expert is required for each TX Channel (0,1); two total
+ * --------------------------------------------------------
+ */
+class zbx_tx_gain_expert : public uhd::experts::worker_node_t
+{
+public:
+ zbx_tx_gain_expert(const uhd::experts::node_retriever_t& db,
+ const uhd::fs_path fe_path,
+ const size_t chan,
+ uhd::usrp::pwr_cal_mgr::sptr power_mgr,
+ uhd::usrp::cal::zbx_tx_dsa_cal::sptr dsa_cal)
+ : uhd::experts::worker_node_t(fe_path / "zbx_gain_expert")
+ , _gain_in(db, fe_path / "gains" / ZBX_GAIN_STAGE_ALL / "value" / "desired")
+ , _profile(db, fe_path / "gains" / "all" / "profile")
+ , _frequency(db, fe_path / "freq" / "coerced")
+ , _gain_out(db, fe_path / "gains" / ZBX_GAIN_STAGE_ALL / "value" / "coerced")
+ , _dsa1(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA1 / "value" / "desired")
+ , _dsa2(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA2 / "value" / "desired")
+ , _amp_gain(db, fe_path / "gains" / ZBX_GAIN_STAGE_AMP / "value" / "desired")
+ , _power_mgr(power_mgr)
+ , _dsa_cal(dsa_cal)
+ , _chan(chan)
+ {
+ bind_accessor(_gain_in);
+ bind_accessor(_profile);
+ bind_accessor(_frequency);
+ bind_accessor(_gain_out);
+ bind_accessor(_dsa1);
+ bind_accessor(_dsa2);
+ bind_accessor(_amp_gain);
+ }
+
+private:
+ void resolve() override;
+ void _set_tx_dsa(const std::string, const uint8_t desired_gain);
+ double _set_tx_amp_by_gain(const double gain);
+ // Inputs from user/API
+ uhd::experts::data_reader_t<double> _gain_in;
+ // Inputs for DSA calibration
+ uhd::experts::data_reader_t<std::string> _profile;
+ uhd::experts::data_reader_t<double> _frequency;
+
+ // Output to user/API
+ uhd::experts::data_writer_t<double> _gain_out;
+ // Outputs to CPLD programming expert
+ uhd::experts::data_writer_t<double> _dsa1;
+ uhd::experts::data_writer_t<double> _dsa2;
+ uhd::experts::data_writer_t<double> _amp_gain;
+
+ uhd::usrp::pwr_cal_mgr::sptr _power_mgr;
+ uhd::usrp::cal::zbx_tx_dsa_cal::sptr _dsa_cal;
+ const size_t _chan;
+};
+
+/*!---------------------------------------------------------
+ * zbx_rx_gain_expert (RX Gain Expert)
+ *
+ * This expert is responsible for controlling the gain of each RX channel
+ *
+ * This should trigger:
+ * - Adjacent CPLD RX Programming Expert
+ *
+ * One instance of this expert is required for each RX Channel (0,1); two total
+ * --------------------------------------------------------
+ */
+class zbx_rx_gain_expert : public uhd::experts::worker_node_t
+{
+public:
+ zbx_rx_gain_expert(const uhd::experts::node_retriever_t& db,
+ const uhd::fs_path fe_path,
+ const size_t chan,
+ uhd::usrp::pwr_cal_mgr::sptr power_mgr,
+ uhd::usrp::cal::zbx_rx_dsa_cal::sptr dsa_cal)
+ : uhd::experts::worker_node_t(fe_path / "zbx_gain_expert")
+ , _gain_in(db, fe_path / "gains" / ZBX_GAIN_STAGE_ALL / "value" / "desired")
+ , _profile(db, fe_path / "gains" / "all" / "profile")
+ , _frequency(db, fe_path / "freq" / "coerced")
+ , _gain_out(db, fe_path / "gains" / ZBX_GAIN_STAGE_ALL / "value" / "coerced")
+ , _dsa1(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA1 / "value" / "desired")
+ , _dsa2(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA2 / "value" / "desired")
+ , _dsa3a(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA3A / "value" / "desired")
+ , _dsa3b(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA3B / "value" / "desired")
+ , _power_mgr(power_mgr)
+ , _dsa_cal(dsa_cal)
+ , _chan(chan)
+ {
+ bind_accessor(_gain_in);
+ bind_accessor(_profile);
+ bind_accessor(_frequency);
+ bind_accessor(_gain_out);
+ bind_accessor(_dsa1);
+ bind_accessor(_dsa2);
+ bind_accessor(_dsa3a);
+ bind_accessor(_dsa3b);
+ }
+
+private:
+ void resolve() override;
+
+ // Inputs from user/API
+ uhd::experts::data_reader_t<double> _gain_in;
+ uhd::experts::data_reader_t<std::string> _profile;
+ // Inputs for dsa calibration
+ uhd::experts::data_reader_t<double> _frequency;
+
+ // Output to user/API
+ uhd::experts::data_writer_t<double> _gain_out;
+ // Outputs to CPLD programming expert
+ uhd::experts::data_writer_t<double> _dsa1;
+ uhd::experts::data_writer_t<double> _dsa2;
+ uhd::experts::data_writer_t<double> _dsa3a;
+ uhd::experts::data_writer_t<double> _dsa3b;
+
+ uhd::usrp::pwr_cal_mgr::sptr _power_mgr;
+ uhd::usrp::cal::zbx_rx_dsa_cal::sptr _dsa_cal;
+ const size_t _chan;
+};
+
+/*!---------------------------------------------------------
+ * zbx_tx_programming_expert (TX CPLD Programming Expert)
+ *
+ * This expert is responsible for programming the ZBX CPLD with parameters determined by
+ * user input or other experts This includes antenna setting, gain/dsa steps, lo source
+ * control, rf filter settings
+ *
+ * This expert should not trigger any other experts, these are all blind parameters
+ *
+ * One instance of this expert is required for each TX Channel (0,1); two total
+ * --------------------------------------------------------
+ */
+class zbx_tx_programming_expert : public uhd::experts::worker_node_t
+{
+public:
+ zbx_tx_programming_expert(const uhd::experts::node_retriever_t& db,
+ const uhd::fs_path tx_fe_path,
+ const uhd::fs_path rx_fe_path, /*needed for shared command time*/
+ const size_t chan,
+ uhd::usrp::cal::zbx_tx_dsa_cal::sptr dsa_cal,
+ std::shared_ptr<zbx_cpld_ctrl> cpld)
+ : experts::worker_node_t(tx_fe_path / "zbx_tx_programming_expert")
+ , _antenna(db, tx_fe_path / "antenna" / "value")
+ , _atr_mode(db, tx_fe_path / "atr_mode")
+ , _profile(db, tx_fe_path / "gains" / "all" / "profile")
+ , _command_time(db, rx_fe_path / "time" / "cmd")
+ , _frequency(db, tx_fe_path / "freq" / "coerced")
+ , _dsa1(db, tx_fe_path / "gains" / ZBX_GAIN_STAGE_DSA1 / "value" / "coerced")
+ , _dsa2(db, tx_fe_path / "gains" / ZBX_GAIN_STAGE_DSA2 / "value" / "coerced")
+ , _amp_gain(db, tx_fe_path / "gains" / ZBX_GAIN_STAGE_AMP / "value" / "coerced")
+ , _rf_filter(db, tx_fe_path / "rf" / "filter")
+ , _if1_filter(db, tx_fe_path / "if1" / "filter")
+ , _if2_filter(db, tx_fe_path / "if2" / "filter")
+ , _is_highband(db, tx_fe_path / "is_highband")
+ , _lo1_source(db, tx_fe_path / "ch" / ZBX_LO1 / "source")
+ , _lo2_source(db, tx_fe_path / "ch" / ZBX_LO2 / "source")
+ , _dsa_cal(dsa_cal)
+ , _cpld(cpld)
+ , _chan(chan)
+ {
+ bind_accessor(_antenna);
+ bind_accessor(_atr_mode);
+ bind_accessor(_profile);
+ bind_accessor(_command_time);
+ bind_accessor(_frequency);
+ bind_accessor(_dsa1);
+ bind_accessor(_dsa2);
+ bind_accessor(_amp_gain);
+ bind_accessor(_rf_filter);
+ bind_accessor(_if1_filter);
+ bind_accessor(_if2_filter);
+ bind_accessor(_is_highband);
+ bind_accessor(_lo1_source);
+ bind_accessor(_lo2_source);
+ }
+
+private:
+ void resolve() override;
+
+ // Inputs from user/API
+ uhd::experts::data_reader_t<std::string> _antenna;
+ uhd::experts::data_reader_t<zbx_cpld_ctrl::atr_mode> _atr_mode;
+ uhd::experts::data_reader_t<std::string> _profile;
+
+ // Inputs from the Frequency FE expert
+ // Note: this is just for node dependencies, we want to be notified if just the tune
+ // frequency has been changed.
+ uhd::experts::data_reader_t<time_spec_t> _command_time;
+ uhd::experts::data_reader_t<double> _frequency;
+
+ // Inputs from Gain TX expert
+ uhd::experts::data_reader_t<double> _dsa1;
+ uhd::experts::data_reader_t<double> _dsa2;
+ uhd::experts::data_reader_t<double> _amp_gain;
+
+ // Inputs from Frequency FE expert
+ uhd::experts::data_reader_t<int> _rf_filter;
+ uhd::experts::data_reader_t<int> _if1_filter;
+ uhd::experts::data_reader_t<int> _if2_filter;
+ uhd::experts::data_reader_t<bool> _is_highband;
+ // Inputs from LO expert(s)
+ uhd::experts::data_reader_t<zbx_lo_source_t> _lo1_source;
+ uhd::experts::data_reader_t<zbx_lo_source_t> _lo2_source;
+
+ uhd::usrp::cal::zbx_tx_dsa_cal::sptr _dsa_cal;
+ // Expects constructed cpld control objects
+ std::shared_ptr<zbx_cpld_ctrl> _cpld;
+ const size_t _chan;
+};
+
+/*!---------------------------------------------------------
+ * zbx_rx_programming_expert (RX CPLD Programming Expert)
+ *
+ * This expert is responsible for programming the ZBX CPLD with parameters determined by
+ * user input or other experts.
+ * This includes antenna setting, gain/dsa steps, lo source control, rf filter settings
+ *
+ * This expert should not trigger any other experts, these are all blind parameters
+ *
+ * One instance of this expert is required for each RX Channel (0,1); two total
+ * --------------------------------------------------------
+ */
+class zbx_rx_programming_expert : public uhd::experts::worker_node_t
+{
+public:
+ zbx_rx_programming_expert(const uhd::experts::node_retriever_t& db,
+ const uhd::fs_path fe_path,
+ const size_t chan,
+ uhd::usrp::cal::zbx_rx_dsa_cal::sptr dsa_cal,
+ std::shared_ptr<zbx_cpld_ctrl> cpld)
+ : experts::worker_node_t(fe_path / "zbx_rx_programming_expert")
+ , _antenna(db, fe_path / "antenna" / "value")
+ , _atr_mode(db, fe_path / "atr_mode")
+ , _profile(db, fe_path / "gains" / "all" / "profile")
+ , _command_time(db, fe_path / "time" / "cmd")
+ , _frequency(db, fe_path / "freq" / "coerced")
+ , _dsa1(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA1 / "value" / "coerced")
+ , _dsa2(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA2 / "value" / "coerced")
+ , _dsa3a(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA3A / "value" / "coerced")
+ , _dsa3b(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA3B / "value" / "coerced")
+ , _rf_filter(db, fe_path / "rf" / "filter")
+ , _if1_filter(db, fe_path / "if1" / "filter")
+ , _if2_filter(db, fe_path / "if2" / "filter")
+ , _is_highband(db, fe_path / "is_highband")
+ , _lo1_source(db, fe_path / "ch" / ZBX_LO1 / "source")
+ , _lo2_source(db, fe_path / "ch" / ZBX_LO2 / "source")
+ , _dsa_cal(dsa_cal)
+ , _cpld(cpld)
+ , _chan(chan)
+ {
+ bind_accessor(_antenna);
+ bind_accessor(_atr_mode);
+ bind_accessor(_profile);
+ bind_accessor(_command_time);
+ bind_accessor(_frequency);
+ bind_accessor(_dsa1);
+ bind_accessor(_dsa2);
+ bind_accessor(_dsa3a);
+ bind_accessor(_dsa3b);
+ bind_accessor(_rf_filter);
+ bind_accessor(_if1_filter);
+ bind_accessor(_if2_filter);
+ bind_accessor(_is_highband);
+ bind_accessor(_lo1_source);
+ bind_accessor(_lo2_source);
+ }
+
+private:
+ void resolve() override;
+ void _update_leds();
+
+ // Inputs from user/API
+ uhd::experts::data_reader_t<std::string> _antenna;
+ uhd::experts::data_reader_t<zbx_cpld_ctrl::atr_mode> _atr_mode;
+ uhd::experts::data_reader_t<std::string> _profile;
+
+ // Inputs from the Frequency FE expert
+ // Note: this is just for node dependencies, we want to be notified if just the tune
+ // frequency has been changed.
+ uhd::experts::data_reader_t<time_spec_t> _command_time;
+ uhd::experts::data_reader_t<double> _frequency;
+
+ // Inputs from Gain expert
+ uhd::experts::data_reader_t<double> _dsa1;
+ uhd::experts::data_reader_t<double> _dsa2;
+ uhd::experts::data_reader_t<double> _dsa3a;
+ uhd::experts::data_reader_t<double> _dsa3b;
+
+ // Inputs from Frequency FE expert
+ uhd::experts::data_reader_t<int> _rf_filter;
+ uhd::experts::data_reader_t<int> _if1_filter;
+ uhd::experts::data_reader_t<int> _if2_filter;
+ uhd::experts::data_reader_t<bool> _is_highband;
+ // Inputs from LO expert(s)
+ uhd::experts::data_reader_t<zbx_lo_source_t> _lo1_source;
+ uhd::experts::data_reader_t<zbx_lo_source_t> _lo2_source;
+
+ uhd::usrp::cal::zbx_rx_dsa_cal::sptr _dsa_cal;
+ // Expects constructed cpld control objects
+ std::shared_ptr<zbx_cpld_ctrl> _cpld;
+ const size_t _chan;
+};
+
+/*!---------------------------------------------------------
+ * zbx_band_inversion_expert
+ *
+ * This expert is responsible for handling the band inversion calls to MPM on the target
+ * device
+ *
+ * This expert should not trigger any others
+ *
+ * One instance of this expert is required for each Direction (TX/RX) and Channel (0,1);
+ * four total
+ * --------------------------------------------------------
+ */
+class zbx_band_inversion_expert : public uhd::experts::worker_node_t
+{
+public:
+ zbx_band_inversion_expert(const uhd::experts::node_retriever_t& db,
+ const uhd::fs_path fe_path,
+ const uhd::direction_t trx,
+ const size_t chan,
+ const int db_idx,
+ uhd::usrp::zbx_rpc_iface::sptr rpcc)
+ : uhd::experts::worker_node_t(fe_path / "zbx_band_inversion_expert")
+ , _is_band_inverted(db, fe_path / "band_inverted")
+ , _db_idx(db_idx)
+ , _rpcc(rpcc)
+ , _trx(trx)
+ , _chan(chan)
+ {
+ bind_accessor(_is_band_inverted);
+ }
+
+private:
+ void resolve() override;
+
+ // Inputs from Frequency FE expert
+ uhd::experts::data_reader_t<bool> _is_band_inverted;
+
+ const size_t _db_idx;
+ uhd::usrp::zbx_rpc_iface::sptr _rpcc;
+ const uhd::direction_t _trx;
+ const size_t _chan;
+};
+
+/*!---------------------------------------------------------
+ * zbx_rfdc_freq_expert
+ *
+ * This expert is responsible for handling any rfdc frequency calls to MPM on the target
+ * device
+ *
+ * This expert should not trigger any experts
+ *
+ * One instance of this expert is required for each Direction (TX/RX) and Channel (0,1);
+ * four total
+ * --------------------------------------------------------
+ */
+class zbx_rfdc_freq_expert : public uhd::experts::worker_node_t
+{
+public:
+ zbx_rfdc_freq_expert(const uhd::experts::node_retriever_t& db,
+ const uhd::fs_path fe_path,
+ const uhd::direction_t trx,
+ const size_t chan,
+ const std::string rpc_prefix,
+ int db_idx,
+ uhd::usrp::x400_rpc_iface::sptr rpcc)
+ : uhd::experts::worker_node_t(fe_path / "zbx_rfdc_freq_expert")
+ , _rfdc_freq_desired(
+ db, fe_path / "los" / RFDC_NCO / "freq" / "value" / "desired")
+ , _rfdc_freq_coerced(
+ db, fe_path / "los" / RFDC_NCO / "freq" / "value" / "coerced")
+ , _if2_frequency_desired(db, fe_path / "if_freq" / "desired")
+ , _if2_frequency_coerced(db, fe_path / "if_freq" / "coerced")
+ , _rpc_prefix(rpc_prefix)
+ , _db_idx(db_idx)
+ , _rpcc(rpcc)
+ , _trx(trx)
+ , _chan(chan)
+ {
+ bind_accessor(_rfdc_freq_desired);
+ bind_accessor(_rfdc_freq_coerced);
+ bind_accessor(_if2_frequency_desired);
+ bind_accessor(_if2_frequency_coerced);
+ }
+
+private:
+ void resolve() override;
+
+ // Inputs from user/API
+ uhd::experts::data_reader_t<double> _rfdc_freq_desired;
+
+ // Outputs to user/API
+ uhd::experts::data_writer_t<double> _rfdc_freq_coerced;
+
+
+ // Inputs from Frequency FE expert
+ uhd::experts::data_reader_t<double> _if2_frequency_desired;
+
+ // Outputs to Frequency BE expert
+ uhd::experts::data_writer_t<double> _if2_frequency_coerced;
+
+
+ const std::string _rpc_prefix;
+ const size_t _db_idx;
+ uhd::usrp::x400_rpc_iface::sptr _rpcc;
+ const uhd::direction_t _trx;
+ const size_t _chan;
+};
+
+using uhd::rfnoc::x400::rfdc_control;
+/*!---------------------------------------------------------
+ * zbx_sync_expert
+ *
+ * This expert is responsible for handling the phase alignment.
+ * Per channel, there are up to 4 things whose phase need syncing: The two
+ * LOs, the NCO, and the ADC/DAC gearboxes. However, the LOs share a sync
+ * register, and so do the NCOs. To minimize writes, we thus need a single sync
+ * expert at the end of the graph, who combines all LOs and all NCOs.
+ * --------------------------------------------------------
+ */
+class zbx_sync_expert : public uhd::experts::worker_node_t
+{
+public:
+ zbx_sync_expert(const uhd::experts::node_retriever_t& db,
+ const uhd::fs_path tx_fe_path,
+ const uhd::fs_path rx_fe_path,
+ rfdc_control::sptr rfdcc,
+ std::shared_ptr<zbx_cpld_ctrl> cpld)
+ : uhd::experts::worker_node_t("zbx_sync_expert")
+ , _fe_time{{db, rx_fe_path / 0 / "time/fe"}, {db, rx_fe_path / 1 / "time/fe"}}
+ , _lo_freqs{{zbx_lo_t::RX0_LO1,
+ {db, rx_fe_path / 0 / "los" / ZBX_LO1 / "freq" / "value" / "coerced"}},
+ {zbx_lo_t::RX0_LO2,
+ {db, rx_fe_path / 0 / "los" / ZBX_LO2 / "freq" / "value" / "coerced"}},
+ {zbx_lo_t::TX0_LO1,
+ {db, tx_fe_path / 0 / "los" / ZBX_LO1 / "freq" / "value" / "coerced"}},
+ {zbx_lo_t::TX0_LO2,
+ {db, tx_fe_path / 0 / "los" / ZBX_LO2 / "freq" / "value" / "coerced"}},
+ {zbx_lo_t::RX1_LO1,
+ {db, rx_fe_path / 1 / "los" / ZBX_LO1 / "freq" / "value" / "coerced"}},
+ {zbx_lo_t::RX1_LO2,
+ {db, rx_fe_path / 1 / "los" / ZBX_LO2 / "freq" / "value" / "coerced"}},
+ {zbx_lo_t::TX1_LO1,
+ {db, tx_fe_path / 1 / "los" / ZBX_LO1 / "freq" / "value" / "coerced"}},
+ {zbx_lo_t::TX1_LO2,
+ {db, tx_fe_path / 1 / "los" / ZBX_LO2 / "freq" / "value" / "coerced"}}}
+ , _nco_freqs{{rfdc_control::rfdc_type::RX0,
+ {db, rx_fe_path / 0 / "if_freq" / "coerced"}},
+ {rfdc_control::rfdc_type::RX1,
+ {db, rx_fe_path / 1 / "if_freq" / "coerced"}},
+ {rfdc_control::rfdc_type::TX0,
+ {db, tx_fe_path / 0 / "if_freq" / "coerced"}},
+ {rfdc_control::rfdc_type::TX1,
+ {db, tx_fe_path / 1 / "if_freq" / "coerced"}}}
+ , _rfdcc(rfdcc)
+ , _cpld(cpld)
+ {
+ for (auto& fe_time : _fe_time) {
+ bind_accessor(fe_time);
+ }
+ for (auto& lo_freq : _lo_freqs) {
+ bind_accessor(lo_freq.second);
+ }
+ for (auto& nco_freq : _nco_freqs) {
+ bind_accessor(nco_freq.second);
+ }
+ }
+
+private:
+ void resolve() override;
+
+ // Inputs from user/API
+ // Command time: We have 2 channels, one time spec per channel
+ std::vector<uhd::experts::data_reader_t<time_spec_t>> _fe_time;
+ // We have 8 LOs:
+ std::map<zbx_lo_t, uhd::experts::data_reader_t<double>> _lo_freqs;
+ // We have 4 NCOs
+ std::map<rfdc_control::rfdc_type, uhd::experts::data_reader_t<double>> _nco_freqs;
+
+ // This expert has no outputs.
+
+ // Attributes
+ rfdc_control::sptr _rfdcc;
+ std::shared_ptr<zbx_cpld_ctrl> _cpld;
+ //! Store the sync state of the ADC gearboxes. If false, we assume they're
+ // out of sync. This could also be a vector of booleans if we want to be
+ // able to sync ADC gearboxes individually.
+ bool _adcs_synced = false;
+ //! Store the sync state of the DAC gearboxes. If false, we assume they're
+ // out of sync. This could also be a vector of booleans if we want to be
+ // able to sync DAC gearboxes individually.
+ bool _dacs_synced = false;
+};
+
+}}} // namespace uhd::usrp::zbx
diff --git a/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_lo_ctrl.hpp b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_lo_ctrl.hpp
new file mode 100644
index 000000000..add7013ef
--- /dev/null
+++ b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_lo_ctrl.hpp
@@ -0,0 +1,86 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include "zbx_constants.hpp"
+#include <uhd/types/direction.hpp>
+#include <uhdlib/usrp/common/lmx2572.hpp>
+#include <functional>
+
+namespace uhd { namespace usrp { namespace zbx {
+
+class zbx_lo_ctrl final
+{
+public:
+ // Pass in our lo selection and poke/peek functions
+ zbx_lo_ctrl(zbx_lo_t lo,
+ lmx2572_iface::write_fn_t&& poke16,
+ lmx2572_iface::read_fn_t&& peek16,
+ lmx2572_iface::sleep_fn_t&& sleep,
+ const double default_frequency,
+ const double db_prc_rate,
+ const bool testing_mode_enabled);
+
+ // Passes in a desired LO frequency to the LMX driver, returns the coerced frequency
+ double set_lo_freq(const double freq);
+
+ // Returns cached LO frequency value
+ double get_lo_freq();
+
+ // Spins up a timeout loop to wait for the PLL's to lock
+ // \throws uhd::runtime_error on a failure to lock
+ void wait_for_lo_lock();
+
+ // Returns the lock status of the PLL
+ bool get_lock_status();
+
+ // Enable/disable LO port
+ // Targeted LO port depends on whether test mode is disabled/enabled
+ void set_lo_port_enabled(bool enable);
+
+ // Returns status of LO port
+ // Targeted LO port depends on whether test mode is disabled/enabled
+ bool get_lo_port_enabled();
+
+ // Enable test mode of the LO
+ void set_lo_test_mode_enabled(bool enable);
+
+ // Returns whether the test mode has been enabled
+ bool get_lo_test_mode_enabled();
+
+ static zbx_lo_t lo_string_to_enum(
+ const uhd::direction_t trx, const size_t channel, const std::string name);
+
+ // TODO: Future implementation of spur dodging
+ // void set_spur_dodging(const bool enable);
+ // bool get_spur_dodging();
+private:
+ // Returns the appropriate output port for given LO
+ lmx2572_iface::output_t _get_output_port(bool test_port);
+
+ // Specific LO that this class was constructed for
+ const zbx_lo_t _lo;
+
+ const std::string _log_id;
+
+ // LMX driver set up with this object specific LO
+ lmx2572_iface::sptr _lmx;
+
+ // Cached overall LO output frequency.
+ // TODO: seperate between coerced/desired frequencies for recalculation once LO step
+ // quantization is introduced
+ double _freq;
+
+ // Daughterboard PRC rate, used as the reference frequency
+ double _db_prc_rate;
+
+ // Set LO output mode, RF output mode is considered normal use case
+ // Testing mode is for LMX V&V
+ bool _testing_mode_enabled;
+};
+
+}}} // namespace uhd::usrp::zbx