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