aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_expert.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'host/lib/include/uhdlib/usrp/dboard/zbx/zbx_expert.hpp')
-rw-r--r--host/lib/include/uhdlib/usrp/dboard/zbx/zbx_expert.hpp837
1 files changed, 837 insertions, 0 deletions
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