diff options
Diffstat (limited to 'host/lib/include/uhdlib/usrp/dboard/zbx')
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 |