aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib/include/uhdlib/usrp/dboard/zbx
diff options
context:
space:
mode:
Diffstat (limited to 'host/lib/include/uhdlib/usrp/dboard/zbx')
-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
5 files changed, 2095 insertions, 0 deletions
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