diff options
author | Martin Braun <martin.braun@ettus.com> | 2019-07-03 20:15:35 -0700 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2019-11-26 12:16:25 -0800 |
commit | c256b9df6502536c2e451e690f1ad5962c664d1a (patch) | |
tree | a83ad13e6f5978bbe14bb3ecf8294ba1e3d28db4 /host | |
parent | 9a8435ed998fc5c65257f4c55768750b227ab19e (diff) | |
download | uhd-c256b9df6502536c2e451e690f1ad5962c664d1a.tar.gz uhd-c256b9df6502536c2e451e690f1ad5962c664d1a.tar.bz2 uhd-c256b9df6502536c2e451e690f1ad5962c664d1a.zip |
x300/mpmd: Port all RFNoC devices to the new RFNoC framework
Co-Authored-By: Alex Williams <alex.williams@ni.com>
Co-Authored-By: Sugandha Gupta <sugandha.gupta@ettus.com>
Co-Authored-By: Brent Stapleton <brent.stapleton@ettus.com>
Co-Authored-By: Ciro Nishiguchi <ciro.nishiguchi@ni.com>
Diffstat (limited to 'host')
97 files changed, 11424 insertions, 8175 deletions
diff --git a/host/docs/configuration.dox b/host/docs/configuration.dox index 4ebc2cf9e..8216a59d0 100644 --- a/host/docs/configuration.dox +++ b/host/docs/configuration.dox @@ -36,8 +36,6 @@ and possible more options. ext_adc_self_test | Run an extended ADC self test (more than the usual) | X3x0 | ext_adc_self_test=1 recover_mb_eeprom | Disable version checks. Can damage hardware. Only recommended for recovering devices with corrupted EEPROMs. | X3x0 | recover_mb_eeprom=1 skip_dram | Ignore DRAM FIFO block. Connect TX streamers straight into DUC or radio. | X3x0, N3xx | skip_dram=1 - skip_ddc | Ignore DDC block. Connect Rx streamers straight into radio. | X3x0, N3xx, E3xx | skip_ddc=1 - skip_duc | Ignore DUC block. Connect Rx streamers or DRAM straight into radio. | X3x0, N3xx, E3xx | skip_duc=1 In addition, many of the streaming-related options can be set per-device at configuration time. diff --git a/host/include/uhd/erfnoc/blocks/e310_bsp.yml b/host/include/uhd/erfnoc/blocks/e310_bsp.yml new file mode 100644 index 000000000..8fabbb55a --- /dev/null +++ b/host/include/uhd/erfnoc/blocks/e310_bsp.yml @@ -0,0 +1,24 @@ +type: e31x +type_id: E310 +family: 7SERIES +transports: +- name: dma + type: dma + width: 64 + +clocks: +- name: radio + +io_ports: + ctrlport_radio: + type: ctrl_port + drive: slave + rename: + pattern: (ctrlport_)(.*) + repl: m_\1\2 + time_keeper: + type: time_keeper + drive: broadcaster + x300_radio: + type: x300_radio + drive: master diff --git a/host/include/uhd/erfnoc/core/e320_bsp.yml b/host/include/uhd/erfnoc/core/e320_bsp.yml new file mode 100644 index 000000000..c8d5b7de0 --- /dev/null +++ b/host/include/uhd/erfnoc/core/e320_bsp.yml @@ -0,0 +1,27 @@ +type: e320 +type_id: E320 +family: 7SERIES +transports: +- name: eth + type: 10G + width: 64 +- name: dma + type: dma + width: 64 + +clocks: +- name: radio + +io_ports: + ctrl_port: + type: ctrl_port + drive: slave + rename: + pattern: (ctrlport_)(.*) + repl: m_\1\2 + time_keeper: + type: time_keeper + drive: broadcaster + x300_radio: + type: x300_radio + drive: master diff --git a/host/include/uhd/erfnoc/core/n300_bsp.yml b/host/include/uhd/erfnoc/core/n300_bsp.yml new file mode 100644 index 000000000..f0c75df4e --- /dev/null +++ b/host/include/uhd/erfnoc/core/n300_bsp.yml @@ -0,0 +1,36 @@ +type: n300 +type_id: 1300 +family: 7SERIES +transports: +- name: eth0 + type: 1G + width: 64 +- name: eth1 + type: 10G + width: 64 +- name: dma + type: dma + width: 64 + +clocks: +- name: radio + +io_ports: + ctrlport_radio0: + type: ctrl_port + drive: slave + rename: + pattern: (ctrlport_)(.*) + repl: m_\1radio0_\2 + time_keeper: + type: time_keeper + drive: broadcaster + x300_radio0: + type: x300_radio + drive: master + rename: + pattern: (.*) + repl: \1_radio0 + dram: + type: axi4_mm_4x64_4g + drive: master diff --git a/host/include/uhd/erfnoc/core/n310_bsp.yml b/host/include/uhd/erfnoc/core/n310_bsp.yml new file mode 100644 index 000000000..08690ed5a --- /dev/null +++ b/host/include/uhd/erfnoc/core/n310_bsp.yml @@ -0,0 +1,48 @@ +type: n310 +type_id: 1300 +family: 7SERIES +transports: +- name: eth0 + type: 1G + width: 64 +- name: eth1 + type: 10G + width: 64 +- name: dma + type: dma + width: 64 + +clocks: +- name: radio + +io_ports: + ctrlport_radio0: + type: ctrl_port + drive: slave + rename: + pattern: (ctrlport_)(.*) + repl: m_\1radio0_\2 + ctrlport_radio1: + type: ctrl_port + drive: slave + rename: + pattern: (ctrlport_)(.*) + repl: m_\1radio1_\2 + time_keeper: + type: time_keeper + drive: broadcaster + x300_radio0: + type: x300_radio + drive: master + rename: + pattern: (.*) + repl: \1_radio0 + x300_radio1: + type: x300_radio + drive: master + rename: + pattern: (.*) + repl: \1_radio1 + dram: + type: axi4_mm_4x64_4g + drive: master diff --git a/host/include/uhd/erfnoc/core/n320_bsp.yml b/host/include/uhd/erfnoc/core/n320_bsp.yml new file mode 100644 index 000000000..5d31da947 --- /dev/null +++ b/host/include/uhd/erfnoc/core/n320_bsp.yml @@ -0,0 +1,48 @@ +type: n320 +type_id: 1320 +family: 7SERIES +transports: +- name: eth0 + type: 1G + width: 64 +- name: eth1 + type: 10G + width: 64 +- name: dma + type: dma + width: 64 + +clocks: +- name: radio + +io_ports: + ctrlport_radio0: + type: ctrl_port + drive: slave + rename: + pattern: (ctrlport_)(.*) + repl: m_\1radio0_\2 + ctrlport_radio1: + type: ctrl_port + drive: slave + rename: + pattern: (ctrlport_)(.*) + repl: m_\1radio1_\2 + time_keeper: + type: time_keeper + drive: broadcaster + radio_ch0: + type: radio_1x32 + drive: master + rename: + pattern: (.*) + repl: \1_radio0 + radio_ch1: + type: radio_1x32 + drive: master + rename: + pattern: (.*) + repl: \1_radio1 + dram: + type: axi4_mm_4x64_4g + drive: master diff --git a/host/include/uhd/erfnoc/core/x300_bsp.yml b/host/include/uhd/erfnoc/core/x300_bsp.yml new file mode 100644 index 000000000..20a78958f --- /dev/null +++ b/host/include/uhd/erfnoc/core/x300_bsp.yml @@ -0,0 +1,49 @@ +type: x300 +type_id: A300 +family: 7SERIES +transports: +- name: eth0 + type: 10G + width: 64 +- name: eth1 + type: 1G + width: 64 +- name: pcie + type: PCIe + width: 64 + +clocks: +- name: radio +- name: ce + +io_ports: + ctrlport_radio0: + type: ctrl_port + drive: slave + rename: + pattern: (ctrlport_)(.*) + repl: m_\1radio0_\2 + ctrlport_radio1: + type: ctrl_port + drive: slave + rename: + pattern: (ctrlport_)(.*) + repl: m_\1radio1_\2 + time_keeper: + type: time_keeper + drive: broadcaster + x300_radio0: + type: x300_radio + drive: master + rename: + pattern: (.*) + repl: \1_radio0 + x300_radio1: + type: x300_radio + drive: master + rename: + pattern: (.*) + repl: \1_radio1 + dram: + type: axi4_mm_2x64_4g + drive: master diff --git a/host/include/uhd/erfnoc/core/x310_bsp.yml b/host/include/uhd/erfnoc/core/x310_bsp.yml index e63d382d8..20a78958f 100644 --- a/host/include/uhd/erfnoc/core/x310_bsp.yml +++ b/host/include/uhd/erfnoc/core/x310_bsp.yml @@ -44,3 +44,6 @@ io_ports: rename: pattern: (.*) repl: \1_radio1 + dram: + type: axi4_mm_2x64_4g + drive: master diff --git a/host/include/uhd/rfnoc/defaults.hpp b/host/include/uhd/rfnoc/defaults.hpp index 9a3ce252f..0b95ddc2e 100644 --- a/host/include/uhd/rfnoc/defaults.hpp +++ b/host/include/uhd/rfnoc/defaults.hpp @@ -12,6 +12,9 @@ namespace uhd { namespace rfnoc { +// FIXME come up with a better place for this +static const size_t CHDR_MAX_LEN_HDR = 16; + static const std::string CLOCK_KEY_GRAPH("__graph__"); static const std::string PROP_KEY_DECIM("decim"); diff --git a/host/lib/include/uhdlib/usrp/common/apply_corrections.hpp b/host/lib/include/uhdlib/usrp/common/apply_corrections.hpp index 3b4669f9a..fca566493 100644 --- a/host/lib/include/uhdlib/usrp/common/apply_corrections.hpp +++ b/host/lib/include/uhdlib/usrp/common/apply_corrections.hpp @@ -14,30 +14,26 @@ namespace uhd{ namespace usrp{ - void apply_tx_fe_corrections( - property_tree::sptr sub_tree, //starts at mboards/x - const fs_path db_path, - const fs_path tx_fe_corr_path, - const double tx_lo_freq //actual lo freq - ); +void apply_tx_fe_corrections(property_tree::sptr sub_tree, // starts at mboards/x + const std::string& db_serial, + const fs_path tx_fe_corr_path, + const double tx_lo_freq // actual lo freq +); - void apply_tx_fe_corrections( - property_tree::sptr sub_tree, //starts at mboards/x - const std::string &slot, //name of dboard slot - const double tx_lo_freq //actual lo freq - ); - void apply_rx_fe_corrections( - property_tree::sptr sub_tree, //starts at mboards/x - const std::string &slot, //name of dboard slot - const double rx_lo_freq //actual lo freq - ); +void apply_tx_fe_corrections(property_tree::sptr sub_tree, // starts at mboards/x + const std::string& slot, // name of dboard slot + const double tx_lo_freq // actual lo freq +); +void apply_rx_fe_corrections(property_tree::sptr sub_tree, // starts at mboards/x + const std::string& slot, // name of dboard slot + const double rx_lo_freq // actual lo freq +); - void apply_rx_fe_corrections( - property_tree::sptr sub_tree, //starts at mboards/x - const fs_path db_path, - const fs_path rx_fe_corr_path, - const double rx_lo_freq //actual lo freq - ); +void apply_rx_fe_corrections(property_tree::sptr sub_tree, // starts at mboards/x + const std::string& db_serial, + const fs_path rx_fe_corr_path, + const double rx_lo_freq // actual lo freq +); }} //namespace uhd::usrp #endif /* INCLUDED_LIBUHD_USRP_COMMON_APPLY_CORRECTIONS_HPP */ diff --git a/host/lib/include/uhdlib/usrp/common/mpmd_mb_controller.hpp b/host/lib/include/uhdlib/usrp/common/mpmd_mb_controller.hpp new file mode 100644 index 000000000..0c64ecbad --- /dev/null +++ b/host/lib/include/uhdlib/usrp/common/mpmd_mb_controller.hpp @@ -0,0 +1,101 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_MPMD_MB_CONTROLLER_HPP +#define INCLUDED_LIBUHD_MPMD_MB_CONTROLLER_HPP + +#include <uhd/rfnoc/mb_controller.hpp> +#include <uhdlib/utils/rpc.hpp> +#include <memory> + +namespace uhd { namespace rfnoc { + +/*! MPM-Specific version of the mb_controller + * + * Reminder: There is one of these per motherboard. + * + * This motherboard controller abstracts out a bunch of RPC calls. + */ +class mpmd_mb_controller : public mb_controller +{ +public: + using sptr = std::shared_ptr<mpmd_mb_controller>; + + mpmd_mb_controller(uhd::rpc_client::sptr rpcc, uhd::device_addr_t device_info); + + //! Return reference to the RPC client + uhd::rpc_client::sptr get_rpc_client() { return _rpc; } + + /************************************************************************** + * Timekeeper API + *************************************************************************/ + //! MPM-specific version of the timekeeper controls + // + // MPM devices talk to MPM via RPC to control the timekeeper + class mpmd_timekeeper : public mb_controller::timekeeper + { + public: + using sptr = std::shared_ptr<mpmd_timekeeper>; + + mpmd_timekeeper(const size_t tk_idx, uhd::rpc_client::sptr rpc_client) + : _tk_idx(tk_idx), _rpc(rpc_client) + { + // nop + } + + uint64_t get_ticks_now(); + uint64_t get_ticks_last_pps(); + void set_ticks_now(const uint64_t ticks); + void set_ticks_next_pps(const uint64_t ticks); + void set_period(const uint64_t period_ns); + + /*! Update the tick rate + * Note: This is separate from set_tick_rate because the latter is + * protected, and we need to implement mpmd-specific functionality here + */ + void update_tick_rate(const double tick_rate); + + private: + const size_t _tk_idx; + uhd::rpc_client::sptr _rpc; + }; + + /************************************************************************** + * Motherboard Control API (see mb_controller.hpp) + *************************************************************************/ + std::string get_mboard_name() const; + void set_time_source(const std::string& source); + std::string get_time_source() const; + std::vector<std::string> get_time_sources() const; + void set_clock_source(const std::string& source); + std::string get_clock_source() const; + std::vector<std::string> get_clock_sources() const; + void set_sync_source(const std::string& clock_source, const std::string& time_source); + void set_sync_source(const uhd::device_addr_t& sync_source); + uhd::device_addr_t get_sync_source() const; + std::vector<uhd::device_addr_t> get_sync_sources(); + void set_clock_source_out(const bool enb); + void set_time_source_out(const bool enb); + uhd::sensor_value_t get_sensor(const std::string& name); + std::vector<std::string> get_sensor_names(); + uhd::usrp::mboard_eeprom_t get_eeprom(); + +private: + /************************************************************************** + * Attributes + *************************************************************************/ + //! Reference to RPC interface + mutable uhd::rpc_client::sptr _rpc; + + uhd::device_addr_t _device_info; + + //! List of MB sensor names + std::unordered_set<std::string> _sensor_names; +}; + +}} // namespace uhd::rfnoc + +#endif /* INCLUDED_LIBUHD_MPMD_MB_CONTROLLER_HPP */ diff --git a/host/lib/usrp/common/apply_corrections.cpp b/host/lib/usrp/common/apply_corrections.cpp index 5c606c338..b57ace71b 100644 --- a/host/lib/usrp/common/apply_corrections.cpp +++ b/host/lib/usrp/common/apply_corrections.cpp @@ -82,18 +82,15 @@ static std::complex<double> get_fe_correction( ); } -static void apply_fe_corrections( - uhd::property_tree::sptr sub_tree, - const uhd::fs_path &db_path, - const uhd::fs_path &fe_path, - const std::string &file_prefix, - const double lo_freq -){ - //extract eeprom serial - const uhd::usrp::dboard_eeprom_t db_eeprom = sub_tree->access<uhd::usrp::dboard_eeprom_t>(db_path).get(); - +static void apply_fe_corrections(uhd::property_tree::sptr sub_tree, + const std::string& db_serial, + const uhd::fs_path& fe_path, + const std::string& file_prefix, + const double lo_freq) +{ //make the calibration file path - const fs::path cal_data_path = fs::path(uhd::get_app_path()) / ".uhd" / "cal" / (file_prefix + db_eeprom.serial + ".csv"); + const fs::path cal_data_path = fs::path(uhd::get_app_path()) / ".uhd" / "cal" + / (file_prefix + db_serial + ".csv"); if (not fs::exists(cal_data_path)) return; //parse csv file or get from cache @@ -133,28 +130,26 @@ static void apply_fe_corrections( /*********************************************************************** * Wrapper routines with nice try/catch + print **********************************************************************/ -void uhd::usrp::apply_tx_fe_corrections( //overloading to work according to rfnoc tree struct - property_tree::sptr sub_tree, //starts at mboards/x - const uhd::fs_path db_path, +void uhd::usrp::apply_tx_fe_corrections( // overloading to work according to rfnoc tree + // struct + property_tree::sptr sub_tree, // starts at mboards/x + const std::string& db_serial, const uhd::fs_path tx_fe_corr_path, - const double lo_freq //actual lo freq -){ + const double lo_freq // actual lo freq +) +{ boost::mutex::scoped_lock l(corrections_mutex); try{ - apply_fe_corrections( - sub_tree, - db_path + "/tx_eeprom", + apply_fe_corrections(sub_tree, + db_serial, tx_fe_corr_path + "/iq_balance/value", "tx_iq_cal_v0.2_", - lo_freq - ); - apply_fe_corrections( - sub_tree, - db_path + "/tx_eeprom", + lo_freq); + apply_fe_corrections(sub_tree, + db_serial, tx_fe_corr_path + "/dc_offset/value", "tx_dc_cal_v0.2_", - lo_freq - ); + lo_freq); } catch(const std::exception &e){ UHD_LOGGER_ERROR("CAL") << "Failure in apply_tx_fe_corrections: " << e.what(); @@ -167,42 +162,44 @@ void uhd::usrp::apply_tx_fe_corrections( const double lo_freq //actual lo freq ){ boost::mutex::scoped_lock l(corrections_mutex); + + // extract eeprom serial + const uhd::fs_path db_path = "dboards/" + slot + "/tx_eeprom"; + const std::string db_serial = + sub_tree->access<uhd::usrp::dboard_eeprom_t>(db_path).get().serial; + try{ - apply_fe_corrections( - sub_tree, - "dboards/" + slot + "/tx_eeprom", + apply_fe_corrections(sub_tree, + db_serial, "tx_frontends/" + slot + "/iq_balance/value", "tx_iq_cal_v0.2_", - lo_freq - ); - apply_fe_corrections( - sub_tree, - "dboards/" + slot + "/tx_eeprom", + lo_freq); + apply_fe_corrections(sub_tree, + db_serial, "tx_frontends/" + slot + "/dc_offset/value", "tx_dc_cal_v0.2_", - lo_freq - ); + lo_freq); } catch(const std::exception &e){ UHD_LOGGER_ERROR("CAL") << "Failure in apply_tx_fe_corrections: " << e.what(); } } -void uhd::usrp::apply_rx_fe_corrections( //overloading to work according to rfnoc tree struct - property_tree::sptr sub_tree, //starts at mboards/x - const uhd::fs_path db_path, +void uhd::usrp::apply_rx_fe_corrections( // overloading to work according to rfnoc tree + // struct + property_tree::sptr sub_tree, // starts at mboards/x + const std::string& db_serial, const uhd::fs_path rx_fe_corr_path, - const double lo_freq //actual lo freq -){ + const double lo_freq // actual lo freq +) +{ boost::mutex::scoped_lock l(corrections_mutex); try{ - apply_fe_corrections( - sub_tree, - db_path + "/rx_eeprom", + apply_fe_corrections(sub_tree, + db_serial, rx_fe_corr_path + "/iq_balance/value", "rx_iq_cal_v0.2_", - lo_freq - ); + lo_freq); } catch(const std::exception &e){ UHD_LOGGER_ERROR("CAL") << "Failure in apply_tx_fe_corrections: " << e.what(); @@ -215,14 +212,15 @@ void uhd::usrp::apply_rx_fe_corrections( const double lo_freq //actual lo freq ){ boost::mutex::scoped_lock l(corrections_mutex); + const uhd::fs_path db_path = "dboards/" + slot + "/rx_eeprom"; + const std::string db_serial = + sub_tree->access<uhd::usrp::dboard_eeprom_t>(db_path).get().serial; try{ - apply_fe_corrections( - sub_tree, - "dboards/" + slot + "/rx_eeprom", + apply_fe_corrections(sub_tree, + db_serial, "rx_frontends/" + slot + "/iq_balance/value", "rx_iq_cal_v0.2_", - lo_freq - ); + lo_freq); } catch(const std::exception &e){ UHD_LOGGER_ERROR("CAL") << "Failure in apply_rx_fe_corrections: " << e.what(); diff --git a/host/lib/usrp/dboard/e3xx/CMakeLists.txt b/host/lib/usrp/dboard/e3xx/CMakeLists.txt index 5d452fb53..6a14c0766 100644 --- a/host/lib/usrp/dboard/e3xx/CMakeLists.txt +++ b/host/lib/usrp/dboard/e3xx/CMakeLists.txt @@ -1,13 +1,14 @@ # # Copyright 2018 Ettus Research, a National Instruments Company +# Copyright 2019 Ettus Research, a National Instruments Brand # # SPDX-License-Identifier: GPL-3.0-or-later # IF(ENABLE_E300 OR ENABLE_E320) LIST(APPEND E3XX_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/e3xx_radio_ctrl_impl.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/e3xx_radio_ctrl_init.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/e3xx_radio_control_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/e3xx_radio_control_init.cpp ${CMAKE_CURRENT_SOURCE_DIR}/e3xx_ad9361_iface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/e3xx_bands.cpp ) @@ -16,14 +17,14 @@ ENDIF(ENABLE_E300 OR ENABLE_E320) IF(ENABLE_E300) LIST(APPEND E300_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/e31x_radio_ctrl_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/e31x_radio_control_impl.cpp ) LIBUHD_APPEND_SOURCES(${E300_SOURCES}) ENDIF(ENABLE_E300) IF(ENABLE_E320) LIST(APPEND E320_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/e320_radio_ctrl_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/e320_radio_control_impl.cpp ) LIBUHD_APPEND_SOURCES(${E320_SOURCES}) ENDIF(ENABLE_E320) diff --git a/host/lib/usrp/dboard/e3xx/e31x_radio_control_impl.cpp b/host/lib/usrp/dboard/e3xx/e31x_radio_control_impl.cpp new file mode 100644 index 000000000..b7524e04c --- /dev/null +++ b/host/lib/usrp/dboard/e3xx/e31x_radio_control_impl.cpp @@ -0,0 +1,212 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "e31x_radio_control_impl.hpp" +#include "e31x_regs.hpp" +#include <uhd/rfnoc/registry.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; + +e31x_radio_control_impl::e31x_radio_control_impl(make_args_ptr make_args) + : e3xx_radio_control_impl(std::move(make_args)) +{ + // Swap front ends for E310 + _fe_swap = true; + _init_mpm(); +} + +e31x_radio_control_impl::~e31x_radio_control_impl() +{ + RFNOC_LOG_TRACE("e31x_radio_control_impl::dtor()"); +} + +/****************************************************************************** + * API Calls + *****************************************************************************/ +uint32_t e31x_radio_control_impl::get_tx_switches( + const size_t chan, + const double freq +) { + RFNOC_LOG_TRACE( + "Update all TX freq related switches. f=" << freq << " Hz, " + ); + + size_t fe_chan = _fe_swap ? (chan ? 0 : 1): chan; + + auto tx_sw1 = TX_SW1_LB_2750; // SW1 = 0 + auto vctxrx_sw = VCTXRX_SW_OFF; + auto tx_bias = (fe_chan == 0) ? TX1_BIAS_LB_ON: TX2_BIAS_LB_ON; + + const auto band = e3xx_radio_control_impl::map_freq_to_tx_band(freq); + switch(band) { + case tx_band::LB_80: + tx_sw1 = TX_SW1_LB_80; + vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; + break; + case tx_band::LB_160: + tx_sw1 = TX_SW1_LB_160; + vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; + break; + case tx_band::LB_225: + tx_sw1 = TX_SW1_LB_225; + vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; + break; + case tx_band::LB_400: + tx_sw1 = TX_SW1_LB_400; + vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; + break; + case tx_band::LB_575: + tx_sw1 = TX_SW1_LB_575; + vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; + break; + case tx_band::LB_1000: + tx_sw1 = TX_SW1_LB_1000; + vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; + break; + case tx_band::LB_1700: + tx_sw1 = TX_SW1_LB_1700; + vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; + break; + case tx_band::LB_2750: + tx_sw1 = TX_SW1_LB_2750; + vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; + break; + case tx_band::HB: + tx_sw1 = TX_SW1_LB_80; + vctxrx_sw = VCTXRX_SW_TX_HB; + tx_bias = (fe_chan == 0) ? TX1_BIAS_HB_ON: TX2_BIAS_HB_ON; + break; + case tx_band::INVALID_BAND: + RFNOC_LOG_ERROR( + "Cannot map TX frequency to band: " << freq); + UHD_THROW_INVALID_CODE_PATH(); + break; + } + auto tx_regs = 0 | + vctxrx_sw << VCTXRX_SW_SHIFT | + tx_bias << TX_BIAS_SHIFT | + tx_sw1 << TX_SW1_SHIFT; + return tx_regs; +} + +uint32_t e31x_radio_control_impl::get_rx_switches( + const size_t chan, + const double freq, + const std::string &ant +){ + RFNOC_LOG_TRACE( + "Update all RX freq related switches. f=" << freq << " Hz, " + ); + + size_t fe_chan = _fe_swap ? (chan ? 0 : 1): chan; + + // Default to OFF + auto rx_sw1 = RX_SW1_OFF; + auto rx_swc = RX_SWC_OFF; + auto rx_swb = RX_SWB_OFF; + auto vctxrx_sw = VCTXRX_SW_OFF; + auto vcrx_sw = VCRX_SW_LB; + if (ant == "TX/RX") { + vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_RX: VCTXRX2_SW_RX; + } + + RFNOC_LOG_INFO("RX freq = " << freq); + const auto band = e3xx_radio_control_impl::map_freq_to_rx_band(freq); + RFNOC_LOG_INFO("RX band = " << int(band)); + + switch(band) { + case rx_band::LB_B2: + rx_sw1 = RX_SW1_LB_B2; + rx_swc = RX_SWC_LB_B2; + rx_swb = RX_SWB_OFF; + break; + case rx_band::LB_B3: + rx_sw1 = RX_SW1_LB_B3; + rx_swc = RX_SWC_LB_B3; + rx_swb = RX_SWB_OFF; + break; + case rx_band::LB_B4: + rx_sw1 = RX_SW1_LB_B4; + rx_swc = RX_SWC_LB_B4; + rx_swb = RX_SWB_OFF; + break; + case rx_band::LB_B5: + rx_sw1 = RX_SW1_LB_B5; + rx_swc = RX_SWC_OFF; + rx_swb = RX_SWB_LB_B5; + break; + case rx_band::LB_B6: + rx_sw1 = RX_SW1_LB_B6; + rx_swc = RX_SWC_OFF; + rx_swb = RX_SWB_LB_B6; + break; + case rx_band::LB_B7: + rx_sw1 = RX_SW1_LB_B7; + rx_swc = RX_SWC_OFF; + rx_swb = RX_SWB_LB_B7; + break; + case rx_band::HB: + rx_sw1 = RX_SW1_OFF; + rx_swc = RX_SWC_OFF; + rx_swb = RX_SWB_OFF; + vcrx_sw = VCRX_SW_HB; + break; + case rx_band::INVALID_BAND: + RFNOC_LOG_ERROR("Cannot map RX frequency to band: " << freq); + UHD_THROW_INVALID_CODE_PATH(); + break; + } + RFNOC_LOG_INFO("RX SW1 = " << rx_sw1); + RFNOC_LOG_INFO("RX SWC = " << rx_swc); + RFNOC_LOG_INFO("RX SWB = " << rx_swb); + RFNOC_LOG_INFO("RX VCRX_SW = " << vcrx_sw); + RFNOC_LOG_INFO("RX VCTXRX_SW = " << vctxrx_sw); + + auto rx_regs = 0 | + vcrx_sw << VCRX_SW_SHIFT | + vctxrx_sw << VCTXRX_SW_SHIFT | + rx_swc << RX_SWC_SHIFT | + rx_swb << RX_SWB_SHIFT | + rx_sw1 << RX_SW1_SHIFT; + return rx_regs; +} + +uint32_t e31x_radio_control_impl::get_idle_switches() +{ + uint32_t idle_regs = VCRX_SW_OFF << VCRX_SW_SHIFT | + VCTXRX_SW_OFF << VCTXRX_SW_SHIFT | + TX_BIAS_OFF << TX_BIAS_SHIFT | + RX_SWC_OFF << RX_SWC_SHIFT | + RX_SWB_OFF << RX_SWB_SHIFT | + RX_SW1_OFF << RX_SW1_SHIFT | + TX_SW1_LB_2750 << TX_SW1_SHIFT; + return idle_regs; +} + +uint32_t e31x_radio_control_impl::get_idle_led() +{ + return 0; +} + +uint32_t e31x_radio_control_impl::get_rx_led() +{ + return 1 << LED_RX_RX_SHIFT; +} + +uint32_t e31x_radio_control_impl::get_tx_led() +{ + return 1 << LED_TXRX_TX_SHIFT; +} + +uint32_t e31x_radio_control_impl::get_txrx_led() +{ + return 1 << LED_TXRX_RX_SHIFT; +} + +UHD_RFNOC_BLOCK_REGISTER_FOR_DEVICE_DIRECT( + e31x_radio_control, RADIO_BLOCK, E310, "Radio", true, "radio_clk", "bus_clk") diff --git a/host/lib/usrp/dboard/e3xx/e31x_radio_ctrl_impl.hpp b/host/lib/usrp/dboard/e3xx/e31x_radio_control_impl.hpp index 581a90c8e..c51d74203 100644 --- a/host/lib/usrp/dboard/e3xx/e31x_radio_ctrl_impl.hpp +++ b/host/lib/usrp/dboard/e3xx/e31x_radio_control_impl.hpp @@ -1,5 +1,6 @@ // // Copyright 2018 Ettus Research, a National Instruments Company +// Copyright 2019 Ettus Research, a National Instruments Brand // // SPDX-License-Identifier: GPL-3.0-or-later // @@ -8,30 +9,37 @@ #define INCLUDED_LIBUHD_RFNOC_E31X_RADIO_CTRL_IMPL_HPP #include "e3xx_constants.hpp" -#include "e3xx_radio_ctrl_impl.hpp" +#include "e3xx_radio_control_impl.hpp" -namespace uhd { - namespace rfnoc { +namespace { +static constexpr char E31x_GPIO_BANK[] = "INT0"; +} -/*! \brief Provide access to an E31X radio. +namespace uhd { namespace rfnoc { + +/*! Provide access to an E31X radio. + * + * This class only contains hardware-specific things that are different between + * E31X and E320. */ -class e31x_radio_ctrl_impl : public e3xx_radio_ctrl_impl +class e31x_radio_control_impl : public e3xx_radio_control_impl { public: /************************************************************************ - * Structors + * Structors and deinit ***********************************************************************/ - e31x_radio_ctrl_impl( - const make_args_t &make_args - ); - virtual ~e31x_radio_ctrl_impl(); + e31x_radio_control_impl(make_args_ptr make_args); + virtual ~e31x_radio_control_impl(); -protected: + std::vector<std::string> get_gpio_banks() const + { + return {E31x_GPIO_BANK}; + } +private: /************************************************************************** * ATR/ Switches Types *************************************************************************/ - enum tx_sw1_t { TX_SW1_LB_80 = 7, TX_SW1_LB_160 = 6, @@ -93,32 +101,13 @@ protected: }; /************************************************************************ - * API calls + * E3XX API calls ***********************************************************************/ - virtual bool check_radio_config(); - const std::string get_default_timing_mode() { return TIMING_MODE_1R1T; }; - /*! Run a loopback self test. - * - * This will write data to the AD936x and read it back again. - * If this test fails, it generally means the interface is broken, - * so we assume it passes and throw otherwise. Running this requires - * a core that we can peek and poke the loopback values into. - * - * \param iface An interface to the associated radio control core - * \param iface The radio control core's address to write the loopback value - * \param iface The radio control core's readback address to read back the returned - * value - * - * \throws a uhd::runtime_error if the loopback value didn't match. - */ - void loopback_self_test(std::function<void(uint32_t)> poker_functor, - std::function<uint64_t()> peeker_functor); - uint32_t get_rx_switches( const size_t chan, const double freq, @@ -136,7 +125,7 @@ protected: uint32_t get_rx_led(); uint32_t get_txrx_led(); uint32_t get_idle_led(); -}; /* class radio_ctrl_impl */ +}; }} /* namespace uhd::rfnoc */ diff --git a/host/lib/usrp/dboard/e3xx/e31x_radio_ctrl_impl.cpp b/host/lib/usrp/dboard/e3xx/e31x_radio_ctrl_impl.cpp deleted file mode 100644 index 60df247a2..000000000 --- a/host/lib/usrp/dboard/e3xx/e31x_radio_ctrl_impl.cpp +++ /dev/null @@ -1,300 +0,0 @@ -// -// Copyright 2018 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "e31x_radio_ctrl_impl.hpp" -#include "e31x_regs.hpp" - -using namespace uhd; -using namespace uhd::usrp; -using namespace uhd::rfnoc; - -e31x_radio_ctrl_impl::e31x_radio_ctrl_impl( - const make_args_t &make_args -): block_ctrl_base(make_args) -{ - // Swap front ends for E310 - _fe_swap = true; -} - -e31x_radio_ctrl_impl::~e31x_radio_ctrl_impl() -{ - UHD_LOG_TRACE(unique_id(), "e31x_radio_ctrl_impl::dtor() "); -} - -/****************************************************************************** - * API Calls - *****************************************************************************/ -bool e31x_radio_ctrl_impl::check_radio_config() -{ - // mapping of frontend to radio perif index - static const size_t FE0 = 1; - static const size_t FE1 = 0; - const size_t num_rx = _is_streamer_active(RX_DIRECTION, FE0) + _is_streamer_active(RX_DIRECTION, FE1); - const size_t num_tx = _is_streamer_active(TX_DIRECTION, FE0) + _is_streamer_active(TX_DIRECTION, FE1); - - //setup the active chains in the codec - if ((num_rx + num_tx) == 0) { - // Ensure at least one RX chain is enabled so AD9361 outputs a sample clock - this->set_streaming_mode(true, false, true, false); - } else { - this->set_streaming_mode( - _is_streamer_active(TX_DIRECTION, FE0), - _is_streamer_active(TX_DIRECTION, FE1), - _is_streamer_active(RX_DIRECTION, FE0), - _is_streamer_active(RX_DIRECTION, FE1) - ); - } - return true; -} - -/* loopback_self_test checks the integrity of the FPGA->AD936x->FPGA sample interface. - The AD936x is put in loopback mode that sends the TX data unchanged to the RX side. - A test value is written to the codec_idle register in the TX side of the radio. - The readback register is then used to capture the values on the TX and RX sides - simultaneously for comparison. It is a reasonably effective test for AC timing - since I/Q Ch0/Ch1 alternate over the same wires. Note, however, that it uses - whatever timing is configured at the time the test is called rather than select - worst case conditions to stress the interface. - Note: This currently only tests 2R2T mode -*/ -void e31x_radio_ctrl_impl::loopback_self_test( - std::function<void(uint32_t)> poker_functor, std::function<uint64_t()> peeker_functor) -{ - // Save current rate before running this test - const double current_rate = this->get_rate(); - // Set 2R2T mode, stream on all channels - this->set_streaming_mode(true, false, true, false); - // Set maximum rate for 2R2T mode - this->set_rate(30.72e6); - // Put AD936x in loopback mode - _ad9361->data_port_loopback(true); - UHD_LOG_INFO(unique_id(), "Performing CODEC loopback test... "); - size_t hash = size_t(time(NULL)); - constexpr size_t loopback_count = 100; - - // Allow some time for AD936x to enter loopback mode. - // There is no clear statement in the documentation of how long it takes, - // but UG-570 does say to "allow six ADC_CLK/64 clock cycles of flush time" - // when leaving the TX or RX states. That works out to ~75us at the - // minimum clock rate of 5 MHz, which lines up with test results. - // Sleeping 1ms is far more than enough. - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - - for (size_t i = 0; i < loopback_count; i++) { - // Create test word - boost::hash_combine(hash, i); - const uint32_t word32 = uint32_t(hash) & 0xfff0fff0; - // const uint32_t word32 = 0xCA00C100; - // Write test word to codec_idle idle register (on TX side) - poker_functor(word32); - - // Read back values - TX is lower 32-bits and RX is upper 32-bits - const uint64_t rb_word64 = peeker_functor(); - const uint32_t rb_tx = uint32_t(rb_word64 >> 32); - const uint32_t rb_rx = uint32_t(rb_word64 & 0xffffffff); - - // Compare TX and RX values to test word - bool test_fail = word32 != rb_tx or word32 != rb_rx; - if (test_fail) { - UHD_LOG_WARNING(unique_id(), - "CODEC loopback test failed! " - << boost::format("Expected: 0x%08X Received (TX/RX): 0x%08X/0x%08X") - % word32 % rb_tx % rb_rx); - throw uhd::runtime_error("CODEC loopback test failed."); - } - } - UHD_LOG_INFO(unique_id(), "CODEC loopback test passed"); - - // Zero out the idle data. - poker_functor(0); - - // Take AD936x out of loopback mode - _ad9361->data_port_loopback(false); - this->set_streaming_mode(true, false, true, false); - // Switch back to current rate - this->set_rate(current_rate); -} - - -uint32_t e31x_radio_ctrl_impl::get_tx_switches( - const size_t chan, - const double freq -) { - UHD_LOG_TRACE(unique_id(), - "Update all TX freq related switches. f=" << freq << " Hz, " - ); - - size_t fe_chan = _fe_swap ? (chan ? 0 : 1): chan; - - auto tx_sw1 = TX_SW1_LB_2750; // SW1 = 0 - auto vctxrx_sw = VCTXRX_SW_OFF; - auto tx_bias = (fe_chan == 0) ? TX1_BIAS_LB_ON: TX2_BIAS_LB_ON; - - const auto band = e3xx_radio_ctrl_impl::map_freq_to_tx_band(freq); - switch(band) { - case tx_band::LB_80: - tx_sw1 = TX_SW1_LB_80; - vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; - break; - case tx_band::LB_160: - tx_sw1 = TX_SW1_LB_160; - vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; - break; - case tx_band::LB_225: - tx_sw1 = TX_SW1_LB_225; - vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; - break; - case tx_band::LB_400: - tx_sw1 = TX_SW1_LB_400; - vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; - break; - case tx_band::LB_575: - tx_sw1 = TX_SW1_LB_575; - vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; - break; - case tx_band::LB_1000: - tx_sw1 = TX_SW1_LB_1000; - vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; - break; - case tx_band::LB_1700: - tx_sw1 = TX_SW1_LB_1700; - vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; - break; - case tx_band::LB_2750: - tx_sw1 = TX_SW1_LB_2750; - vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; - break; - case tx_band::HB: - tx_sw1 = TX_SW1_LB_80; - vctxrx_sw = VCTXRX_SW_TX_HB; - tx_bias = (fe_chan == 0) ? TX1_BIAS_HB_ON: TX2_BIAS_HB_ON; - break; - case tx_band::INVALID_BAND: - UHD_LOG_ERROR(unique_id(), - "Cannot map TX frequency to band: " << freq); - UHD_THROW_INVALID_CODE_PATH(); - break; - } - auto tx_regs = 0 | - vctxrx_sw << VCTXRX_SW_SHIFT | - tx_bias << TX_BIAS_SHIFT | - tx_sw1 << TX_SW1_SHIFT; - return tx_regs; -} - -uint32_t e31x_radio_ctrl_impl::get_rx_switches( - const size_t chan, - const double freq, - const std::string &ant -){ - UHD_LOG_TRACE(unique_id(), - "Update all RX freq related switches. f=" << freq << " Hz, " - ); - - size_t fe_chan = _fe_swap ? (chan ? 0 : 1): chan; - - // Default to OFF - auto rx_sw1 = RX_SW1_OFF; - auto rx_swc = RX_SWC_OFF; - auto rx_swb = RX_SWB_OFF; - auto vctxrx_sw = VCTXRX_SW_OFF; - auto vcrx_sw = VCRX_SW_LB; - if (ant == "TX/RX") { - vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_RX: VCTXRX2_SW_RX; - } - - const auto band = e3xx_radio_ctrl_impl::map_freq_to_rx_band(freq); - - switch(band) { - case rx_band::LB_B2: - rx_sw1 = RX_SW1_LB_B2; - rx_swc = RX_SWC_LB_B2; - rx_swb = RX_SWB_OFF; - break; - case rx_band::LB_B3: - rx_sw1 = RX_SW1_LB_B3; - rx_swc = RX_SWC_LB_B3; - rx_swb = RX_SWB_OFF; - break; - case rx_band::LB_B4: - rx_sw1 = RX_SW1_LB_B4; - rx_swc = RX_SWC_LB_B4; - rx_swb = RX_SWB_OFF; - break; - case rx_band::LB_B5: - rx_sw1 = RX_SW1_LB_B5; - rx_swc = RX_SWC_OFF; - rx_swb = RX_SWB_LB_B5; - break; - case rx_band::LB_B6: - rx_sw1 = RX_SW1_LB_B6; - rx_swc = RX_SWC_OFF; - rx_swb = RX_SWB_LB_B6; - break; - case rx_band::LB_B7: - rx_sw1 = RX_SW1_LB_B7; - rx_swc = RX_SWC_OFF; - rx_swb = RX_SWB_LB_B7; - break; - case rx_band::HB: - rx_sw1 = RX_SW1_OFF; - rx_swc = RX_SWC_OFF; - rx_swb = RX_SWB_OFF; - vcrx_sw = VCRX_SW_HB; - break; - case rx_band::INVALID_BAND: - UHD_LOG_ERROR(unique_id(), - "Cannot map RX frequency to band: " << freq); - UHD_THROW_INVALID_CODE_PATH(); - break; - } - - UHD_LOG_TRACE(unique_id(), - "RX band = " << int(band) << "RX SW1 = " << rx_sw1 << "RX SWC = " << rx_swc - << "RX SWB = " << rx_swb << "RX VCRX_SW = " << vcrx_sw - << "RX VCTXRX_SW = " << vctxrx_sw); - - auto rx_regs = 0 | - vcrx_sw << VCRX_SW_SHIFT | - vctxrx_sw << VCTXRX_SW_SHIFT | - rx_swc << RX_SWC_SHIFT | - rx_swb << RX_SWB_SHIFT | - rx_sw1 << RX_SW1_SHIFT; - return rx_regs; -} - -uint32_t e31x_radio_ctrl_impl::get_idle_switches() -{ - uint32_t idle_regs = VCRX_SW_OFF << VCRX_SW_SHIFT | - VCTXRX_SW_OFF << VCTXRX_SW_SHIFT | - TX_BIAS_OFF << TX_BIAS_SHIFT | - RX_SWC_OFF << RX_SWC_SHIFT | - RX_SWB_OFF << RX_SWB_SHIFT | - RX_SW1_OFF << RX_SW1_SHIFT | - TX_SW1_LB_2750 << TX_SW1_SHIFT; - return idle_regs; -} - -uint32_t e31x_radio_ctrl_impl::get_idle_led() -{ - return 0; -} - -uint32_t e31x_radio_ctrl_impl::get_rx_led() -{ - return 1 << LED_RX_RX_SHIFT; -} - -uint32_t e31x_radio_ctrl_impl::get_tx_led() -{ - return 1 << LED_TXRX_TX_SHIFT; -} - -uint32_t e31x_radio_ctrl_impl::get_txrx_led() -{ - return 1 << LED_TXRX_RX_SHIFT; -} -UHD_RFNOC_BLOCK_REGISTER(e31x_radio_ctrl, "E31XRadio"); diff --git a/host/lib/usrp/dboard/e3xx/e320_radio_control_impl.cpp b/host/lib/usrp/dboard/e3xx/e320_radio_control_impl.cpp new file mode 100644 index 000000000..df325bb75 --- /dev/null +++ b/host/lib/usrp/dboard/e3xx/e320_radio_control_impl.cpp @@ -0,0 +1,183 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "e320_radio_control_impl.hpp" +#include "e320_regs.hpp" +#include <uhd/rfnoc/registry.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; + +e320_radio_control_impl::e320_radio_control_impl(make_args_ptr make_args) + : e3xx_radio_control_impl(std::move(make_args)) +{ + RFNOC_LOG_TRACE("e320_radio_control_impl::ctor()"); + // Don't swap front ends for E320 + _fe_swap = false; + _init_mpm(); +} + +e320_radio_control_impl::~e320_radio_control_impl() +{ + RFNOC_LOG_TRACE("e320_radio_control_impl::dtor() "); +} + +/****************************************************************************** + * E320 API Calls + *****************************************************************************/ +uint32_t e320_radio_control_impl::get_tx_switches(const size_t chan, const double freq) +{ + RFNOC_LOG_TRACE("Update all TX freq related switches. f=" << freq << " Hz, "); + auto tx_sw1 = TX_SW1_LB_160; + auto tx_sw2 = TX_SW2_LB_160; + auto trx_sw = (chan == 0) ? TRX1_SW_TX_LB : TRX2_SW_TX_LB; + auto tx_amp = TX_AMP_LF_ON; + + const auto band = e3xx_radio_control_impl::map_freq_to_tx_band(freq); + switch (band) { + case tx_band::LB_80: + tx_sw1 = TX_SW1_LB_80; + tx_sw2 = TX_SW2_LB_80; + break; + case tx_band::LB_160: + tx_sw1 = TX_SW1_LB_160; + tx_sw2 = TX_SW2_LB_160; + break; + case tx_band::LB_225: + tx_sw1 = TX_SW1_LB_225; + tx_sw2 = TX_SW2_LB_225; + break; + case tx_band::LB_400: + tx_sw1 = TX_SW1_LB_400; + tx_sw2 = TX_SW2_LB_400; + break; + case tx_band::LB_575: + tx_sw1 = TX_SW1_LB_575; + tx_sw2 = TX_SW2_LB_575; + break; + case tx_band::LB_1000: + tx_sw1 = TX_SW1_LB_1000; + tx_sw2 = TX_SW2_LB_1000; + break; + case tx_band::LB_1700: + tx_sw1 = TX_SW1_LB_1700; + tx_sw2 = TX_SW2_LB_1700; + break; + case tx_band::LB_2750: + tx_sw1 = TX_SW1_LB_2750; + tx_sw2 = TX_SW2_LB_2750; + break; + case tx_band::HB: + trx_sw = (chan == 0) ? TRX1_SW_TX_HB : TRX2_SW_TX_HB; + tx_amp = TX_AMP_HF_ON; + break; + case tx_band::INVALID_BAND: + RFNOC_LOG_ERROR("Cannot map TX frequency to band: " << freq); + UHD_THROW_INVALID_CODE_PATH(); + break; + } + + auto tx_regs = tx_amp << TX_AMP_SHIFT | trx_sw << TRX_SW_SHIFT + | tx_sw2 << TX_SW2_SHIFT | tx_sw1 << TX_SW1_SHIFT; + return tx_regs; +} + +uint32_t e320_radio_control_impl::get_rx_switches( + const size_t chan, const double freq, const std::string& ant) +{ + RFNOC_LOG_TRACE("Update all RX freq related switches. f=" << (freq / 1e6) << " MHz"); + // Default to OFF + auto rx_sw1 = RX_SW1_OFF; + auto rx_sw2 = RX_SW2_OFF; + auto rx_sw3 = RX_SW3_OFF; + auto trx_sw = (chan == 0) ? TRX1_SW_RX : TRX2_SW_RX; + if (ant == "TX/RX") { + rx_sw3 = RX_SW3_HBRX_LBTRX; + trx_sw = (chan == 0) ? TRX1_SW_RX : TRX2_SW_RX; + } else if (ant == "RX2") { + rx_sw3 = RX_SW3_HBTRX_LBRX; + // Set TRX switch to TX when receiving on RX2 + trx_sw = TRX1_SW_TX_HB; + } + + const auto band = e3xx_radio_control_impl::map_freq_to_rx_band(freq); + switch (band) { + case rx_band::LB_B2: + rx_sw1 = RX_SW1_LB_B2; + rx_sw2 = RX_SW2_LB_B2; + break; + case rx_band::LB_B3: + rx_sw1 = RX_SW1_LB_B3; + rx_sw2 = RX_SW2_LB_B3; + break; + case rx_band::LB_B4: + rx_sw1 = RX_SW1_LB_B4; + rx_sw2 = RX_SW2_LB_B4; + break; + case rx_band::LB_B5: + rx_sw1 = RX_SW1_LB_B5; + rx_sw2 = RX_SW2_LB_B5; + break; + case rx_band::LB_B6: + rx_sw1 = RX_SW1_LB_B6; + rx_sw2 = RX_SW2_LB_B6; + break; + case rx_band::LB_B7: + rx_sw1 = RX_SW1_LB_B7; + rx_sw2 = RX_SW2_LB_B7; + break; + case rx_band::HB: + rx_sw1 = RX_SW1_OFF; + rx_sw2 = RX_SW2_OFF; + if (ant == "TX/RX") { + rx_sw3 = RX_SW3_HBTRX_LBRX; + } else if (ant == "RX2") { + rx_sw3 = RX_SW3_HBRX_LBTRX; + } + break; + case rx_band::INVALID_BAND: + RFNOC_LOG_ERROR("Cannot map RX frequency to band: " << freq); + UHD_THROW_INVALID_CODE_PATH(); + break; + } + + auto rx_regs = trx_sw << TRX_SW_SHIFT | rx_sw3 << RX_SW3_SHIFT + | rx_sw2 << RX_SW2_SHIFT | rx_sw1 << RX_SW1_SHIFT; + return rx_regs; +} + +uint32_t e320_radio_control_impl::get_idle_switches() +{ + uint32_t idle_regs = TX_AMP_OFF << TX_AMP_SHIFT | TRX1_SW_TX_HB << TRX_SW_SHIFT + | TX_SW2_LB_80 << TX_SW2_SHIFT | TX_SW1_LB_80 << TX_SW1_SHIFT + | RX_SW3_OFF << RX_SW3_SHIFT | RX_SW2_OFF << RX_SW2_SHIFT + | RX_SW1_OFF << RX_SW1_SHIFT; + return idle_regs; +} + +uint32_t e320_radio_control_impl::get_idle_led() +{ + return 0; +} + +uint32_t e320_radio_control_impl::get_rx_led() +{ + return 1 << TRX_LED_GRN_SHIFT; +} + +uint32_t e320_radio_control_impl::get_tx_led() +{ + return 1 << TX_LED_RED_SHIFT; +} + +uint32_t e320_radio_control_impl::get_txrx_led() +{ + return 1 << RX_LED_GRN_SHIFT; +} + +UHD_RFNOC_BLOCK_REGISTER_FOR_DEVICE_DIRECT( + e320_radio_control, RADIO_BLOCK, E320, "Radio", true, "radio_clk", "bus_clk") diff --git a/host/lib/usrp/dboard/e3xx/e320_radio_ctrl_impl.hpp b/host/lib/usrp/dboard/e3xx/e320_radio_control_impl.hpp index 7f75cadc7..f781eb49d 100644 --- a/host/lib/usrp/dboard/e3xx/e320_radio_ctrl_impl.hpp +++ b/host/lib/usrp/dboard/e3xx/e320_radio_control_impl.hpp @@ -8,20 +8,32 @@ # define INCLUDED_LIBUHD_RFNOC_E320_RADIO_CTRL_IMPL_HPP # include "e3xx_constants.hpp" -# include "e3xx_radio_ctrl_impl.hpp" +# include "e3xx_radio_control_impl.hpp" + +namespace { +static constexpr char E320_GPIO_BANK[] = "FP0"; +} namespace uhd { namespace rfnoc { /*! \brief Provide access to an E320 radio. + * + * This class only contains hardware-specific things that are different between + * E320 and E31X. */ -class e320_radio_ctrl_impl : public e3xx_radio_ctrl_impl +class e320_radio_control_impl : public e3xx_radio_control_impl { public: /************************************************************************ * Structors ***********************************************************************/ - e320_radio_ctrl_impl(const make_args_t& make_args); - virtual ~e320_radio_ctrl_impl(); + e320_radio_control_impl(make_args_ptr make_args); + virtual ~e320_radio_control_impl(); + + std::vector<std::string> get_gpio_banks() const + { + return {E320_GPIO_BANK}; + } protected: /************************************************************************** @@ -89,32 +101,13 @@ protected: enum tx_amp_t { TX_AMP_HF_ON = 2, TX_AMP_LF_ON = 1, TX_AMP_OFF = 3 }; /************************************************************************ - * API calls + * E3XX API calls ***********************************************************************/ - virtual bool check_radio_config(); - const std::string get_default_timing_mode() { return TIMING_MODE_2R2T; }; - /*! Run a loopback self test. - * - * This will write data to the AD936x and read it back again. - * If this test fails, it generally means the interface is broken, - * so we assume it passes and throw otherwise. Running this requires - * a core that we can peek and poke the loopback values into. - * - * \param iface An interface to the associated radio control core - * \param iface The radio control core's address to write the loopback value - * \param iface The radio control core's readback address to read back the returned - * value - * - * \throws a uhd::runtime_error if the loopback value didn't match. - */ - void loopback_self_test(std::function<void(uint32_t)> poker_functor, - std::function<uint64_t()> peeker_functor); - uint32_t get_rx_switches( const size_t chan, const double freq, const std::string& ant); @@ -126,7 +119,7 @@ protected: uint32_t get_rx_led(); uint32_t get_txrx_led(); uint32_t get_idle_led(); -}; /* class radio_ctrl_impl */ +}; }} /* namespace uhd::rfnoc */ diff --git a/host/lib/usrp/dboard/e3xx/e320_radio_ctrl_impl.cpp b/host/lib/usrp/dboard/e3xx/e320_radio_ctrl_impl.cpp deleted file mode 100644 index c48cabc9c..000000000 --- a/host/lib/usrp/dboard/e3xx/e320_radio_ctrl_impl.cpp +++ /dev/null @@ -1,272 +0,0 @@ -// -// Copyright 2018 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "e320_radio_ctrl_impl.hpp" -#include "e320_regs.hpp" - -using namespace uhd; -using namespace uhd::usrp; -using namespace uhd::rfnoc; - -e320_radio_ctrl_impl::e320_radio_ctrl_impl(const make_args_t& make_args) - : block_ctrl_base(make_args) -{ - UHD_LOG_TRACE(unique_id(), "Entering e320_radio_ctrl_impl ctor..."); - // Don't swap front ends for E320 - _fe_swap = false; -} - -e320_radio_ctrl_impl::~e320_radio_ctrl_impl() -{ - UHD_LOG_TRACE(unique_id(), "e320_radio_ctrl_impl::dtor() "); -} - -/****************************************************************************** - * API Calls - *****************************************************************************/ -bool e320_radio_ctrl_impl::check_radio_config() -{ - // mapping of frontend to radio perif index - static const size_t FE0 = 0; - static const size_t FE1 = 1; - const size_t num_rx = - _is_streamer_active(RX_DIRECTION, FE0) + _is_streamer_active(RX_DIRECTION, FE1); - const size_t num_tx = - _is_streamer_active(TX_DIRECTION, FE0) + _is_streamer_active(TX_DIRECTION, FE1); - - // setup the active chains in the codec - if ((num_rx + num_tx) == 0) { - // Ensure at least one RX chain is enabled so AD9361 outputs a sample clock - this->set_streaming_mode(true, false, true, false); - } else { - this->set_streaming_mode(_is_streamer_active(TX_DIRECTION, FE0), - _is_streamer_active(TX_DIRECTION, FE1), - _is_streamer_active(RX_DIRECTION, FE0), - _is_streamer_active(RX_DIRECTION, FE1)); - } - return true; -} - -/* loopback_self_test checks the integrity of the FPGA->AD936x->FPGA sample interface. - The AD936x is put in loopback mode that sends the TX data unchanged to the RX side. - A test value is written to the codec_idle register in the TX side of the radio. - The readback register is then used to capture the values on the TX and RX sides - simultaneously for comparison. It is a reasonably effective test for AC timing - since I/Q Ch0/Ch1 alternate over the same wires. Note, however, that it uses - whatever timing is configured at the time the test is called rather than select - worst case conditions to stress the interface. - Note: This currently only tests 2R2T mode -*/ -void e320_radio_ctrl_impl::loopback_self_test( - std::function<void(uint32_t)> poker_functor, std::function<uint64_t()> peeker_functor) -{ - // Save current rate before running this test - const double current_rate = this->get_rate(); - // Set 2R2T mode, stream on all channels - this->set_streaming_mode(true, true, true, true); - // Set maximum rate for 2R2T mode - this->set_rate(30.72e6); - // Put AD936x in loopback mode - _ad9361->data_port_loopback(true); - UHD_LOG_INFO(unique_id(), "Performing CODEC loopback test... "); - size_t hash = size_t(time(NULL)); - constexpr size_t loopback_count = 100; - - // Allow some time for AD936x to enter loopback mode. - // There is no clear statement in the documentation of how long it takes, - // but UG-570 does say to "allow six ADC_CLK/64 clock cycles of flush time" - // when leaving the TX or RX states. That works out to ~75us at the - // minimum clock rate of 5 MHz, which lines up with test results. - // Sleeping 1ms is far more than enough. - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - - for (size_t i = 0; i < loopback_count; i++) { - // Create test word - boost::hash_combine(hash, i); - const uint32_t word32 = uint32_t(hash) & 0xfff0fff0; - // const uint32_t word32 = 0xCA00C100; - // Write test word to codec_idle idle register (on TX side) - poker_functor(word32); - - // Read back values - TX is lower 32-bits and RX is upper 32-bits - const uint64_t rb_word64 = peeker_functor(); - const uint32_t rb_tx = uint32_t(rb_word64 >> 32); - const uint32_t rb_rx = uint32_t(rb_word64 & 0xffffffff); - - // Compare TX and RX values to test word - bool test_fail = word32 != rb_tx or word32 != rb_rx; - if (test_fail) { - UHD_LOG_WARNING(unique_id(), - "CODEC loopback test failed! " - << boost::format("Expected: 0x%08X Received (TX/RX): 0x%08X/0x%08X") - % word32 % rb_tx % rb_rx); - throw uhd::runtime_error("CODEC loopback test failed."); - } - } - UHD_LOG_INFO(unique_id(), "CODEC loopback test passed"); - - // Zero out the idle data. - poker_functor(0); - - // Take AD936x out of loopback mode - _ad9361->data_port_loopback(false); - this->set_streaming_mode(true, false, true, false); - // Switch back to current rate - this->set_rate(current_rate); -} - -uint32_t e320_radio_ctrl_impl::get_tx_switches(const size_t chan, const double freq) -{ - UHD_LOG_TRACE( - unique_id(), "Update all TX freq related switches. f=" << freq << " Hz, "); - auto tx_sw1 = TX_SW1_LB_160; - auto tx_sw2 = TX_SW2_LB_160; - auto trx_sw = (chan == 0) ? TRX1_SW_TX_LB : TRX2_SW_TX_LB; - auto tx_amp = TX_AMP_LF_ON; - - const auto band = e3xx_radio_ctrl_impl::map_freq_to_tx_band(freq); - switch (band) { - case tx_band::LB_80: - tx_sw1 = TX_SW1_LB_80; - tx_sw2 = TX_SW2_LB_80; - break; - case tx_band::LB_160: - tx_sw1 = TX_SW1_LB_160; - tx_sw2 = TX_SW2_LB_160; - break; - case tx_band::LB_225: - tx_sw1 = TX_SW1_LB_225; - tx_sw2 = TX_SW2_LB_225; - break; - case tx_band::LB_400: - tx_sw1 = TX_SW1_LB_400; - tx_sw2 = TX_SW2_LB_400; - break; - case tx_band::LB_575: - tx_sw1 = TX_SW1_LB_575; - tx_sw2 = TX_SW2_LB_575; - break; - case tx_band::LB_1000: - tx_sw1 = TX_SW1_LB_1000; - tx_sw2 = TX_SW2_LB_1000; - break; - case tx_band::LB_1700: - tx_sw1 = TX_SW1_LB_1700; - tx_sw2 = TX_SW2_LB_1700; - break; - case tx_band::LB_2750: - tx_sw1 = TX_SW1_LB_2750; - tx_sw2 = TX_SW2_LB_2750; - break; - case tx_band::HB: - trx_sw = (chan == 0) ? TRX1_SW_TX_HB : TRX2_SW_TX_HB; - tx_amp = TX_AMP_HF_ON; - break; - case tx_band::INVALID_BAND: - UHD_LOG_ERROR(unique_id(), "Cannot map TX frequency to band: " << freq); - UHD_THROW_INVALID_CODE_PATH(); - break; - } - - auto tx_regs = tx_amp << TX_AMP_SHIFT | trx_sw << TRX_SW_SHIFT - | tx_sw2 << TX_SW2_SHIFT | tx_sw1 << TX_SW1_SHIFT; - return tx_regs; -} - -uint32_t e320_radio_ctrl_impl::get_rx_switches( - const size_t chan, const double freq, const std::string& ant) -{ - UHD_LOG_TRACE( - unique_id(), "Update all RX freq related switches. f=" << freq << " Hz, "); - // Default to OFF - auto rx_sw1 = RX_SW1_OFF; - auto rx_sw2 = RX_SW2_OFF; - auto rx_sw3 = RX_SW3_OFF; - auto trx_sw = (chan == 0) ? TRX1_SW_RX : TRX2_SW_RX; - if (ant == "TX/RX") { - rx_sw3 = RX_SW3_HBRX_LBTRX; - trx_sw = (chan == 0) ? TRX1_SW_RX : TRX2_SW_RX; - } else if (ant == "RX2") { - rx_sw3 = RX_SW3_HBTRX_LBRX; - // Set TRX switch to TX when receiving on RX2 - trx_sw = TRX1_SW_TX_HB; - } - - const auto band = e3xx_radio_ctrl_impl::map_freq_to_rx_band(freq); - switch (band) { - case rx_band::LB_B2: - rx_sw1 = RX_SW1_LB_B2; - rx_sw2 = RX_SW2_LB_B2; - break; - case rx_band::LB_B3: - rx_sw1 = RX_SW1_LB_B3; - rx_sw2 = RX_SW2_LB_B3; - break; - case rx_band::LB_B4: - rx_sw1 = RX_SW1_LB_B4; - rx_sw2 = RX_SW2_LB_B4; - break; - case rx_band::LB_B5: - rx_sw1 = RX_SW1_LB_B5; - rx_sw2 = RX_SW2_LB_B5; - break; - case rx_band::LB_B6: - rx_sw1 = RX_SW1_LB_B6; - rx_sw2 = RX_SW2_LB_B6; - break; - case rx_band::LB_B7: - rx_sw1 = RX_SW1_LB_B7; - rx_sw2 = RX_SW2_LB_B7; - break; - case rx_band::HB: - rx_sw1 = RX_SW1_OFF; - rx_sw2 = RX_SW2_OFF; - if (ant == "TX/RX") { - rx_sw3 = RX_SW3_HBTRX_LBRX; - } else if (ant == "RX2") { - rx_sw3 = RX_SW3_HBRX_LBTRX; - } - break; - case rx_band::INVALID_BAND: - UHD_LOG_ERROR(unique_id(), "Cannot map RX frequency to band: " << freq); - UHD_THROW_INVALID_CODE_PATH(); - break; - } - - auto rx_regs = trx_sw << TRX_SW_SHIFT | rx_sw3 << RX_SW3_SHIFT - | rx_sw2 << RX_SW2_SHIFT | rx_sw1 << RX_SW1_SHIFT; - return rx_regs; -} - -uint32_t e320_radio_ctrl_impl::get_idle_switches() -{ - uint32_t idle_regs = TX_AMP_OFF << TX_AMP_SHIFT | TRX1_SW_TX_HB << TRX_SW_SHIFT - | TX_SW2_LB_80 << TX_SW2_SHIFT | TX_SW1_LB_80 << TX_SW1_SHIFT - | RX_SW3_OFF << RX_SW3_SHIFT | RX_SW2_OFF << RX_SW2_SHIFT - | RX_SW1_OFF << RX_SW1_SHIFT; - return idle_regs; -} - -uint32_t e320_radio_ctrl_impl::get_idle_led() -{ - return 0; -} - -uint32_t e320_radio_ctrl_impl::get_rx_led() -{ - return 1 << TRX_LED_GRN_SHIFT; -} - -uint32_t e320_radio_ctrl_impl::get_tx_led() -{ - return 1 << TX_LED_RED_SHIFT; -} - -uint32_t e320_radio_ctrl_impl::get_txrx_led() -{ - return 1 << RX_LED_GRN_SHIFT; -} -UHD_RFNOC_BLOCK_REGISTER(e320_radio_ctrl, "NeonRadio"); diff --git a/host/lib/usrp/dboard/e3xx/e3xx_bands.cpp b/host/lib/usrp/dboard/e3xx/e3xx_bands.cpp index 001cf5d1b..83e96b3ec 100644 --- a/host/lib/usrp/dboard/e3xx/e3xx_bands.cpp +++ b/host/lib/usrp/dboard/e3xx/e3xx_bands.cpp @@ -5,7 +5,7 @@ // #include "e3xx_constants.hpp" -#include "e3xx_radio_ctrl_impl.hpp" +#include "e3xx_radio_control_impl.hpp" #include <uhd/utils/math.hpp> /* @@ -131,9 +131,9 @@ constexpr double E3XX_TX_LB_2750_MIN_FREQ = 1842.6e6; constexpr double E3XX_TX_HB_MIN_FREQ = 2940.0e6; } // namespace -e3xx_radio_ctrl_impl::rx_band e3xx_radio_ctrl_impl::map_freq_to_rx_band(const double freq) +e3xx_radio_control_impl::rx_band e3xx_radio_control_impl::map_freq_to_rx_band(const double freq) { - e3xx_radio_ctrl_impl::rx_band band; + e3xx_radio_control_impl::rx_band band; if (fp_compare_epsilon<double>(freq) < AD9361_RX_MIN_FREQ) { band = rx_band::INVALID_BAND; @@ -158,9 +158,9 @@ e3xx_radio_ctrl_impl::rx_band e3xx_radio_ctrl_impl::map_freq_to_rx_band(const do return band; } -e3xx_radio_ctrl_impl::tx_band e3xx_radio_ctrl_impl::map_freq_to_tx_band(const double freq) +e3xx_radio_control_impl::tx_band e3xx_radio_control_impl::map_freq_to_tx_band(const double freq) { - e3xx_radio_ctrl_impl::tx_band band; + e3xx_radio_control_impl::tx_band band; if (fp_compare_epsilon<double>(freq) < AD9361_TX_MIN_FREQ) { band = tx_band::INVALID_BAND; diff --git a/host/lib/usrp/dboard/e3xx/e3xx_constants.hpp b/host/lib/usrp/dboard/e3xx/e3xx_constants.hpp index 53f64d837..f883a7d72 100644 --- a/host/lib/usrp/dboard/e3xx/e3xx_constants.hpp +++ b/host/lib/usrp/dboard/e3xx/e3xx_constants.hpp @@ -12,12 +12,11 @@ #include <vector> static constexpr size_t FPGPIO_MASTER_RADIO = 0; -static constexpr size_t TOTAL_RADIO_PORTS = 2; -static constexpr double AD9361_RX_MIN_BANDWIDTH = 20.0e6; // HZ -static constexpr double AD9361_RX_MAX_BANDWIDTH = 40.0e6; // HZ +static constexpr double AD9361_RX_MIN_BANDWIDTH = 20.0e6; // Hz +static constexpr double AD9361_RX_MAX_BANDWIDTH = 40.0e6; // Hz -static constexpr double AD9361_TX_MIN_BANDWIDTH = 20.0e6; // HZ -static constexpr double AD9361_TX_MAX_BANDWIDTH = 40.0e6; // HZ +static constexpr double AD9361_TX_MIN_BANDWIDTH = 20.0e6; // Hz +static constexpr double AD9361_TX_MAX_BANDWIDTH = 40.0e6; // Hz static constexpr double AD9361_TX_MIN_FREQ = 47.0e6; // Hz static constexpr double AD9361_TX_MAX_FREQ = 6.0e9; // Hz @@ -44,7 +43,10 @@ static constexpr double E3XX_DEFAULT_BANDWIDTH = 40e6; // Hz static constexpr char E3XX_DEFAULT_RX_ANTENNA[] = "RX2"; static constexpr char E3XX_DEFAULT_TX_ANTENNA[] = "TX/RX"; -static const std::vector<std::string> E3XX_RX_ANTENNAS = {"RX2", "TX/RX"}; +static const std::vector<std::string> E3XX_RX_ANTENNAS = { + E3XX_DEFAULT_RX_ANTENNA, E3XX_DEFAULT_TX_ANTENNA}; + +static constexpr char E3XX_GPIO_BANK[] = "INT0"; static constexpr size_t E3XX_NUM_CHANS = 2; diff --git a/host/lib/usrp/dboard/e3xx/e3xx_radio_control_impl.cpp b/host/lib/usrp/dboard/e3xx/e3xx_radio_control_impl.cpp new file mode 100644 index 000000000..29381a53c --- /dev/null +++ b/host/lib/usrp/dboard/e3xx/e3xx_radio_control_impl.cpp @@ -0,0 +1,621 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "e3xx_radio_control_impl.hpp" +#include "e3xx_constants.hpp" +#include <uhd/transport/chdr.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/types/eeprom.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/math.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/format.hpp> +#include <cmath> +#include <cstdlib> +#include <sstream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; +using namespace uhd::math::fp_compare; + +/****************************************************************************** + * Structors + *****************************************************************************/ +e3xx_radio_control_impl::e3xx_radio_control_impl(make_args_ptr make_args) + : radio_control_impl(std::move(make_args)) +{ + RFNOC_LOG_TRACE("Entering e3xx_radio_control_impl ctor..."); + UHD_ASSERT_THROW(get_block_id().get_block_count() == 0); + UHD_ASSERT_THROW( + std::max(get_num_output_ports(), get_num_input_ports()) == E3XX_NUM_CHANS); + UHD_ASSERT_THROW(get_mb_controller()); + _e3xx_mb_control = std::dynamic_pointer_cast<mpmd_mb_controller>(get_mb_controller()); + UHD_ASSERT_THROW(_e3xx_mb_control); + _e3xx_timekeeper = std::dynamic_pointer_cast<mpmd_mb_controller::mpmd_timekeeper>( + _e3xx_mb_control->get_timekeeper(0)); + UHD_ASSERT_THROW(_e3xx_timekeeper); + _rpcc = _e3xx_mb_control->get_rpc_client(); + UHD_ASSERT_THROW(_rpcc); + RFNOC_LOG_TRACE("Instantiating AD9361 control object..."); + _ad9361 = make_rpc(_rpcc); + + _init_defaults(); + _init_peripherals(); + _init_prop_tree(); + + // Properties + for (auto& samp_rate_prop : _samp_rate_in) { + samp_rate_prop.set(_master_clock_rate); + } + for (auto& samp_rate_prop : _samp_rate_out) { + samp_rate_prop.set(_master_clock_rate); + } +} + +e3xx_radio_control_impl::~e3xx_radio_control_impl() +{ + RFNOC_LOG_TRACE("e3xx_radio_control_impl::dtor() "); +} + +void e3xx_radio_control_impl::deinit() +{ + _db_gpio.clear(); + _leds_gpio.clear(); + _fp_gpio.reset(); + _wb_ifaces.clear(); +} + + +/****************************************************************************** + * API Calls + *****************************************************************************/ +bool e3xx_radio_control_impl::check_topology(const std::vector<size_t>& connected_inputs, + const std::vector<size_t>& connected_outputs) +{ + if (!node_t::check_topology(connected_inputs, connected_outputs)) { + return false; + } + // Now we know that the connected ports are either 0 or 1 + + // Check if we're running a 2x1 or 1x2 configuration -- the device does not + // support this! + if ((connected_outputs.size() == 1 && connected_inputs.size() == 2) + || (connected_outputs.size() == 2 && connected_inputs.size() == 1)) { + const std::string err_msg("Invalid channel configuration: This device does not " + "support 1 TX x 2 RX or 2 TX x 1 RX configurations!"); + RFNOC_LOG_ERROR(err_msg); + throw uhd::runtime_error(err_msg); + } + // mapping of frontend to radio perif index + const size_t FE0 = _fe_swap ? 1 : 0; + const size_t FE1 = _fe_swap ? 0 : 1; + + const bool tx_fe0_active = std::any_of(connected_inputs.begin(), + connected_inputs.end(), + [FE0](const size_t port) { return port == FE0; }); + const bool tx_fe1_active = std::any_of(connected_inputs.begin(), + connected_inputs.end(), + [FE1](const size_t port) { return port == FE1; }); + const bool rx_fe0_active = std::any_of(connected_outputs.begin(), + connected_outputs.end(), + [FE0](const size_t port) { return port == FE0; }); + const bool rx_fe1_active = std::any_of(connected_outputs.begin(), + connected_outputs.end(), + [FE1](const size_t port) { return port == FE1; }); + RFNOC_LOG_TRACE("TX FE0 Active: " << tx_fe0_active); + RFNOC_LOG_TRACE("TX FE1 Active: " << tx_fe1_active); + RFNOC_LOG_TRACE("RX FE0 Active: " << rx_fe0_active); + RFNOC_LOG_TRACE("RX FE1 Active: " << rx_fe1_active); + + //setup the active chains in the codec + if (connected_inputs.size() + connected_outputs.size() == 0) { + // Ensure at least one RX chain is enabled so AD9361 outputs a sample clock + this->set_streaming_mode(true, false, true, false); + } else { + this->set_streaming_mode( + tx_fe0_active, tx_fe1_active, rx_fe0_active, rx_fe1_active); + } + return true; +} + + +void e3xx_radio_control_impl::set_streaming_mode( + const bool tx1, const bool tx2, const bool rx1, const bool rx2) +{ + RFNOC_LOG_TRACE("Setting streaming mode...") + const size_t num_rx = rx1 + rx2; + const size_t num_tx = tx1 + tx2; + + // setup the active chains in the codec + if ((num_rx + num_tx) == 0) { + // Ensure at least one RX chain is enabled so AD9361 outputs a sample clock + _ad9361->set_active_chains(true, false, true, false); + } else { + // setup the active chains in the codec + _ad9361->set_active_chains(tx1, tx2, rx1, rx2); + } + + // setup 1R1T/2R2T mode in catalina and fpga + // The Catalina interface in the fpga needs to know which TX channel to use for + // the data on the LVDS lines. + if ((num_rx == 2) or (num_tx == 2)) { + // AD9361 is in 1R1T mode + _ad9361->set_timing_mode(this->get_default_timing_mode()); + this->set_channel_mode(MIMO); + } else { + // AD9361 is in 1R1T mode + _ad9361->set_timing_mode(TIMING_MODE_1R1T); + + // Set to SIS0_TX1 if we're using the second TX antenna, otherwise + // default to SISO_TX0 + this->set_channel_mode(tx2 ? SISO_TX1 : SISO_TX0); + } +} + +void e3xx_radio_control_impl::set_channel_mode(const std::string& channel_mode) +{ + // MIMO for 2R2T mode for 2 channels + // SISO_TX1 for 1R1T mode for 1 channel - TX1 + // SISO_TX0 for 1R1T mode for 1 channel - TX0 + _rpcc->request_with_token<void>("set_channel_mode", channel_mode); +} + +double e3xx_radio_control_impl::set_rate(const double rate) +{ + std::lock_guard<std::mutex> l(_set_lock); + RFNOC_LOG_DEBUG("Asking for clock rate " << rate / 1e6 << " MHz\n"); + // On E3XX, tick rate and samp rate are always the same + double actual_tick_rate = _ad9361->set_clock_rate(rate); + RFNOC_LOG_DEBUG("Actual clock rate " << actual_tick_rate / 1e6 << " MHz\n"); + set_tick_rate(actual_tick_rate); + radio_control_impl::set_rate(actual_tick_rate); + _e3xx_timekeeper->update_tick_rate(rate); + return rate; +} + +uhd::meta_range_t e3xx_radio_control_impl::get_rate_range() const +{ + return _ad9361->get_clock_rate_range(); +} + +/****************************************************************************** + * RF API calls + *****************************************************************************/ +void e3xx_radio_control_impl::set_tx_antenna(const std::string& ant, const size_t chan) +{ + if (ant != get_tx_antenna(chan)) { + throw uhd::value_error( + str(boost::format("[%s] Requesting invalid TX antenna value: %s") + % get_unique_id() % ant)); + } + radio_control_impl::set_tx_antenna(ant, chan); + // We can't actually set the TX antenna, so let's stop here. +} + +void e3xx_radio_control_impl::set_rx_antenna(const std::string& ant, const size_t chan) +{ + UHD_ASSERT_THROW(chan <= E3XX_NUM_CHANS); + if (std::find(E3XX_RX_ANTENNAS.begin(), E3XX_RX_ANTENNAS.end(), ant) + == E3XX_RX_ANTENNAS.end()) { + throw uhd::value_error( + str(boost::format("[%s] Requesting invalid RX antenna value: %s") + % get_unique_id() % ant)); + } + RFNOC_LOG_TRACE("Setting RX antenna to " << ant << " for chan " << chan); + + radio_control_impl::set_rx_antenna(ant, chan); + _set_atr_bits(chan); +} + +double e3xx_radio_control_impl::set_tx_frequency(const double freq, const size_t chan) +{ + RFNOC_LOG_TRACE("set_tx_frequency(f=" << freq << ", chan=" << chan << ")"); + std::lock_guard<std::mutex> l(_set_lock); + + double clipped_freq = uhd::clip(freq, AD9361_TX_MIN_FREQ, AD9361_TX_MAX_FREQ); + + double coerced_freq = + _ad9361->tune(get_which_ad9361_chain(TX_DIRECTION, chan, _fe_swap), clipped_freq); + radio_control_impl::set_tx_frequency(coerced_freq, chan); + // Front-end switching + _set_atr_bits(chan); + + return coerced_freq; +} + +double e3xx_radio_control_impl::set_rx_frequency(const double freq, const size_t chan) +{ + RFNOC_LOG_TRACE("set_rx_frequency(f=" << freq << ", chan=" << chan << ")"); + std::lock_guard<std::mutex> l(_set_lock); + + double clipped_freq = uhd::clip(freq, AD9361_RX_MIN_FREQ, AD9361_RX_MAX_FREQ); + + double coerced_freq = + _ad9361->tune(get_which_ad9361_chain(RX_DIRECTION, chan, _fe_swap), clipped_freq); + radio_control_impl::set_rx_frequency(coerced_freq, chan); + // Front-end switching + _set_atr_bits(chan); + + return coerced_freq; +} + +void e3xx_radio_control_impl::set_rx_agc(const bool enb, const size_t chan) +{ + std::lock_guard<std::mutex> l(_set_lock); + RFNOC_LOG_TRACE("set_rx_agc(enb=" << enb << ", chan=" << chan << ")"); + const std::string rx_fe = get_which_ad9361_chain(RX_DIRECTION, chan); + _ad9361->set_agc(rx_fe, enb); +} + +double e3xx_radio_control_impl::set_rx_bandwidth(const double bandwidth, const size_t chan) +{ + std::lock_guard<std::mutex> l(_set_lock); + double clipped_bw = + _ad9361->set_bw_filter(get_which_ad9361_chain(RX_DIRECTION, chan, _fe_swap), bandwidth); + return radio_control_impl::set_rx_bandwidth(clipped_bw, chan); +} + +double e3xx_radio_control_impl::set_tx_bandwidth(const double bandwidth, const size_t chan) +{ + std::lock_guard<std::mutex> l(_set_lock); + double clipped_bw = + _ad9361->set_bw_filter(get_which_ad9361_chain(TX_DIRECTION, chan, _fe_swap), bandwidth); + return radio_control_impl::set_tx_bandwidth(clipped_bw, chan); +} + +double e3xx_radio_control_impl::set_tx_gain(const double gain, const size_t chan) +{ + std::lock_guard<std::mutex> l(_set_lock); + RFNOC_LOG_TRACE("set_tx_gain(gain=" << gain << ", chan=" << chan << ")"); + double clip_gain = uhd::clip(gain, AD9361_MIN_TX_GAIN, AD9361_MAX_TX_GAIN); + _ad9361->set_gain(get_which_ad9361_chain(TX_DIRECTION, chan, _fe_swap), clip_gain); + radio_control_impl::set_tx_gain(clip_gain, chan); + return clip_gain; +} + +double e3xx_radio_control_impl::set_rx_gain(const double gain, const size_t chan) +{ + std::lock_guard<std::mutex> l(_set_lock); + UHD_ASSERT_THROW(chan < get_num_output_ports()); + RFNOC_LOG_TRACE("set_rx_gain(gain=" << gain << ", chan=" << chan << ")"); + double clip_gain = uhd::clip(gain, AD9361_MIN_RX_GAIN, AD9361_MAX_RX_GAIN); + _ad9361->set_gain(get_which_ad9361_chain(RX_DIRECTION, chan, _fe_swap), clip_gain); + radio_control_impl::set_rx_gain(clip_gain, chan); + return clip_gain; +} + +std::vector<std::string> e3xx_radio_control_impl::get_tx_antennas(const size_t) const +{ + return {E3XX_DEFAULT_TX_ANTENNA}; +} + +std::vector<std::string> e3xx_radio_control_impl::get_rx_antennas(const size_t) const +{ + return E3XX_RX_ANTENNAS; +} + +freq_range_t e3xx_radio_control_impl::get_tx_frequency_range(const size_t) const +{ + return freq_range_t(AD9361_TX_MIN_FREQ, AD9361_TX_MAX_FREQ, 1.0); +} + +freq_range_t e3xx_radio_control_impl::get_rx_frequency_range(const size_t) const +{ + return freq_range_t(AD9361_RX_MIN_FREQ, AD9361_RX_MAX_FREQ, 1.0); +} + +uhd::gain_range_t e3xx_radio_control_impl::get_tx_gain_range(const size_t) const +{ + return meta_range_t(AD9361_MIN_TX_GAIN, AD9361_MAX_TX_GAIN, AD9361_TX_GAIN_STEP); +} + +uhd::gain_range_t e3xx_radio_control_impl::get_rx_gain_range(const size_t) const +{ + return meta_range_t(AD9361_MIN_RX_GAIN, AD9361_MAX_RX_GAIN, AD9361_RX_GAIN_STEP); +} + +meta_range_t e3xx_radio_control_impl::get_tx_bandwidth_range(size_t) const +{ + return meta_range_t(AD9361_TX_MIN_BANDWIDTH, AD9361_TX_MAX_BANDWIDTH); +} + +meta_range_t e3xx_radio_control_impl::get_rx_bandwidth_range(size_t) const +{ + return meta_range_t(AD9361_RX_MIN_BANDWIDTH, AD9361_RX_MAX_BANDWIDTH); +} + +/************************************************************************** + * Calibration-Related API Calls + *************************************************************************/ +void e3xx_radio_control_impl::set_rx_dc_offset(const bool enb, size_t chan) +{ + std::lock_guard<std::mutex> l(_set_lock); + RFNOC_LOG_TRACE("set_rx_dc_offset(enb=" << enb << ", chan=" << chan << ")"); + const std::string rx_fe = get_which_ad9361_chain(RX_DIRECTION, chan); + _ad9361->set_dc_offset_auto(rx_fe, enb); +} + +void e3xx_radio_control_impl::set_rx_iq_balance(const bool enb, size_t chan) +{ + std::lock_guard<std::mutex> l(_set_lock); + RFNOC_LOG_TRACE("set_rx_iq_balance(enb=" << enb << ", chan=" << chan << ")"); + const std::string rx_fe = get_which_ad9361_chain(RX_DIRECTION, chan); + _ad9361->set_iq_balance_auto(rx_fe, enb); +} + +/************************************************************************** + * GPIO Controls + *************************************************************************/ +void e3xx_radio_control_impl::set_gpio_attr( + const std::string& bank, const std::string& attr, const uint32_t value) +{ + if (bank != get_gpio_banks().front()) { + RFNOC_LOG_ERROR("Invalid GPIO bank: " << bank); + throw uhd::key_error("Invalid GPIO bank!"); + } + if (!gpio_atr::gpio_attr_rev_map.count(attr)) { + RFNOC_LOG_ERROR("Invalid GPIO attr: " << attr); + throw uhd::key_error("Invalid GPIO attr!"); + } + + const gpio_atr::gpio_attr_t gpio_attr = gpio_atr::gpio_attr_rev_map.at(attr); + + if (gpio_attr == gpio_atr::GPIO_READBACK) { + RFNOC_LOG_WARNING("Cannot set READBACK attr."); + return; + } + + _fp_gpio->set_gpio_attr(gpio_attr, value); +} + +uint32_t e3xx_radio_control_impl::get_gpio_attr( + const std::string& bank, const std::string& attr) +{ + if (bank != get_gpio_banks().front()) { + RFNOC_LOG_ERROR("Invalid GPIO bank: " << bank); + throw uhd::key_error("Invalid GPIO bank!"); + } + + const gpio_atr::gpio_attr_t gpio_attr = gpio_atr::gpio_attr_rev_map.at(attr); + return _fp_gpio->get_attr_reg(gpio_attr); +} + +/****************************************************************************** + * Sensor API + *****************************************************************************/ +std::vector<std::string> e3xx_radio_control_impl::get_rx_sensor_names(const size_t) const +{ + return _rx_sensor_names; +} + +uhd::sensor_value_t e3xx_radio_control_impl::get_rx_sensor( + const std::string& sensor_name, const size_t chan) +{ + return sensor_value_t(_rpcc->request_with_token<sensor_value_t::sensor_map_t>( + _rpc_prefix + "get_sensor", "RX", sensor_name, chan)); +} + +std::vector<std::string> e3xx_radio_control_impl::get_tx_sensor_names(const size_t) const +{ + return _tx_sensor_names; +} + +uhd::sensor_value_t e3xx_radio_control_impl::get_tx_sensor( + const std::string& sensor_name, const size_t chan) +{ + return sensor_value_t(_rpcc->request_with_token<sensor_value_t::sensor_map_t>( + _rpc_prefix + "get_sensor", "TX", sensor_name, chan)); +} + +/* loopback_self_test checks the integrity of the FPGA->AD936x->FPGA sample interface. + The AD936x is put in loopback mode that sends the TX data unchanged to the RX side. + A test value is written to the codec_idle register in the TX side of the radio. + The readback register is then used to capture the values on the TX and RX sides + simultaneously for comparison. It is a reasonably effective test for AC timing + since I/Q Ch0/Ch1 alternate over the same wires. Note, however, that it uses + whatever timing is configured at the time the test is called rather than select + worst case conditions to stress the interface. + Note: This currently only tests 2R2T mode +*/ +void e3xx_radio_control_impl::loopback_self_test(const size_t chan) +{ + // Save current rate before running this test + const double current_rate = this->get_rate(); + // Set 2R2T mode, stream on all channels + this->set_streaming_mode(true, true, true, true); + // This was in there in the E320 code, but the comments didn't make sense: + //this->set_streaming_mode(true, true, true, true); + // Set maximum rate for 2R2T mode + /* FIXME + * We're directly setting the master clock rate here because we want to + * avoid property propagation, etc, and we know that we're going to set it + * back once we're done + * this->set_rate(30.72e6); + */ + _ad9361->set_clock_rate(30.72e6); + // Put AD936x in loopback mode + _ad9361->data_port_loopback(true); + RFNOC_LOG_INFO("Performing CODEC loopback test... "); + size_t hash = size_t(time(NULL)); + constexpr size_t loopback_count = 100; + + // Allow some time for AD936x to enter loopback mode. + // There is no clear statement in the documentation of how long it takes, + // but UG-570 does say to "allow six ADC_CLK/64 clock cycles of flush time" + // when leaving the TX or RX states. That works out to ~75us at the + // minimum clock rate of 5 MHz, which lines up with test results. + // Sleeping 1ms is far more than enough. + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + + for (size_t i = 0; i < loopback_count; i++) { + // Create test word + boost::hash_combine(hash, i); + const uint32_t word32 = uint32_t(hash) & 0xfff0fff0; + // Write test word to codec_idle idle register (on TX side) + regs().poke32( + regmap::RADIO_BASE_ADDR + chan * regmap::REG_CHAN_OFFSET + regmap::REG_TX_IDLE_VALUE, word32); + + // Read back values - TX is lower 32-bits and RX is upper 32-bits + const uint32_t rb_tx = + regs().peek32(regmap::RADIO_BASE_ADDR + chan * regmap::REG_CHAN_OFFSET + regmap::REG_TX_IDLE_VALUE); + const uint32_t rb_rx = + regs().peek32(regmap::RADIO_BASE_ADDR + chan * regmap::REG_CHAN_OFFSET + regmap::REG_RX_DATA); + + // Compare TX and RX values to test word + bool test_fail = word32 != rb_tx or word32 != rb_rx; + if (test_fail) { + RFNOC_LOG_WARNING( + "CODEC loopback test failed! " + << boost::format("Expected: 0x%08X Received (TX/RX): 0x%08X/0x%08X") + % word32 % rb_tx % rb_rx); + throw uhd::runtime_error("CODEC loopback test failed."); + } + } + RFNOC_LOG_INFO("CODEC loopback test passed"); + + // Zero out the idle data. + regs().poke32(regmap::RADIO_BASE_ADDR + chan * regmap::REG_CHAN_OFFSET + regmap::REG_TX_IDLE_VALUE, 0); + + // Take AD936x out of loopback mode + _ad9361->data_port_loopback(false); + this->set_streaming_mode(true, false, true, false); + // Switch back to current rate + // FIXME along with the other comment above + // this->set_rate(current_rate); + _ad9361->set_clock_rate(current_rate); +} + +void e3xx_radio_control_impl::_identify_with_leds(const int identify_duration) +{ + RFNOC_LOG_INFO( + "Running LED identification process for " << identify_duration << " seconds."); + auto end_time = + std::chrono::steady_clock::now() + std::chrono::seconds(identify_duration); + bool led_state = true; + while (std::chrono::steady_clock::now() < end_time) { + // Add update_leds + led_state = !led_state; + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } +} + +void e3xx_radio_control_impl::_set_atr_bits(const size_t chan) +{ + const auto rx_freq = radio_control_impl::get_rx_frequency(chan); + const auto tx_freq = radio_control_impl::get_tx_frequency(chan); + const auto rx_ant = radio_control_impl::get_rx_antenna(chan); + const uint32_t rx_regs = this->get_rx_switches(chan, rx_freq, rx_ant); + const uint32_t tx_regs = this->get_tx_switches(chan, tx_freq); + const uint32_t idle_regs = this->get_idle_switches(); + + _db_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_IDLE, idle_regs); + _db_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_RX_ONLY, rx_regs); + _db_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_TX_ONLY, tx_regs); + _db_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_FULL_DUPLEX, rx_regs | tx_regs); + + // The LED signal names are reversed, but are consistent with the schematic + const bool is_txrx = rx_ant == "TX/RX"; + const int idle_led = 0; + const int rx_led = this->get_rx_led(); + const int tx_led = this->get_tx_led(); + const int txrx_led = this->get_txrx_led(); + + _leds_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_IDLE, idle_led); + _leds_gpio[chan]->set_atr_reg( + usrp::gpio_atr::ATR_REG_RX_ONLY, is_txrx ? txrx_led : rx_led); + _leds_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_TX_ONLY, tx_led); + _leds_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_FULL_DUPLEX, rx_led | tx_led); +} + +void e3xx_radio_control_impl::set_db_eeprom(const eeprom_map_t& db_eeprom) +{ + _rpcc->notify_with_token("set_db_eeprom", 0, db_eeprom); +} + +eeprom_map_t e3xx_radio_control_impl::get_db_eeprom() +{ + return _rpcc->request_with_token<eeprom_map_t>("get_db_eeprom", 0); +} + +/************************************************************************** + * Filter API + *************************************************************************/ +std::vector<std::string> e3xx_radio_control_impl::get_rx_filter_names(const size_t) const +{ + return _rx_filter_names; +} + +uhd::filter_info_base::sptr e3xx_radio_control_impl::get_rx_filter( + const std::string& name, const size_t chan) +{ + return _ad9361->get_filter( + get_which_ad9361_chain(RX_DIRECTION, chan, _fe_swap), name); +} + +void e3xx_radio_control_impl::set_rx_filter( + const std::string& name, uhd::filter_info_base::sptr filter, const size_t chan) +{ + _ad9361->set_filter( + get_which_ad9361_chain(RX_DIRECTION, chan, _fe_swap), name, filter); +} + +std::vector<std::string> e3xx_radio_control_impl::get_tx_filter_names(const size_t) const +{ + return _tx_filter_names; +} + +uhd::filter_info_base::sptr e3xx_radio_control_impl::get_tx_filter( + const std::string& name, const size_t chan) +{ + return _ad9361->get_filter( + get_which_ad9361_chain(TX_DIRECTION, chan, _fe_swap), name); +} + +void e3xx_radio_control_impl::set_tx_filter( + const std::string& name, uhd::filter_info_base::sptr filter, const size_t chan) +{ + _ad9361->set_filter( + get_which_ad9361_chain(TX_DIRECTION, chan, _fe_swap), name, filter); +} + + +/************************************************************************** + * Radio Identification API Calls + *************************************************************************/ +size_t e3xx_radio_control_impl::get_chan_from_dboard_fe( + const std::string& fe, const uhd::direction_t) const +{ + // A and B are available here for backward compat + if (fe == "A" || fe == "0") { + return 0; + } + if (fe == "B" || fe == "1") { + return 1; + } + throw uhd::key_error(std::string("[E3xx] Invalid frontend: ") + fe); +} + +std::string e3xx_radio_control_impl::get_dboard_fe_from_chan( + const size_t chan, const uhd::direction_t) const +{ + if (chan == 0) { + return "0"; + } + if (chan == 1) { + return "1"; + } + throw uhd::lookup_error( + std::string("[E3xx] Invalid channel: ") + std::to_string(chan)); +} + +std::string e3xx_radio_control_impl::get_fe_name( + const size_t, const uhd::direction_t) const +{ + return "E3xx"; +} diff --git a/host/lib/usrp/dboard/e3xx/e3xx_radio_control_impl.hpp b/host/lib/usrp/dboard/e3xx/e3xx_radio_control_impl.hpp new file mode 100644 index 000000000..f49cde64a --- /dev/null +++ b/host/lib/usrp/dboard/e3xx/e3xx_radio_control_impl.hpp @@ -0,0 +1,293 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_RFNOC_E3XX_RADIO_CTRL_IMPL_HPP +#define INCLUDED_LIBUHD_RFNOC_E3XX_RADIO_CTRL_IMPL_HPP + +#include "e3xx_ad9361_iface.hpp" +#include <uhd/types/eeprom.hpp> +#include <uhd/types/serial.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/usrp/gpio_defs.hpp> +#include <uhd/rfnoc/filter_node.hpp> +#include <uhdlib/rfnoc/radio_control_impl.hpp> +#include <uhdlib/usrp/common/mpmd_mb_controller.hpp> +#include <uhdlib/usrp/cores/gpio_atr_3000.hpp> +#include <mutex> + +namespace uhd { namespace rfnoc { + +namespace e3xx_regs { + constexpr uint32_t PERIPH_BASE = 0x80000; + constexpr uint32_t PERIPH_REG_OFFSET = 8; + constexpr uint32_t PERIPH_REG_CHAN_OFFSET = 0x800; + constexpr uint32_t SR_LEDS = PERIPH_BASE + 176 * PERIPH_REG_OFFSET; + constexpr uint32_t SR_FP_GPIO = PERIPH_BASE + 184 * PERIPH_REG_OFFSET; + constexpr uint32_t SR_DB_GPIO = PERIPH_BASE + 192 * PERIPH_REG_OFFSET; + + constexpr uint32_t RB_DB_GPIO = PERIPH_BASE + 19 * PERIPH_REG_OFFSET; + constexpr uint32_t RB_FP_GPIO = PERIPH_BASE + 20 * PERIPH_REG_OFFSET; + + +} + +/*! \brief Provide access to an E3xx radio. + */ +class e3xx_radio_control_impl : public radio_control_impl, public uhd::rfnoc::detail::filter_node +{ +public: + //! Frequency bands for RX. Bands are a function of the analog filter banks + enum class rx_band { INVALID_BAND, LB_B2, LB_B3, LB_B4, LB_B5, LB_B6, LB_B7, HB }; + + //! Frequency bands for TX. Bands are a function of the analog filter banks + enum class tx_band { + INVALID_BAND, + LB_80, + LB_160, + LB_225, + LB_400, + LB_575, + LB_1000, + LB_1700, + LB_2750, + HB + }; + + /************************************************************************** + * ATR/ Switches Types + *************************************************************************/ + //! ATR state + enum atr_state_t { IDLE, RX_ONLY, TX_ONLY, FULL_DUPLEX }; + + //! Channel select: + enum chan_sel_t { CHAN1, CHAN2, BOTH }; + + /************************************************************************ + * Structors + ***********************************************************************/ + e3xx_radio_control_impl(make_args_ptr make_args); + virtual ~e3xx_radio_control_impl(); + + /************************************************************************ + * node_t && noc_block_base API calls + ***********************************************************************/ + void deinit(); + + bool check_topology(const std::vector<size_t>& connected_inputs, + const std::vector<size_t>& connected_outputs); + + /************************************************************************ + * radio_control API calls + ***********************************************************************/ + double set_rate(const double rate); + uhd::meta_range_t get_rate_range() const; + + // Setters + void set_tx_antenna(const std::string &ant, const size_t chan); + void set_rx_antenna(const std::string &ant, const size_t chan); + double set_tx_frequency(const double freq, const size_t chan); + double set_rx_frequency(const double freq, const size_t chan); + double set_tx_gain(const double gain, const size_t chan); + double set_rx_gain(const double gain, const size_t chan); + void set_rx_agc(const bool enable, const size_t chan); + double set_tx_bandwidth(const double bandwidth, const size_t chan); + double set_rx_bandwidth(const double bandwidth, const size_t chan); + + // Getters + std::vector<std::string> get_tx_antennas(const size_t chan) const; + std::vector<std::string> get_rx_antennas(const size_t chan) const; + uhd::freq_range_t get_tx_frequency_range(const size_t chan) const; + uhd::freq_range_t get_rx_frequency_range(const size_t chan) const; + uhd::gain_range_t get_tx_gain_range(const size_t) const; + uhd::gain_range_t get_rx_gain_range(const size_t) const; + meta_range_t get_tx_bandwidth_range(size_t chan) const; + meta_range_t get_rx_bandwidth_range(size_t chan) const; + + /************************************************************************** + * Calibration-Related API Calls + *************************************************************************/ + virtual void set_rx_dc_offset(const bool enb, size_t chan = ALL_CHANS); + virtual void set_rx_iq_balance(const bool enb, size_t chan); + + /************************************************************************** + * GPIO Controls + *************************************************************************/ + virtual void set_gpio_attr( + const std::string& bank, const std::string& attr, const uint32_t value); + virtual uint32_t get_gpio_attr(const std::string& bank, const std::string& attr); + + /************************************************************************** + * Sensor API + *************************************************************************/ + std::vector<std::string> get_rx_sensor_names(size_t chan) const; + uhd::sensor_value_t get_rx_sensor(const std::string& name, size_t chan); + std::vector<std::string> get_tx_sensor_names(size_t chan) const; + uhd::sensor_value_t get_tx_sensor(const std::string& name, size_t chan); + + /************************************************************************** + * Filter API + *************************************************************************/ + std::vector<std::string> get_rx_filter_names(const size_t chan) const; + uhd::filter_info_base::sptr get_rx_filter(const std::string& name, const size_t chan); + void set_rx_filter( + const std::string& name, uhd::filter_info_base::sptr filter, const size_t chan); + + std::vector<std::string> get_tx_filter_names(const size_t chan) const; + uhd::filter_info_base::sptr get_tx_filter(const std::string& name, const size_t chan); + void set_tx_filter( + const std::string& name, uhd::filter_info_base::sptr filter, const size_t chan); + + /************************************************************************** + * Radio Identification API Calls + *************************************************************************/ + std::string get_slot_name() const { return "A"; } + virtual size_t get_chan_from_dboard_fe( + const std::string& fe, const uhd::direction_t direction) const; + virtual std::string get_dboard_fe_from_chan( + const size_t chan, const uhd::direction_t direction) const; + virtual std::string get_fe_name( + const size_t chan, const uhd::direction_t direction) const; + +protected: + //! Map a frequency in Hz to an rx_band value. Will return + // rx_band::INVALID_BAND if the frequency is out of range. + rx_band map_freq_to_rx_band(const double freq); + //! Map a frequency in Hz to an tx_band value. Will return + // tx_band::INVALID_BAND if the frequency is out of range. + tx_band map_freq_to_tx_band(const double freq); + + virtual const std::string get_default_timing_mode() = 0; + + /*! Run a loopback self test. + * + * This will write data to the AD936x and read it back again. + * If this test fails, it generally means the interface is broken, + * so we assume it passes and throw otherwise. Running this requires + * a core that we can peek and poke the loopback values into. + * + * \param iface An interface to the associated radio control core + * \param iface The radio control core's address to write the loopback value + * \param iface The radio control core's readback address to read back the returned + * value + * + * \throws a uhd::runtime_error if the loopback value didn't match. + */ + void loopback_self_test(const size_t chan); + + virtual uint32_t get_rx_switches( + const size_t chan, const double freq, const std::string& ant) = 0; + + virtual uint32_t get_tx_switches(const size_t chan, const double freq) = 0; + + virtual uint32_t get_idle_switches() = 0; + + virtual uint32_t get_tx_led() = 0; + virtual uint32_t get_rx_led() = 0; + virtual uint32_t get_txrx_led() = 0; + virtual uint32_t get_idle_led() = 0; + + //! Reference to the AD9361 controls + // e3xx_ad9361_iface::uptr _ad9361; + ad9361_ctrl::sptr _ad9361; + + //! Swap RFA and RFB for catalina + bool _fe_swap; + + //! Init RPC-related items + void _init_mpm(); + +private: + /************************************************************************** + * Helpers + *************************************************************************/ + //! Initialize all the peripherals connected to this block + void _init_peripherals(); + + //! Set state of this class to sensible defaults + void _init_defaults(); + + //! Init a subtree for the RF frontends + void _init_frontend_subtree(uhd::property_tree::sptr subtree, const size_t chan_idx); + + //! Initialize Catalina defaults + void _init_codec(); + + //! Initialize property tree + void _init_prop_tree(); + + //! Set streaming mode - active chains, channel_mode, timing_mode + void set_streaming_mode( + const bool tx1, const bool tx2, const bool rx1, const bool rx2); + + //! Set which channel mode is used + void set_channel_mode(const std::string& channel_mode); + + /************************************************************************** + * Misc Controls + *************************************************************************/ + //! Blink the front-panel LEDs for \p identify_duration, + // and resume normal operation. + void _identify_with_leds(const int identify_duration); + + void _set_atr_bits(const size_t chan); + + void set_db_eeprom(const uhd::eeprom_map_t& db_eeprom); + + uhd::eeprom_map_t get_db_eeprom(); + + /************************************************************************** + * Private attributes + *************************************************************************/ + //! Locks access to setter APIs + mutable std::mutex _set_lock; + + //! Prepended for all dboard RPC calls + std::string _rpc_prefix = "db_0_"; + + //! Reference to the MB controller + uhd::rfnoc::mpmd_mb_controller::sptr _e3xx_mb_control; + + //! Reference to the MB timekeeper + uhd::rfnoc::mpmd_mb_controller::mpmd_timekeeper::sptr _e3xx_timekeeper; + + //! Reference to wb_iface adapters + std::vector<uhd::timed_wb_iface::sptr> _wb_ifaces; + + //! Reference to the RPC client + uhd::rpc_client::sptr _rpcc; + + //! ATR controls. These control the AD9361 gain up/down bits. + // Every radio channel gets its own ATR state register. + std::vector<usrp::gpio_atr::gpio_atr_3000::sptr> _db_gpio; + + // ATR controls for LEDs + // Every radio channel gets its own ATR state register. + std::vector<usrp::gpio_atr::gpio_atr_3000::sptr> _leds_gpio; + + //! Front panel GPIO controller. Note that only one radio block per + // module can be the FP-GPIO master. + usrp::gpio_atr::gpio_atr_3000::sptr _fp_gpio; + + //! Sampling rate + double _master_clock_rate = 1.0; + + //! RX sensor names (they get cached) + std::vector<std::string> _rx_sensor_names; + + //! TX sensor names (they get cached) + std::vector<std::string> _tx_sensor_names; + + //! RX filter names (they get cached) + std::vector<std::string> _rx_filter_names; + + //! TX filter names (they get cached) + std::vector<std::string> _tx_filter_names; +}; + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_E3XX_RADIO_CTRL_IMPL_HPP */ diff --git a/host/lib/usrp/dboard/e3xx/e3xx_radio_control_init.cpp b/host/lib/usrp/dboard/e3xx/e3xx_radio_control_init.cpp new file mode 100644 index 000000000..f97feeb68 --- /dev/null +++ b/host/lib/usrp/dboard/e3xx/e3xx_radio_control_init.cpp @@ -0,0 +1,305 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "e3xx_constants.hpp" +#include "e3xx_radio_control_impl.hpp" +#include <uhd/transport/chdr.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/utils/log.hpp> +#include <uhdlib/rfnoc/reg_iface_adapter.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/algorithm/string/case_conv.hpp> +#include <boost/algorithm/string/split.hpp> +#include <string> +#include <vector> + +using namespace uhd; +using namespace uhd::rfnoc; + +void e3xx_radio_control_impl::_init_defaults() +{ + RFNOC_LOG_TRACE("Initializing defaults..."); + const size_t num_rx_chans = get_num_output_ports(); + const size_t num_tx_chans = get_num_input_ports(); + + RFNOC_LOG_TRACE( + "Num TX chans: " << num_tx_chans << " Num RX chans: " << num_rx_chans); + + + // Note: MCR gets set during the init() call (prior to this), which takes + // in arguments from the device args. So if block_args contains a + // master_clock_rate key, then it should better be whatever the device is + // configured to do. + auto block_args = get_block_args(); + _master_clock_rate = + _rpcc->request_with_token<double>(_rpc_prefix + "get_master_clock_rate"); + const double block_args_mcr = + block_args.cast<double>("master_clock_rate", _master_clock_rate); + if (block_args_mcr != _master_clock_rate) { + throw uhd::runtime_error( + str(boost::format("Master clock rate mismatch. Device returns %f MHz, " + "but should have been %f MHz.") + % (_master_clock_rate / 1e6) % (block_args_mcr / 1e6))); + } + RFNOC_LOG_DEBUG("Master Clock Rate is: " << (_master_clock_rate / 1e6) << " MHz."); + set_tick_rate(_master_clock_rate); + _e3xx_timekeeper->update_tick_rate(_master_clock_rate); + radio_control_impl::set_rate(_master_clock_rate); + for (size_t chan = 0; chan < num_rx_chans; chan++) { + radio_control_impl::set_rx_frequency(E3XX_DEFAULT_FREQ, chan); + radio_control_impl::set_rx_gain(E3XX_DEFAULT_GAIN, chan); + radio_control_impl::set_rx_antenna(E3XX_DEFAULT_RX_ANTENNA, chan); + radio_control_impl::set_rx_bandwidth(E3XX_DEFAULT_BANDWIDTH, chan); + } + + for (size_t chan = 0; chan < num_tx_chans; chan++) { + radio_control_impl::set_tx_frequency(E3XX_DEFAULT_FREQ, chan); + radio_control_impl::set_tx_gain(E3XX_DEFAULT_GAIN, chan); + radio_control_impl::set_tx_antenna(E3XX_DEFAULT_TX_ANTENNA, chan); + radio_control_impl::set_tx_bandwidth(E3XX_DEFAULT_BANDWIDTH, chan); + } + + _rx_sensor_names = _rpcc->request_with_token<std::vector<std::string>>( + this->_rpc_prefix + "get_sensors", "RX"); + _tx_sensor_names = _rpcc->request_with_token<std::vector<std::string>>( + this->_rpc_prefix + "get_sensors", "TX"); + + // Cache the filter names + // FIXME: Uncomment this + //_rx_filter_names = _ad9361->get_filter_names( + // get_which_ad9361_chain(RX_DIRECTION, 0, _fe_swap)); + //_tx_filter_names = _ad9361->get_filter_names( + // get_which_ad9361_chain(TX_DIRECTION, 0, _fe_swap)); +} + +void e3xx_radio_control_impl::_init_peripherals() +{ + RFNOC_LOG_TRACE("Initializing peripherals..."); + for (size_t radio_idx = 0; radio_idx < E3XX_NUM_CHANS; radio_idx++) { + _wb_ifaces.push_back(RFNOC_MAKE_WB_IFACE(0, radio_idx)); + } + _db_gpio.clear(); // Following the as-if rule, this can get optimized out + for (size_t radio_idx = 0; radio_idx < E3XX_NUM_CHANS; radio_idx++) { + RFNOC_LOG_TRACE("Initializing DB GPIOs for channel " << radio_idx); + // Note: The register offset is baked into the different _wb_iface + // objects! + _db_gpio.emplace_back( + usrp::gpio_atr::gpio_atr_3000::make_write_only(_wb_ifaces.at(radio_idx), + e3xx_regs::SR_DB_GPIO + (radio_idx * e3xx_regs::PERIPH_REG_CHAN_OFFSET), + e3xx_regs::PERIPH_REG_OFFSET)); + _db_gpio[radio_idx]->set_atr_mode( + usrp::gpio_atr::MODE_ATR, usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL); + } + _leds_gpio.clear(); // Following the as-if rule, this can get optimized out + for (size_t radio_idx = 0; radio_idx < E3XX_NUM_CHANS; radio_idx++) { + RFNOC_LOG_TRACE("Initializing LED GPIOs for channel " << radio_idx); + _leds_gpio.emplace_back( + usrp::gpio_atr::gpio_atr_3000::make_write_only(_wb_ifaces.at(radio_idx), + e3xx_regs::SR_LEDS + (radio_idx * e3xx_regs::PERIPH_REG_CHAN_OFFSET), + e3xx_regs::PERIPH_REG_OFFSET)); + _leds_gpio[radio_idx]->set_atr_mode( + usrp::gpio_atr::MODE_ATR, usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL); + } + RFNOC_LOG_TRACE("Initializing front-panel GPIO control...") + _fp_gpio = usrp::gpio_atr::gpio_atr_3000::make( + _wb_ifaces.at(0), e3xx_regs::SR_FP_GPIO, e3xx_regs::RB_FP_GPIO, e3xx_regs::PERIPH_REG_OFFSET); + + + auto block_args = get_block_args(); + if (block_args.has_key("identify")) { + const std::string identify_val = block_args.get("identify"); + int identify_duration = std::atoi(identify_val.c_str()); + if (identify_duration == 0) { + identify_duration = 5; + } + _identify_with_leds(identify_duration); + } +} + +void e3xx_radio_control_impl::_init_frontend_subtree( + uhd::property_tree::sptr subtree, const size_t chan_idx) +{ + const fs_path tx_fe_path = fs_path("tx_frontends") / chan_idx; + const fs_path rx_fe_path = fs_path("rx_frontends") / chan_idx; + RFNOC_LOG_TRACE( + "Adding non-RFNoC block properties for channel " + << chan_idx << " to prop tree path " << tx_fe_path << " and " << rx_fe_path); + // TX Standard attributes + subtree->create<std::string>(tx_fe_path / "name").set("E3xx"); + subtree->create<std::string>(tx_fe_path / "connection").set("IQ"); + // RX Standard attributes + subtree->create<std::string>(rx_fe_path / "name").set("E3xx"); + subtree->create<std::string>(rx_fe_path / "connection").set("IQ"); + // TX Antenna + subtree->create<std::string>(tx_fe_path / "antenna" / "value") + .add_coerced_subscriber([this, chan_idx](const std::string& ant) { + this->set_tx_antenna(ant, chan_idx); + }) + .set_publisher([this, chan_idx]() { return this->get_tx_antenna(chan_idx); }); + subtree->create<std::vector<std::string>>(tx_fe_path / "antenna" / "options") + .set({E3XX_DEFAULT_TX_ANTENNA}) + .add_coerced_subscriber([](const std::vector<std::string>&) { + throw uhd::runtime_error("Attempting to update antenna options!"); + }); + // RX Antenna + subtree->create<std::string>(rx_fe_path / "antenna" / "value") + .add_coerced_subscriber([this, chan_idx](const std::string& ant) { + this->set_rx_antenna(ant, chan_idx); + }) + .set_publisher([this, chan_idx]() { return this->get_rx_antenna(chan_idx); }); + subtree->create<std::vector<std::string>>(rx_fe_path / "antenna" / "options") + .set(E3XX_RX_ANTENNAS) + .add_coerced_subscriber([](const std::vector<std::string>&) { + throw uhd::runtime_error("Attempting to update antenna options!"); + }); + // TX frequency + subtree->create<double>(tx_fe_path / "freq" / "value") + .set_coercer([this, chan_idx](const double freq) { + return this->set_tx_frequency(freq, chan_idx); + }) + .set_publisher([this, chan_idx]() { return this->get_tx_frequency(chan_idx); }); + subtree->create<meta_range_t>(tx_fe_path / "freq" / "range") + .set_publisher([this]() { return get_tx_frequency_range(0); }) + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update freq range!"); + }); + // RX frequency + subtree->create<double>(rx_fe_path / "freq" / "value") + .set_coercer([this, chan_idx](const double freq) { + return this->set_rx_frequency(freq, chan_idx); + }) + .set_publisher([this, chan_idx]() { return this->get_rx_frequency(chan_idx); }); + subtree->create<meta_range_t>(rx_fe_path / "freq" / "range") + .set_publisher([this]() { return get_rx_frequency_range(0); }) + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update freq range!"); + }); + // TX bandwidth + subtree->create<double>(tx_fe_path / "bandwidth" / "value") + .set_publisher([this, chan_idx]() { return get_tx_bandwidth(chan_idx); }) + .set_coercer([this, chan_idx](const double bw) { + return this->set_tx_bandwidth(bw, chan_idx); + }) + .set_publisher([this, chan_idx]() { return this->get_tx_bandwidth(chan_idx); }); + subtree->create<meta_range_t>(tx_fe_path / "bandwidth" / "range") + .set_publisher([this]() { return get_tx_bandwidth_range(0); }) + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update bandwidth range!"); + }); + // RX bandwidth + subtree->create<double>(rx_fe_path / "bandwidth" / "value") + .set_publisher([this, chan_idx]() { return get_rx_bandwidth(chan_idx); }) + .set_coercer([this, chan_idx](const double bw) { + return this->set_rx_bandwidth(bw, chan_idx); + }); + subtree->create<meta_range_t>(rx_fe_path / "bandwidth" / "range") + .set_publisher([this]() { return get_rx_bandwidth_range(0); }) + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update bandwidth range!"); + }); + + // TX gains + const std::vector<std::string> tx_gain_names = ad9361_ctrl::get_gain_names("TX1"); + for (auto tx_gain_name : tx_gain_names) { + subtree->create<double>(tx_fe_path / "gains" / tx_gain_name / "value") + .set_coercer([this, chan_idx](const double gain) { + return this->set_tx_gain(gain, chan_idx); + }) + .set_publisher( + [this, chan_idx]() { return radio_control_impl::get_tx_gain(chan_idx); }); + subtree->create<meta_range_t>(tx_fe_path / "gains" / tx_gain_name / "range") + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update gain range!"); + }) + .set_publisher([this]() { + return meta_range_t( + AD9361_MIN_TX_GAIN, AD9361_MAX_TX_GAIN, AD9361_TX_GAIN_STEP); + }); + } + + // RX gains + const std::vector<std::string> rx_gain_names = ad9361_ctrl::get_gain_names("RX1"); + for (auto rx_gain_name : rx_gain_names) { + subtree->create<double>(rx_fe_path / "gains" / rx_gain_name / "value") + .set_coercer([this, chan_idx](const double gain) { + return this->set_rx_gain(gain, chan_idx); + }) + .set_publisher( + [this, chan_idx]() { return radio_control_impl::get_rx_gain(chan_idx); }); + + subtree->create<meta_range_t>(rx_fe_path / "gains" / rx_gain_name / "range") + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update gain range!"); + }) + .set_publisher([this]() { + return meta_range_t( + AD9361_MIN_RX_GAIN, AD9361_MAX_RX_GAIN, AD9361_RX_GAIN_STEP); + }); + } + + auto rx_sensor_names = get_rx_sensor_names(chan_idx); + for (const auto& rx_sensor_name : rx_sensor_names) { + RFNOC_LOG_TRACE("Adding RX sensor " << rx_sensor_name); + get_tree()->create<sensor_value_t>(rx_fe_path / "sensors" / rx_sensor_name) + .add_coerced_subscriber([](const sensor_value_t&) { + throw uhd::runtime_error("Attempting to write to sensor!"); + }) + .set_publisher([this, rx_sensor_name, chan_idx]() { + return get_rx_sensor(rx_sensor_name, chan_idx); + }); + } + auto tx_sensor_names = get_tx_sensor_names(chan_idx); + for (const auto& tx_sensor_name : tx_sensor_names) { + RFNOC_LOG_TRACE("Adding TX sensor " << tx_sensor_name); + get_tree()->create<sensor_value_t>(tx_fe_path / "sensors" / tx_sensor_name) + .add_coerced_subscriber([](const sensor_value_t&) { + throw uhd::runtime_error("Attempting to write to sensor!"); + }) + .set_publisher([this, tx_sensor_name, chan_idx]() { + return get_tx_sensor(tx_sensor_name, chan_idx); + }); + } +} + +void e3xx_radio_control_impl::_init_prop_tree() +{ + for (size_t chan_idx = 0; chan_idx < E3XX_NUM_CHANS; chan_idx++) { + this->_init_frontend_subtree(get_tree()->subtree(DB_PATH), chan_idx); + } + get_tree()->create<std::string>("rx_codec/name").set("AD9361 Dual ADC"); + get_tree()->create<std::string>("tx_codec/name").set("AD9361 Dual DAC"); +} + +void e3xx_radio_control_impl::_init_mpm() +{ + // Initialize catalina + _init_codec(); + + // Loopback test + for (size_t chan = 0; chan < E3XX_NUM_CHANS; chan++) { + loopback_self_test(chan); + } +} + +void e3xx_radio_control_impl::_init_codec() +{ + RFNOC_LOG_TRACE("Setting Catalina Defaults... "); + for (size_t chan = 0; chan < E3XX_NUM_CHANS; chan++) { + std::string rx_fe = get_which_ad9361_chain(RX_DIRECTION, chan); + this->set_rx_gain(E3XX_DEFAULT_GAIN, chan); + this->set_rx_frequency(E3XX_DEFAULT_FREQ, chan); + this->set_rx_antenna(E3XX_DEFAULT_RX_ANTENNA, chan); + this->set_rx_bandwidth(E3XX_DEFAULT_BANDWIDTH, chan); + _ad9361->set_dc_offset_auto(rx_fe, E3XX_DEFAULT_AUTO_DC_OFFSET); + _ad9361->set_iq_balance_auto(rx_fe, E3XX_DEFAULT_AUTO_IQ_BALANCE); + _ad9361->set_agc(rx_fe, E3XX_DEFAULT_AGC_ENABLE); + std::string tx_fe = get_which_ad9361_chain(TX_DIRECTION, chan); + this->set_tx_gain(E3XX_DEFAULT_GAIN, chan); + this->set_tx_frequency(E3XX_DEFAULT_FREQ, chan); + this->set_tx_bandwidth(E3XX_DEFAULT_BANDWIDTH, chan); + } +} + diff --git a/host/lib/usrp/dboard/e3xx/e3xx_radio_ctrl_impl.cpp b/host/lib/usrp/dboard/e3xx/e3xx_radio_ctrl_impl.cpp deleted file mode 100644 index 989b73b82..000000000 --- a/host/lib/usrp/dboard/e3xx/e3xx_radio_ctrl_impl.cpp +++ /dev/null @@ -1,343 +0,0 @@ -// -// Copyright 2018 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "e3xx_radio_ctrl_impl.hpp" -#include "e3xx_constants.hpp" -#include <uhd/rfnoc/node_ctrl_base.hpp> -#include <uhd/transport/chdr.hpp> -#include <uhd/types/direction.hpp> -#include <uhd/types/eeprom.hpp> -#include <uhd/utils/algorithm.hpp> -#include <uhd/utils/log.hpp> -#include <uhd/utils/math.hpp> -#include <boost/algorithm/string.hpp> -#include <boost/format.hpp> -#include <boost/make_shared.hpp> -#include <cmath> -#include <cstdlib> -#include <sstream> - -using namespace uhd; -using namespace uhd::usrp; -using namespace uhd::rfnoc; -using namespace uhd::math::fp_compare; - -/****************************************************************************** - * Structors - *****************************************************************************/ -e3xx_radio_ctrl_impl::e3xx_radio_ctrl_impl() -{ - UHD_LOG_TRACE(unique_id(), "Entering e3xx_radio_ctrl_impl ctor..."); - const char radio_slot_name[1] = {'A'}; - _radio_slot = radio_slot_name[get_block_id().get_block_count()]; - UHD_LOG_TRACE(unique_id(), "Radio slot: " << _radio_slot); - _rpc_prefix = "db_0_"; - - _init_defaults(); - _init_peripherals(); - _init_prop_tree(); -} - -e3xx_radio_ctrl_impl::~e3xx_radio_ctrl_impl() -{ - UHD_LOG_TRACE(unique_id(), "e3xx_radio_ctrl_impl::dtor() "); -} - - -/****************************************************************************** - * API Calls - *****************************************************************************/ - -void e3xx_radio_ctrl_impl::set_streaming_mode( - const bool tx1, const bool tx2, const bool rx1, const bool rx2) -{ - UHD_LOG_TRACE(unique_id(), "Setting up streaming ...") - const size_t num_rx = rx1 + rx2; - const size_t num_tx = tx1 + tx2; - - // setup the active chains in the codec - if ((num_rx + num_tx) == 0) { - // Ensure at least one RX chain is enabled so AD9361 outputs a sample clock - _ad9361->set_active_chains(true, false, true, false); - } else { - // setup the active chains in the codec - _ad9361->set_active_chains(tx1, tx2, rx1, rx2); - } - - // setup 1R1T/2R2T mode in catalina and fpga - // The Catalina interface in the fpga needs to know which TX channel to use for - // the data on the LVDS lines. - if ((num_rx == 2) or (num_tx == 2)) { - // AD9361 is in 1R1T mode - _ad9361->set_timing_mode(this->get_default_timing_mode()); - this->set_channel_mode(MIMO); - } else { - // AD9361 is in 1R1T mode - _ad9361->set_timing_mode(TIMING_MODE_1R1T); - - // Set to SIS0_TX1 if we're using the second TX antenna, otherwise - // default to SISO_TX0 - this->set_channel_mode(tx2 ? SISO_TX1 : SISO_TX0); - } -} - -void e3xx_radio_ctrl_impl::set_channel_mode(const std::string& channel_mode) -{ - // MIMO for 2R2T mode for 2 channels - // SISO_TX1 for 1R1T mode for 1 channel - TX1 - // SISO_TX0 for 1R1T mode for 1 channel - TX0 - - _rpcc->request_with_token<void>("set_channel_mode", channel_mode); -} - -double e3xx_radio_ctrl_impl::set_rate(const double rate) -{ - std::lock_guard<std::mutex> l(_set_lock); - UHD_LOG_DEBUG(unique_id(), "Asking for clock rate " << rate / 1e6 << " MHz\n"); - double actual_tick_rate = _ad9361->set_clock_rate(rate); - UHD_LOG_DEBUG( - unique_id(), "Actual clock rate " << actual_tick_rate / 1e6 << " MHz\n"); - - radio_ctrl_impl::set_rate(rate); - return rate; -} - -void e3xx_radio_ctrl_impl::set_tx_antenna(const std::string& ant, const size_t chan) -{ - if (ant != get_tx_antenna(chan)) { - throw uhd::value_error( - str(boost::format("[%s] Requesting invalid TX antenna value: %s") - % unique_id() % ant)); - } - radio_ctrl_impl::set_tx_antenna(ant, chan); - // We can't actually set the TX antenna, so let's stop here. -} - -void e3xx_radio_ctrl_impl::set_rx_antenna(const std::string& ant, const size_t chan) -{ - UHD_ASSERT_THROW(chan <= E3XX_NUM_CHANS); - if (std::find(E3XX_RX_ANTENNAS.begin(), E3XX_RX_ANTENNAS.end(), ant) - == E3XX_RX_ANTENNAS.end()) { - throw uhd::value_error( - str(boost::format("[%s] Requesting invalid RX antenna value: %s") - % unique_id() % ant)); - } - UHD_LOG_TRACE(unique_id(), "Setting RX antenna to " << ant << " for chan " << chan); - - radio_ctrl_impl::set_rx_antenna(ant, chan); - _set_atr_bits(chan); -} - -double e3xx_radio_ctrl_impl::set_tx_frequency(const double freq, const size_t chan) -{ - UHD_LOG_TRACE(unique_id(), "set_tx_frequency(f=" << freq << ", chan=" << chan << ")"); - std::lock_guard<std::mutex> l(_set_lock); - - double clipped_freq = uhd::clip(freq, AD9361_TX_MIN_FREQ, AD9361_TX_MAX_FREQ); - - double coerced_freq = - _ad9361->tune(get_which_ad9361_chain(TX_DIRECTION, chan, _fe_swap), clipped_freq); - radio_ctrl_impl::set_tx_frequency(coerced_freq, chan); - // Front-end switching - _set_atr_bits(chan); - - return coerced_freq; -} - -double e3xx_radio_ctrl_impl::set_rx_frequency(const double freq, const size_t chan) -{ - UHD_LOG_TRACE(unique_id(), "set_rx_frequency(f=" << freq << ", chan=" << chan << ")"); - std::lock_guard<std::mutex> l(_set_lock); - - double clipped_freq = uhd::clip(freq, AD9361_RX_MIN_FREQ, AD9361_RX_MAX_FREQ); - - double coerced_freq = - _ad9361->tune(get_which_ad9361_chain(RX_DIRECTION, chan, _fe_swap), clipped_freq); - radio_ctrl_impl::set_rx_frequency(coerced_freq, chan); - // Front-end switching - _set_atr_bits(chan); - - return coerced_freq; -} - -double e3xx_radio_ctrl_impl::set_rx_bandwidth(const double bandwidth, const size_t chan) -{ - std::lock_guard<std::mutex> l(_set_lock); - double clipped_bw = - _ad9361->set_bw_filter(get_which_ad9361_chain(RX_DIRECTION, chan, _fe_swap), bandwidth); - return radio_ctrl_impl::set_rx_bandwidth(clipped_bw, chan); -} - -double e3xx_radio_ctrl_impl::set_tx_bandwidth(const double bandwidth, const size_t chan) -{ - std::lock_guard<std::mutex> l(_set_lock); - double clipped_bw = - _ad9361->set_bw_filter(get_which_ad9361_chain(TX_DIRECTION, chan, _fe_swap), bandwidth); - return radio_ctrl_impl::set_tx_bandwidth(clipped_bw, chan); -} - -double e3xx_radio_ctrl_impl::set_tx_gain(const double gain, const size_t chan) -{ - std::lock_guard<std::mutex> l(_set_lock); - UHD_LOG_TRACE(unique_id(), "set_tx_gain(gain=" << gain << ", chan=" << chan << ")"); - double clip_gain = uhd::clip(gain, AD9361_MIN_TX_GAIN, AD9361_MAX_TX_GAIN); - _ad9361->set_gain(get_which_ad9361_chain(TX_DIRECTION, chan, _fe_swap), clip_gain); - radio_ctrl_impl::set_tx_gain(clip_gain, chan); - return clip_gain; -} - -double e3xx_radio_ctrl_impl::set_rx_gain(const double gain, const size_t chan) -{ - std::lock_guard<std::mutex> l(_set_lock); - UHD_LOG_TRACE(unique_id(), "set_rx_gain(gain=" << gain << ", chan=" << chan << ")"); - double clip_gain = uhd::clip(gain, AD9361_MIN_RX_GAIN, AD9361_MAX_RX_GAIN); - _ad9361->set_gain(get_which_ad9361_chain(RX_DIRECTION, chan, _fe_swap), clip_gain); - radio_ctrl_impl::set_rx_gain(clip_gain, chan); - return clip_gain; -} - -size_t e3xx_radio_ctrl_impl::get_chan_from_dboard_fe( - const std::string& fe, const direction_t /* dir */ -) -{ - const size_t chan = boost::lexical_cast<size_t>(fe); - if (chan > _get_num_radios() - 1) { - UHD_LOG_WARNING(unique_id(), - boost::format("Invalid channel determined from dboard frontend %s.") % fe); - } - return chan; -} - -std::string e3xx_radio_ctrl_impl::get_dboard_fe_from_chan( - const size_t chan, const direction_t /* dir */ -) -{ - return std::to_string(chan); -} - -void e3xx_radio_ctrl_impl::set_rpc_client( - uhd::rpc_client::sptr rpcc, const uhd::device_addr_t& block_args) -{ - _rpcc = rpcc; - _block_args = block_args; - UHD_LOG_TRACE(unique_id(), "Instantiating AD9361 control object..."); - _ad9361 = make_rpc(_rpcc); - - UHD_LOG_TRACE(unique_id(), "Setting Catalina Defaults... "); - // Initialize catalina - this->_init_codec(); - - if (block_args.has_key("identify")) { - const std::string identify_val = block_args.get("identify"); - int identify_duration = std::atoi(identify_val.c_str()); - if (identify_duration == 0) { - identify_duration = 5; - } - UHD_LOG_INFO(unique_id(), - "Running LED identification process for " << identify_duration - << " seconds."); - _identify_with_leds(identify_duration); - } - // Note: MCR gets set during the init() call (prior to this), which takes - // in arguments from the device args. So if block_args contains a - // master_clock_rate key, then it should better be whatever the device is - // configured to do. - _master_clock_rate = - _rpcc->request_with_token<double>(_rpc_prefix + "get_master_clock_rate"); - if (block_args.cast<double>("master_clock_rate", _master_clock_rate) - != _master_clock_rate) { - throw uhd::runtime_error(str( - boost::format("Master clock rate mismatch. Device returns %f MHz, " - "but should have been %f MHz.") - % (_master_clock_rate / 1e6) - % (block_args.cast<double>("master_clock_rate", _master_clock_rate) / 1e6))); - } - UHD_LOG_DEBUG( - unique_id(), "Master Clock Rate is: " << (_master_clock_rate / 1e6) << " MHz."); - this->set_rate(_master_clock_rate); - - // Loopback test - for (size_t chan = 0; chan < _get_num_radios(); chan++) { - loopback_self_test( - [this, chan]( - const uint32_t value) { this->sr_write(regs::CODEC_IDLE, value, chan); }, - [this, chan]() { - return this->user_reg_read64(regs::RB_CODEC_READBACK, chan); - }); - } - - const size_t db_idx = get_block_id().get_block_count(); - _tree->access<eeprom_map_t>(_root_path / "eeprom") - .add_coerced_subscriber([this, db_idx](const eeprom_map_t& db_eeprom) { - this->_rpcc->notify_with_token("set_db_eeprom", db_idx, db_eeprom); - }) - .set_publisher([this, db_idx]() { - return this->_rpcc->request_with_token<eeprom_map_t>("get_db_eeprom", db_idx); - }); - - // Init sensors - for (const auto& dir : std::vector<direction_t>{RX_DIRECTION, TX_DIRECTION}) { - for (size_t chan_idx = 0; chan_idx < E3XX_NUM_CHANS; chan_idx++) { - _init_mpm_sensors(dir, chan_idx); - } - } -} - -bool e3xx_radio_ctrl_impl::get_lo_lock_status(const direction_t dir) -{ - if (not(bool(_rpcc))) { - UHD_LOG_DEBUG(unique_id(), "Reported no LO lock due to lack of RPC connection."); - return false; - } - - const std::string trx = (dir == RX_DIRECTION) ? "rx" : "tx"; - bool lo_lock = - _rpcc->request_with_token<bool>(_rpc_prefix + "get_ad9361_lo_lock", trx); - UHD_LOG_TRACE(unique_id(), - "AD9361 " << trx << " LO reports lock: " << (lo_lock ? "Yes" : "No")); - - return lo_lock; -} - -void e3xx_radio_ctrl_impl::_set_atr_bits(const size_t chan) -{ - const auto rx_freq = radio_ctrl_impl::get_rx_frequency(chan); - const auto tx_freq = radio_ctrl_impl::get_tx_frequency(chan); - const auto rx_ant = radio_ctrl_impl::get_rx_antenna(chan); - const uint32_t rx_regs = this->get_rx_switches(chan, rx_freq, rx_ant); - const uint32_t tx_regs = this->get_tx_switches(chan, tx_freq); - const uint32_t idle_regs = this->get_idle_switches(); - - _db_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_IDLE, idle_regs); - _db_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_RX_ONLY, rx_regs); - _db_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_TX_ONLY, tx_regs); - _db_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_FULL_DUPLEX, rx_regs | tx_regs); - - // The LED signal names are reversed, but are consistent with the schematic - const bool is_txrx = rx_ant == "TX/RX"; - const int idle_led = 0; - const int rx_led = this->get_rx_led(); - const int tx_led = this->get_tx_led(); - const int txrx_led = this->get_txrx_led(); - - _leds_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_IDLE, idle_led); - _leds_gpio[chan]->set_atr_reg( - usrp::gpio_atr::ATR_REG_RX_ONLY, is_txrx ? txrx_led : rx_led); - _leds_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_TX_ONLY, tx_led); - _leds_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_FULL_DUPLEX, rx_led | tx_led); -} - -void e3xx_radio_ctrl_impl::_identify_with_leds(const int identify_duration) -{ - auto end_time = - std::chrono::steady_clock::now() + std::chrono::seconds(identify_duration); - bool led_state = true; - while (std::chrono::steady_clock::now() < end_time) { - // Add update_leds - led_state = !led_state; - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - } -} diff --git a/host/lib/usrp/dboard/e3xx/e3xx_radio_ctrl_impl.hpp b/host/lib/usrp/dboard/e3xx/e3xx_radio_ctrl_impl.hpp deleted file mode 100644 index 41c2c4594..000000000 --- a/host/lib/usrp/dboard/e3xx/e3xx_radio_ctrl_impl.hpp +++ /dev/null @@ -1,215 +0,0 @@ -// -// Copyright 2018 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#ifndef INCLUDED_LIBUHD_RFNOC_E3XX_RADIO_CTRL_IMPL_HPP -# define INCLUDED_LIBUHD_RFNOC_E3XX_RADIO_CTRL_IMPL_HPP - -# include "e3xx_ad9361_iface.hpp" -# include <uhd/types/serial.hpp> -# include <uhd/usrp/dboard_manager.hpp> -# include <uhd/usrp/gpio_defs.hpp> -# include <uhdlib/rfnoc/radio_ctrl_impl.hpp> -# include <uhdlib/rfnoc/rpc_block_ctrl.hpp> -# include <uhdlib/usrp/cores/gpio_atr_3000.hpp> -# include <mutex> - -namespace uhd { namespace rfnoc { - -/*! \brief Provide access to an E3xx radio. - */ -class e3xx_radio_ctrl_impl : public radio_ctrl_impl, public rpc_block_ctrl -{ -public: - typedef boost::shared_ptr<e3xx_radio_ctrl_impl> sptr; - - //! Frequency bands for RX. Bands are a function of the analog filter banks - enum class rx_band { INVALID_BAND, LB_B2, LB_B3, LB_B4, LB_B5, LB_B6, LB_B7, HB }; - - //! Frequency bands for TX. Bands are a function of the analog filter banks - enum class tx_band { - INVALID_BAND, - LB_80, - LB_160, - LB_225, - LB_400, - LB_575, - LB_1000, - LB_1700, - LB_2750, - HB - }; - - /************************************************************************** - * ATR/ Switches Types - *************************************************************************/ - //! ATR state - enum atr_state_t { IDLE, RX_ONLY, TX_ONLY, FULL_DUPLEX }; - - //! Channel select: - enum chan_sel_t { CHAN1, CHAN2, BOTH }; - - /************************************************************************ - * Structors - ***********************************************************************/ - e3xx_radio_ctrl_impl(); - virtual ~e3xx_radio_ctrl_impl(); - - /************************************************************************ - * API calls - ***********************************************************************/ - - // Note: We use the cached values in radio_ctrl_impl, so most getters are - // not reimplemented here - //! Set streaming mode - active chains, channel_mode, timing_mode - void set_streaming_mode( - const bool tx1, const bool tx2, const bool rx1, const bool rx2); - - //! Set which channel mode is used - void set_channel_mode(const std::string& channel_mode); - - double set_rate(const double rate); - - void set_tx_antenna(const std::string& ant, const size_t chan); - void set_rx_antenna(const std::string& ant, const size_t chan); - - double set_tx_frequency(const double freq, const size_t chan); - double set_rx_frequency(const double freq, const size_t chan); - double set_tx_bandwidth(const double bandwidth, const size_t chan); - double set_rx_bandwidth(const double bandwidth, const size_t chan); - - // gain - double set_tx_gain(const double gain, const size_t chan); - double set_rx_gain(const double gain, const size_t chan); - - size_t get_chan_from_dboard_fe(const std::string& fe, const direction_t dir); - std::string get_dboard_fe_from_chan(const size_t chan, const direction_t dir); - - void set_rpc_client(uhd::rpc_client::sptr rpcc, const uhd::device_addr_t& block_args); - -protected: - //! Map a frequency in Hz to an rx_band value. Will return - // rx_band::INVALID_BAND if the frequency is out of range. - rx_band map_freq_to_rx_band(const double freq); - //! Map a frequency in Hz to an tx_band value. Will return - // tx_band::INVALID_BAND if the frequency is out of range. - tx_band map_freq_to_tx_band(const double freq); - - virtual const std::string get_default_timing_mode() = 0; - - /*! Run a loopback self test. - * - * This will write data to the AD936x and read it back again. - * If this test fails, it generally means the interface is broken, - * so we assume it passes and throw otherwise. Running this requires - * a core that we can peek and poke the loopback values into. - * - * \param iface An interface to the associated radio control core - * \param iface The radio control core's address to write the loopback value - * \param iface The radio control core's readback address to read back the returned - * value - * - * \throws a uhd::runtime_error if the loopback value didn't match. - */ - virtual void loopback_self_test(std::function<void(uint32_t)> poker_functor, - std::function<uint64_t()> peeker_functor) = 0; - - virtual uint32_t get_rx_switches( - const size_t chan, const double freq, const std::string& ant) = 0; - - virtual uint32_t get_tx_switches(const size_t chan, const double freq) = 0; - - virtual uint32_t get_idle_switches() = 0; - - virtual uint32_t get_tx_led() = 0; - virtual uint32_t get_rx_led() = 0; - virtual uint32_t get_txrx_led() = 0; - virtual uint32_t get_idle_led() = 0; - - //! Reference to the AD9361 controls - // e3xx_ad9361_iface::uptr _ad9361; - ad9361_ctrl::sptr _ad9361; - - //! Swap RFA and RFB for catalina - bool _fe_swap; - -private: - /************************************************************************** - * Helpers - *************************************************************************/ - //! Initialize all the peripherals connected to this block - void _init_peripherals(); - - //! Set state of this class to sensible defaults - void _init_defaults(); - - //! Init a subtree for the RF frontends - void _init_frontend_subtree(uhd::property_tree::sptr subtree, const size_t chan_idx); - - //! Initialize Catalina defaults - void _init_codec(); - - //! Initialize property tree - void _init_prop_tree(); - - void _init_mpm_sensors(const direction_t dir, const size_t chan_idx); - - /************************************************************************* - * Sensors - *************************************************************************/ - //! Return LO lock status. Factors in current band (low/high) and - // direction (TX/RX) - bool get_lo_lock_status(const direction_t dir); - - /************************************************************************** - * Misc Controls - *************************************************************************/ - //! Blink the front-panel LEDs for \p identify_duration, - // and resume normal operation. - void _identify_with_leds(const int identify_duration); - - void _set_atr_bits(const size_t chan); - - /************************************************************************** - * Private attributes - *************************************************************************/ - //! Locks access to setter APIs - std::mutex _set_lock; - - //! Letter representation of the radio we're currently running - std::string _radio_slot; - - //! Prepended for all dboard RPC calls - std::string _rpc_prefix; - - //! Additional block args; gets set during set_rpc_client() - uhd::device_addr_t _block_args; - - //! Reference to the RPC client - uhd::rpc_client::sptr _rpcc; - - //! Reference to the SPI core - uhd::spi_iface::sptr _spi; - - //! ATR controls. These control the AD9361 gain - // up/down bits. - // Every radio channel gets its own ATR state register. - std::vector<usrp::gpio_atr::gpio_atr_3000::sptr> _db_gpio; - - // ATR controls for LEDs - std::vector<usrp::gpio_atr::gpio_atr_3000::sptr> _leds_gpio; - - //! Front panel GPIO controller. Note that only one radio block per - // module can be the FP-GPIO master. - usrp::gpio_atr::gpio_atr_3000::sptr _fp_gpio; - - //! Sampling rate - double _master_clock_rate = 1.0; -}; /* class radio_ctrl_impl */ - -}} /* namespace uhd::rfnoc */ - -#endif /* INCLUDED_LIBUHD_RFNOC_E3XX_RADIO_CTRL_IMPL_HPP */ -// vim: sw=4 et: diff --git a/host/lib/usrp/dboard/e3xx/e3xx_radio_ctrl_init.cpp b/host/lib/usrp/dboard/e3xx/e3xx_radio_ctrl_init.cpp deleted file mode 100644 index 5b33b33e7..000000000 --- a/host/lib/usrp/dboard/e3xx/e3xx_radio_ctrl_init.cpp +++ /dev/null @@ -1,427 +0,0 @@ -// -// Copyright 2018 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "e3xx_constants.hpp" -#include "e3xx_radio_ctrl_impl.hpp" -#include <uhd/transport/chdr.hpp> -#include <uhd/types/eeprom.hpp> -#include <uhd/types/sensors.hpp> -#include <uhd/utils/log.hpp> -#include <boost/algorithm/string.hpp> -#include <boost/algorithm/string/case_conv.hpp> -#include <boost/algorithm/string/split.hpp> -#include <string> -#include <vector> - -using namespace uhd; -using namespace uhd::rfnoc; - -//! Helper function to extract single value of port number. -// -// Each GPIO pins can be controlled by each radio output ports. -// This function convert the format of attribute "Radio_N_M" -// to a single value port number = N*number_of_port_per_radio + M - -uint32_t _extract_port_number( - std::string radio_src_string, uhd::property_tree::sptr ptree) -{ - std::string s_val = "0"; - std::vector<std::string> radio_strings; - boost::algorithm::split(radio_strings, - radio_src_string, - boost::is_any_of("_/"), - boost::token_compress_on); - boost::to_lower(radio_strings[0]); - if (radio_strings.size() < 3) { - throw uhd::runtime_error(str( - boost::format("%s is an invalid GPIO source string.") % radio_src_string)); - } - size_t radio_num = std::stoi(radio_strings[1]); - size_t port_num = std::stoi(radio_strings[2]); - if (radio_strings[0] != "radio") { - throw uhd::runtime_error( - "Front panel GPIO bank can only accept a radio block as its driver."); - } - std::string radio_port_out = "Radio_" + radio_strings[1] + "/ports/out"; - std::string radio_port_path = radio_port_out + "/" + radio_strings[2]; - auto found = ptree->exists(fs_path("xbar") / radio_port_path); - if (not found) { - throw uhd::runtime_error( - str(boost::format("Could not find radio port %s.\n") % radio_port_path)); - } - size_t port_size = ptree->list(fs_path("xbar") / radio_port_out).size(); - return radio_num * port_size + port_num; -} - -void e3xx_radio_ctrl_impl::_init_defaults() -{ - UHD_LOG_TRACE(unique_id(), "Initializing defaults..."); - const size_t num_rx_chans = get_output_ports().size(); - const size_t num_tx_chans = get_input_ports().size(); - - UHD_LOG_TRACE(unique_id(), - "Num TX chans: " << num_tx_chans << " Num RX chans: " << num_rx_chans); - - for (size_t chan = 0; chan < num_rx_chans; chan++) { - radio_ctrl_impl::set_rx_frequency(E3XX_DEFAULT_FREQ, chan); - radio_ctrl_impl::set_rx_gain(E3XX_DEFAULT_GAIN, chan); - radio_ctrl_impl::set_rx_antenna(E3XX_DEFAULT_RX_ANTENNA, chan); - radio_ctrl_impl::set_rx_bandwidth(E3XX_DEFAULT_BANDWIDTH, chan); - } - - for (size_t chan = 0; chan < num_tx_chans; chan++) { - radio_ctrl_impl::set_tx_frequency(E3XX_DEFAULT_FREQ, chan); - radio_ctrl_impl::set_tx_gain(E3XX_DEFAULT_GAIN, chan); - radio_ctrl_impl::set_tx_antenna(E3XX_DEFAULT_TX_ANTENNA, chan); - radio_ctrl_impl::set_tx_bandwidth(E3XX_DEFAULT_BANDWIDTH, chan); - } - - /** Update default SPP (overwrites the default value from the XML file) **/ - const size_t max_bytes_header = - uhd::transport::vrt::chdr::max_if_hdr_words64 * sizeof(uint64_t); - const size_t default_spp = - (_tree->access<size_t>("mtu/recv").get() - max_bytes_header) - / (2 * sizeof(int16_t)); - UHD_LOG_DEBUG(unique_id(), "Setting default spp to " << default_spp); - _tree->access<int>(get_arg_path("spp") / "value").set(default_spp); -} - -void e3xx_radio_ctrl_impl::_init_peripherals() -{ - UHD_LOG_TRACE(unique_id(), "Initializing peripherals..."); - - _db_gpio.clear(); // Following the as-if rule, this can get optimized out - for (size_t radio_idx = 0; radio_idx < _get_num_radios(); radio_idx++) { - UHD_LOG_TRACE(unique_id(), "Initializing GPIOs for channel " << radio_idx); - _db_gpio.emplace_back(usrp::gpio_atr::gpio_atr_3000::make_write_only( - _get_ctrl(radio_idx), regs::sr_addr(regs::GPIO))); - _db_gpio[radio_idx]->set_atr_mode( - usrp::gpio_atr::MODE_ATR, usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL); - } - _leds_gpio.clear(); // Following the as-if rule, this can get optimized out - for (size_t radio_idx = 0; radio_idx < _get_num_radios(); radio_idx++) { - UHD_LOG_TRACE(unique_id(), "Initializing GPIOs for channel " << radio_idx); - _leds_gpio.emplace_back(usrp::gpio_atr::gpio_atr_3000::make_write_only( - _get_ctrl(radio_idx), regs::sr_addr(regs::LEDS))); - - _leds_gpio[radio_idx]->set_atr_mode( - usrp::gpio_atr::MODE_ATR, usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL); - } - UHD_LOG_TRACE(unique_id(), "Initializing front-panel GPIO control...") - _fp_gpio = usrp::gpio_atr::gpio_atr_3000::make( - _get_ctrl(0), regs::sr_addr(regs::FP_GPIO), regs::rb_addr(regs::RB_FP_GPIO)); -} - -void e3xx_radio_ctrl_impl::_init_frontend_subtree( - uhd::property_tree::sptr subtree, const size_t chan_idx) -{ - const fs_path tx_fe_path = fs_path("tx_frontends") / chan_idx; - const fs_path rx_fe_path = fs_path("rx_frontends") / chan_idx; - UHD_LOG_TRACE(unique_id(), - "Adding non-RFNoC block properties for channel " - << chan_idx << " to prop tree path " << tx_fe_path << " and " << rx_fe_path); - // TX Standard attributes - subtree->create<std::string>(tx_fe_path / "name").set(str(boost::format("E3xx"))); - subtree->create<std::string>(tx_fe_path / "connection").set("IQ"); - // RX Standard attributes - subtree->create<std::string>(rx_fe_path / "name").set(str(boost::format("E3xx"))); - subtree->create<std::string>(rx_fe_path / "connection").set("IQ"); - // TX Antenna - subtree->create<std::string>(tx_fe_path / "antenna" / "value") - .add_coerced_subscriber([this, chan_idx](const std::string& ant) { - this->set_tx_antenna(ant, chan_idx); - }) - .set_publisher([this, chan_idx]() { return this->get_tx_antenna(chan_idx); }); - subtree->create<std::vector<std::string>>(tx_fe_path / "antenna" / "options") - .set({E3XX_DEFAULT_TX_ANTENNA}) - .add_coerced_subscriber([](const std::vector<std::string>&) { - throw uhd::runtime_error("Attempting to update antenna options!"); - }); - // RX Antenna - subtree->create<std::string>(rx_fe_path / "antenna" / "value") - .add_coerced_subscriber([this, chan_idx](const std::string& ant) { - this->set_rx_antenna(ant, chan_idx); - }) - .set_publisher([this, chan_idx]() { return this->get_rx_antenna(chan_idx); }); - subtree->create<std::vector<std::string>>(rx_fe_path / "antenna" / "options") - .set(E3XX_RX_ANTENNAS) - .add_coerced_subscriber([](const std::vector<std::string>&) { - throw uhd::runtime_error("Attempting to update antenna options!"); - }); - // TX frequency - subtree->create<double>(tx_fe_path / "freq" / "value") - .set_coercer([this, chan_idx](const double freq) { - return this->set_tx_frequency(freq, chan_idx); - }) - .set_publisher([this, chan_idx]() { return this->get_tx_frequency(chan_idx); }); - subtree->create<meta_range_t>(tx_fe_path / "freq" / "range") - .set(meta_range_t(AD9361_TX_MIN_FREQ, AD9361_TX_MAX_FREQ, 1.0)) - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update freq range!"); - }); - // RX frequency - subtree->create<double>(rx_fe_path / "freq" / "value") - .set_coercer([this, chan_idx](const double freq) { - return this->set_rx_frequency(freq, chan_idx); - }) - .set_publisher([this, chan_idx]() { return this->get_rx_frequency(chan_idx); }); - subtree->create<meta_range_t>(rx_fe_path / "freq" / "range") - .set(meta_range_t(AD9361_RX_MIN_FREQ, AD9361_RX_MAX_FREQ, 1.0)) - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update freq range!"); - }); - // TX bandwidth - subtree->create<double>(tx_fe_path / "bandwidth" / "value") - .set(AD9361_TX_MAX_BANDWIDTH) - .set_coercer([this, chan_idx](const double bw) { - return this->set_tx_bandwidth(bw, chan_idx); - }) - .set_publisher([this, chan_idx]() { return this->get_tx_bandwidth(chan_idx); }); - subtree->create<meta_range_t>(tx_fe_path / "bandwidth" / "range") - .set(meta_range_t(AD9361_TX_MIN_BANDWIDTH, AD9361_TX_MAX_BANDWIDTH)) - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update bandwidth range!"); - }); - // RX bandwidth - subtree->create<double>(rx_fe_path / "bandwidth" / "value") - .set(AD9361_RX_MAX_BANDWIDTH) - .set_coercer([this, chan_idx](const double bw) { - return this->set_rx_bandwidth(bw, chan_idx); - }); - subtree->create<meta_range_t>(rx_fe_path / "bandwidth" / "range") - .set(meta_range_t(AD9361_RX_MIN_BANDWIDTH, AD9361_RX_MAX_BANDWIDTH)) - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update bandwidth range!"); - }); - - // TX gains - const std::vector<std::string> tx_gain_names = ad9361_ctrl::get_gain_names("TX1"); - for (auto tx_gain_name : tx_gain_names) { - subtree->create<double>(tx_fe_path / "gains" / tx_gain_name / "value") - .set_coercer([this, chan_idx](const double gain) { - return this->set_tx_gain(gain, chan_idx); - }) - .set_publisher( - [this, chan_idx]() { return radio_ctrl_impl::get_tx_gain(chan_idx); }); - subtree->create<meta_range_t>(tx_fe_path / "gains" / tx_gain_name / "range") - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update gain range!"); - }) - .set_publisher([this]() { - return meta_range_t( - AD9361_MIN_TX_GAIN, AD9361_MAX_TX_GAIN, AD9361_TX_GAIN_STEP); - }); - } - - // RX gains - const std::vector<std::string> rx_gain_names = ad9361_ctrl::get_gain_names("RX1"); - for (auto rx_gain_name : rx_gain_names) { - subtree->create<double>(rx_fe_path / "gains" / rx_gain_name / "value") - .set_coercer([this, chan_idx](const double gain) { - return this->set_rx_gain(gain, chan_idx); - }) - .set_publisher( - [this, chan_idx]() { return radio_ctrl_impl::get_rx_gain(chan_idx); }); - - subtree->create<meta_range_t>(rx_fe_path / "gains" / rx_gain_name / "range") - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update gain range!"); - }) - .set_publisher([this]() { - return meta_range_t( - AD9361_MIN_RX_GAIN, AD9361_MAX_RX_GAIN, AD9361_RX_GAIN_STEP); - }); - } - - // TX LO lock sensor ////////////////////////////////////////////////////// - // Note: The AD9361 LO lock sensors are generated programmatically in - // set_rpc_client(). The actual lo_locked publisher is also set there. - subtree->create<sensor_value_t>(tx_fe_path / "sensors" / "lo_locked") - .set(sensor_value_t("all_los", false, "locked", "unlocked")) - .add_coerced_subscriber([](const sensor_value_t&) { - throw uhd::runtime_error("Attempting to write to sensor!"); - }) - .set_publisher([this]() { - return sensor_value_t( - "all_los", this->get_lo_lock_status(TX_DIRECTION), "locked", "unlocked"); - }); - // RX LO lock sensor (see not on TX LO lock sensor) - subtree->create<sensor_value_t>(rx_fe_path / "sensors" / "lo_locked") - .set(sensor_value_t("all_los", false, "locked", "unlocked")) - .add_coerced_subscriber([](const sensor_value_t&) { - throw uhd::runtime_error("Attempting to write to sensor!"); - }) - .set_publisher([this]() { - return sensor_value_t( - "all_los", this->get_lo_lock_status(RX_DIRECTION), "locked", "unlocked"); - }); -} - -void e3xx_radio_ctrl_impl::_init_prop_tree() -{ - const fs_path fe_base = fs_path("dboards") / _radio_slot; - for (size_t chan_idx = 0; chan_idx < E3XX_NUM_CHANS; chan_idx++) { - this->_init_frontend_subtree(_tree->subtree(fe_base), chan_idx); - } - - _tree->create<eeprom_map_t>(_root_path / "eeprom").set(eeprom_map_t()); - - _tree->create<int>("rx_codecs" / _radio_slot / "gains"); - _tree->create<int>("tx_codecs" / _radio_slot / "gains"); - _tree->create<std::string>("rx_codecs" / _radio_slot / "name").set("AD9361 Dual ADC"); - _tree->create<std::string>("tx_codecs" / _radio_slot / "name").set("AD9361 Dual DAC"); - - if (not _tree->exists("tick_rate")) { - _tree->create<double>("tick_rate") - .set_coercer([this](double tick_rate) { return this->set_rate(tick_rate); }) - .set_publisher([this]() { return this->get_rate(); }); - } else { - UHD_LOG_WARNING(unique_id(), "Cannot set tick_rate again"); - } - - // *****FP_GPIO************************ - for (const auto& attr : usrp::gpio_atr::gpio_attr_map) { - if (not _tree->exists(fs_path("gpio") / "FP0" / attr.second)) { - switch (attr.first) { - case usrp::gpio_atr::GPIO_SRC: - // FIXME: move this creation of this branch of ptree out side of - // radio impl; - // since there's no data dependency between radio and SRC setting for - // FP0 - _tree - ->create<std::vector<std::string>>( - fs_path("gpio") / "FP0" / attr.second) - .set(std::vector<std::string>( - 32, usrp::gpio_atr::default_attr_value_map.at(attr.first))) - .add_coerced_subscriber( - [this, attr](const std::vector<std::string> str_val) { - uint32_t radio_src_value = 0; - uint32_t master_value = 0; - for (size_t i = 0; i < str_val.size(); i++) { - if (str_val[i] == "PS") { - master_value += 1 << i; - ; - } else { - auto port_num = - _extract_port_number(str_val[i], _tree); - radio_src_value = - (1 << (2 * i)) * port_num + radio_src_value; - } - } - _rpcc->notify_with_token( - "set_fp_gpio_master", master_value); - _rpcc->notify_with_token( - "set_fp_gpio_radio_src", radio_src_value); - }); - break; - case usrp::gpio_atr::GPIO_CTRL: - case usrp::gpio_atr::GPIO_DDR: - _tree - ->create<std::vector<std::string>>( - fs_path("gpio") / "FP0" / attr.second) - .set(std::vector<std::string>( - 32, usrp::gpio_atr::default_attr_value_map.at(attr.first))) - .add_coerced_subscriber( - [this, attr](const std::vector<std::string> str_val) { - uint32_t val = 0; - for (size_t i = 0; i < str_val.size(); i++) { - val += usrp::gpio_atr::gpio_attr_value_pair - .at(attr.second) - .at(str_val[i]) - << i; - } - _fp_gpio->set_gpio_attr(attr.first, val); - }); - break; - case usrp::gpio_atr::GPIO_READBACK: { - _tree->create<uint32_t>(fs_path("gpio") / "FP0" / attr.second) - .set_publisher([this]() { return _fp_gpio->read_gpio(); }); - } break; - default: - _tree->create<uint32_t>(fs_path("gpio") / "FP0" / attr.second) - .set(0) - .add_coerced_subscriber([this, attr](const uint32_t val) { - _fp_gpio->set_gpio_attr(attr.first, val); - }); - } - } else { - switch (attr.first) { - case usrp::gpio_atr::GPIO_SRC: - break; - case usrp::gpio_atr::GPIO_CTRL: - case usrp::gpio_atr::GPIO_DDR: - _tree - ->access<std::vector<std::string>>( - fs_path("gpio") / "FP0" / attr.second) - .set(std::vector<std::string>( - 32, usrp::gpio_atr::default_attr_value_map.at(attr.first))) - .add_coerced_subscriber( - [this, attr](const std::vector<std::string> str_val) { - uint32_t val = 0; - for (size_t i = 0; i < str_val.size(); i++) { - val += usrp::gpio_atr::gpio_attr_value_pair - .at(attr.second) - .at(str_val[i]) - << i; - } - _fp_gpio->set_gpio_attr(attr.first, val); - }); - break; - case usrp::gpio_atr::GPIO_READBACK: - break; - default: - _tree->access<uint32_t>(fs_path("gpio") / "FP0" / attr.second) - .set(0) - .add_coerced_subscriber([this, attr](const uint32_t val) { - _fp_gpio->set_gpio_attr(attr.first, val); - }); - } - } - } -} - -void e3xx_radio_ctrl_impl::_init_codec() -{ - for (size_t chan = 0; chan < _get_num_radios(); chan++) { - std::string rx_fe = get_which_ad9361_chain(RX_DIRECTION, chan); - this->set_rx_gain(E3XX_DEFAULT_GAIN, chan); - this->set_rx_frequency(E3XX_DEFAULT_FREQ, chan); - this->set_rx_antenna(E3XX_DEFAULT_RX_ANTENNA, chan); - this->set_rx_bandwidth(E3XX_DEFAULT_BANDWIDTH, chan); - _ad9361->set_dc_offset_auto(rx_fe, E3XX_DEFAULT_AUTO_DC_OFFSET); - _ad9361->set_iq_balance_auto(rx_fe, E3XX_DEFAULT_AUTO_IQ_BALANCE); - _ad9361->set_agc(rx_fe, E3XX_DEFAULT_AGC_ENABLE); - std::string tx_fe = get_which_ad9361_chain(TX_DIRECTION, chan); - this->set_tx_gain(E3XX_DEFAULT_GAIN, chan); - this->set_tx_frequency(E3XX_DEFAULT_FREQ, chan); - this->set_tx_bandwidth(E3XX_DEFAULT_BANDWIDTH, chan); - } -} - -void e3xx_radio_ctrl_impl::_init_mpm_sensors(const direction_t dir, const size_t chan_idx) -{ - const std::string trx = (dir == RX_DIRECTION) ? "RX" : "TX"; - const fs_path fe_path = fs_path("dboards") / _radio_slot - / (dir == RX_DIRECTION ? "rx_frontends" : "tx_frontends") - / chan_idx; - auto sensor_list = _rpcc->request_with_token<std::vector<std::string>>( - this->_rpc_prefix + "get_sensors", trx); - UHD_LOG_TRACE(unique_id(), - "Chan " << chan_idx << ": Found " << sensor_list.size() << " " << trx - << " sensors."); - for (const auto& sensor_name : sensor_list) { - UHD_LOG_TRACE(unique_id(), "Adding " << trx << " sensor " << sensor_name); - _tree->create<sensor_value_t>(fe_path / "sensors" / sensor_name) - .add_coerced_subscriber([](const sensor_value_t&) { - throw uhd::runtime_error("Attempting to write to sensor!"); - }) - .set_publisher([this, trx, sensor_name, chan_idx]() { - return sensor_value_t( - this->_rpcc->request_with_token<sensor_value_t::sensor_map_t>( - this->_rpc_prefix + "get_sensor", trx, sensor_name, chan_idx)); - }); - } -} diff --git a/host/lib/usrp/dboard/magnesium/CMakeLists.txt b/host/lib/usrp/dboard/magnesium/CMakeLists.txt index 1029bfacd..df5fe765a 100644 --- a/host/lib/usrp/dboard/magnesium/CMakeLists.txt +++ b/host/lib/usrp/dboard/magnesium/CMakeLists.txt @@ -8,10 +8,10 @@ # set to true. list(APPEND MAGNESIUM_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_radio_ctrl_impl.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_radio_ctrl_init.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_radio_ctrl_cpld.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_radio_ctrl_gain.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_radio_control.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_radio_control_init.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_radio_control_cpld.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_radio_control_gain.cpp ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_ad9371_iface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_bands.cpp ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_cpld_ctrl.cpp diff --git a/host/lib/usrp/dboard/magnesium/magnesium_bands.cpp b/host/lib/usrp/dboard/magnesium/magnesium_bands.cpp index 13cc52d49..ef72aee95 100644 --- a/host/lib/usrp/dboard/magnesium/magnesium_bands.cpp +++ b/host/lib/usrp/dboard/magnesium/magnesium_bands.cpp @@ -7,7 +7,7 @@ // The band plan #include "magnesium_constants.hpp" -#include "magnesium_radio_ctrl_impl.hpp" +#include "magnesium_radio_control.hpp" #include <uhd/utils/math.hpp> /* @@ -93,11 +93,10 @@ namespace { } // namespace - -magnesium_radio_ctrl_impl::rx_band magnesium_radio_ctrl_impl::_map_freq_to_rx_band( +magnesium_radio_control_impl::rx_band magnesium_radio_control_impl::_map_freq_to_rx_band( const band_map_t band_map, const double freq) { - magnesium_radio_ctrl_impl::rx_band band; + magnesium_radio_control_impl::rx_band band; if (fp_compare_epsilon<double>(freq) < MAGNESIUM_MIN_FREQ) { band = rx_band::INVALID_BAND; @@ -124,10 +123,10 @@ magnesium_radio_ctrl_impl::rx_band magnesium_radio_ctrl_impl::_map_freq_to_rx_ba return band; } -magnesium_radio_ctrl_impl::tx_band magnesium_radio_ctrl_impl::_map_freq_to_tx_band( +magnesium_radio_control_impl::tx_band magnesium_radio_control_impl::_map_freq_to_tx_band( const band_map_t band_map, const double freq) { - magnesium_radio_ctrl_impl::tx_band band; + magnesium_radio_control_impl::tx_band band; if (fp_compare_epsilon<double>(freq) < MAGNESIUM_MIN_FREQ) { band = tx_band::INVALID_BAND; diff --git a/host/lib/usrp/dboard/magnesium/magnesium_constants.hpp b/host/lib/usrp/dboard/magnesium/magnesium_constants.hpp index 9b3bdf800..7d98bca91 100644 --- a/host/lib/usrp/dboard/magnesium/magnesium_constants.hpp +++ b/host/lib/usrp/dboard/magnesium/magnesium_constants.hpp @@ -69,6 +69,13 @@ static constexpr char MAGNESIUM_GAIN2[] = "dsa"; //! Amplifier gain static constexpr char MAGNESIUM_AMP[] = "amp"; +static constexpr char MAGNESIUM_FE_NAME[] = "Magnesium"; + +static constexpr char MAGNESIUM_DEFAULT_RX_ANTENNA[] = "RX2"; +static constexpr char MAGNESIUM_DEFAULT_TX_ANTENNA[] = "TX/RX"; + +static constexpr char MAGNESIUM_FPGPIO_BANK[] = "FP0"; + // Note: MAGNESIUM_NUM_CHANS is independent of the number of chans per // RFNoC block. TODO: When we go to one radio per dboard, this comment can // be deleted. @@ -79,4 +86,20 @@ static constexpr double MAGNESIUM_TX_IF_FREQ = 1.95e9; //! Max time we allow for a call to set_freq() to take static constexpr size_t MAGNESIUM_TUNE_TIMEOUT = 15000; // milliseconds +//! Magnesium gain profile options +static const std::vector<std::string> MAGNESIUM_GP_OPTIONS = {"manual", + "default", + "default_rf_filter_bypass_always_on", + "default_rf_filter_bypass_always_off"}; + +namespace n310_regs { + +constexpr uint32_t DB_GPIO_BASE = 0x80000; // FIXME +constexpr uint32_t DB_GPIO_RB = 0x80000; // FIXME +constexpr uint32_t DB_GPIO_OFFSET = 0x100; // FIXME +constexpr uint32_t FP_GPIO = 0x80000; // FIXME +constexpr uint32_t RB_FP_GPIO = 0x80000; // FIXME + +} + #endif /* INCLUDED_LIBUHD_MAGNESIUM_CONSTANTS_HPP */ diff --git a/host/lib/usrp/dboard/magnesium/magnesium_gain_table.cpp b/host/lib/usrp/dboard/magnesium/magnesium_gain_table.cpp index 67b20f5fa..3e513218a 100644 --- a/host/lib/usrp/dboard/magnesium/magnesium_gain_table.cpp +++ b/host/lib/usrp/dboard/magnesium/magnesium_gain_table.cpp @@ -15,8 +15,8 @@ using namespace uhd::rfnoc; using namespace magnesium; namespace { -typedef magnesium_radio_ctrl_impl::rx_band rx_band; -typedef magnesium_radio_ctrl_impl::tx_band tx_band; +typedef magnesium_radio_control_impl::rx_band rx_band; +typedef magnesium_radio_control_impl::tx_band tx_band; const size_t TX_LOWBAND = 0; const size_t TX_HIGHBAND = 1; @@ -454,7 +454,7 @@ gain_tuple_t fine_tune_ad9371_att(const gain_tuple_t gain_tuple, const double ga gain_tuple_t magnesium::get_rx_gain_tuple( - const double gain_index, const magnesium_radio_ctrl_impl::rx_band band) + const double gain_index, const magnesium_radio_control_impl::rx_band band) { UHD_ASSERT_THROW(gain_index <= ALL_RX_MAX_GAIN and gain_index >= ALL_RX_MIN_GAIN); auto& gain_table = rx_gain_tables.at(map_rx_band(band)); @@ -463,7 +463,7 @@ gain_tuple_t magnesium::get_rx_gain_tuple( } gain_tuple_t magnesium::get_tx_gain_tuple( - const double gain_index, const magnesium_radio_ctrl_impl::tx_band band) + const double gain_index, const magnesium_radio_control_impl::tx_band band) { UHD_ASSERT_THROW(gain_index <= ALL_TX_MAX_GAIN and gain_index >= ALL_TX_MIN_GAIN); auto& gain_table = tx_gain_tables.at(map_tx_band(band)); diff --git a/host/lib/usrp/dboard/magnesium/magnesium_gain_table.hpp b/host/lib/usrp/dboard/magnesium/magnesium_gain_table.hpp index 8769b44ac..6ba91a248 100644 --- a/host/lib/usrp/dboard/magnesium/magnesium_gain_table.hpp +++ b/host/lib/usrp/dboard/magnesium/magnesium_gain_table.hpp @@ -7,7 +7,7 @@ #ifndef INCLUDED_LIBUHD_MAGNESIUM_GAIN_TABLE_HPP #define INCLUDED_LIBUHD_MAGNESIUM_GAIN_TABLE_HPP -#include "magnesium_radio_ctrl_impl.hpp" +#include "magnesium_radio_control.hpp" #include <uhd/types/direction.hpp> namespace magnesium { @@ -31,12 +31,12 @@ struct gain_tuple_t /*! Given a gain index, return a tuple of gain-related settings (Rx) */ gain_tuple_t get_rx_gain_tuple( - const double gain_index, const uhd::rfnoc::magnesium_radio_ctrl_impl::rx_band band_); + const double gain_index, const uhd::rfnoc::magnesium_radio_control_impl::rx_band band_); /*! Given a gain index, return a tuple of gain-related settings (Tx) */ gain_tuple_t get_tx_gain_tuple( - const double gain_index, const uhd::rfnoc::magnesium_radio_ctrl_impl::tx_band band_); + const double gain_index, const uhd::rfnoc::magnesium_radio_control_impl::tx_band band_); } /* namespace magnesium */ diff --git a/host/lib/usrp/dboard/magnesium/magnesium_radio_control.cpp b/host/lib/usrp/dboard/magnesium/magnesium_radio_control.cpp new file mode 100644 index 000000000..dc78cee7d --- /dev/null +++ b/host/lib/usrp/dboard/magnesium/magnesium_radio_control.cpp @@ -0,0 +1,1151 @@ +// +// Copyright 2017 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "magnesium_radio_control.hpp" +#include "magnesium_constants.hpp" +#include "magnesium_gain_table.hpp" +#include <uhd/exception.hpp> +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/rfnoc/registry.hpp> +#include <uhd/transport/chdr.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/types/eeprom.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/math.hpp> +#include <uhd/rfnoc/registry.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/format.hpp> +#include <boost/make_shared.hpp> +#include <cmath> +#include <cstdlib> +#include <sstream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; +using namespace uhd::math::fp_compare; + +namespace { + +/************************************************************************** + * ADF4351 Controls + *************************************************************************/ +/*! + * \param lo_iface Reference to the LO object + * \param freq Frequency (in Hz) of the tone to be generated from the LO + * \param ref_clock_freq Frequency (in Hz) of the reference clock at the + * PLL input of the LO + * \param int_n_mode Integer-N mode on or off + */ +double _lo_set_frequency(adf435x_iface::sptr lo_iface, + const double freq, + const double ref_clock_freq, + const bool int_n_mode) +{ + UHD_LOG_TRACE("MG/ADF4351", + "Attempting to tune low band LO to " << freq << " Hz with ref clock freq " + << ref_clock_freq); + lo_iface->set_feedback_select(adf435x_iface::FB_SEL_DIVIDED); + lo_iface->set_reference_freq(ref_clock_freq); + lo_iface->set_prescaler(adf435x_iface::PRESCALER_4_5); + const double actual_freq = lo_iface->set_frequency(freq, int_n_mode); + lo_iface->set_output_power( + adf435x_iface::RF_OUTPUT_A, adf435x_iface::OUTPUT_POWER_2DBM); + lo_iface->set_output_power( + adf435x_iface::RF_OUTPUT_B, adf435x_iface::OUTPUT_POWER_2DBM); + lo_iface->set_charge_pump_current(adf435x_iface::CHARGE_PUMP_CURRENT_0_31MA); + return actual_freq; +} + +/*! Configure and enable LO + * + * Will tune it to requested frequency and enable outputs. + * + * \param lo_iface Reference to the LO object + * \param lo_freq Frequency (in Hz) of the tone to be generated from the LO + * \param ref_clock_freq Frequency (in Hz) of the reference clock at the + * PLL input of the LO + * \param int_n_mode Integer-N mode on or off + * \returns the actual frequency the LO is running at + */ +double _lo_enable(adf435x_iface::sptr lo_iface, + const double lo_freq, + const double ref_clock_freq, + const bool int_n_mode) +{ + const double actual_lo_freq = + _lo_set_frequency(lo_iface, lo_freq, ref_clock_freq, int_n_mode); + lo_iface->set_output_enable(adf435x_iface::RF_OUTPUT_A, true); + lo_iface->set_output_enable(adf435x_iface::RF_OUTPUT_B, true); + lo_iface->commit(); + return actual_lo_freq; +} + +/*! Disable LO + */ +void _lo_disable(adf435x_iface::sptr lo_iface) +{ + lo_iface->set_output_enable(adf435x_iface::RF_OUTPUT_A, false); + lo_iface->set_output_enable(adf435x_iface::RF_OUTPUT_B, false); + lo_iface->commit(); +} +} // namespace + + +/****************************************************************************** + * Structors + *****************************************************************************/ +magnesium_radio_control_impl::magnesium_radio_control_impl(make_args_ptr make_args) + : radio_control_impl(std::move(make_args)) +{ + RFNOC_LOG_TRACE("Entering magnesium_radio_control_impl ctor..."); + UHD_ASSERT_THROW(get_block_id().get_block_count() < 2); + const char radio_slot_name[2] = {'A', 'B'}; + _radio_slot = radio_slot_name[get_block_id().get_block_count()]; + RFNOC_LOG_TRACE("Radio slot: " << _radio_slot); + _rpc_prefix = (_radio_slot == "A") ? "db_0_" : "db_1_"; + UHD_ASSERT_THROW(get_num_input_ports() == MAGNESIUM_NUM_CHANS); + UHD_ASSERT_THROW(get_num_output_ports() == MAGNESIUM_NUM_CHANS); + UHD_ASSERT_THROW(get_mb_controller()); + _n310_mb_control = std::dynamic_pointer_cast<mpmd_mb_controller>(get_mb_controller()); + UHD_ASSERT_THROW(_n310_mb_control); + _n3xx_timekeeper = std::dynamic_pointer_cast<mpmd_mb_controller::mpmd_timekeeper>( + _n310_mb_control->get_timekeeper(0)); + UHD_ASSERT_THROW(_n3xx_timekeeper); + _rpcc = _n310_mb_control->get_rpc_client(); + UHD_ASSERT_THROW(_rpcc); + + _init_defaults(); + _init_mpm(); + _init_peripherals(); + _init_prop_tree(); +} + +magnesium_radio_control_impl::~magnesium_radio_control_impl() +{ + RFNOC_LOG_TRACE("magnesium_radio_control_impl::dtor() "); +} + + +/****************************************************************************** + * API Calls + *****************************************************************************/ +double magnesium_radio_control_impl::set_rate(double requested_rate) +{ + meta_range_t rates; + for (const double rate : MAGNESIUM_RADIO_RATES) { + rates.push_back(range_t(rate)); + } + + const double rate = rates.clip(requested_rate); + if (!math::frequencies_are_equal(requested_rate, rate)) { + RFNOC_LOG_WARNING("Coercing requested sample rate from " + << (requested_rate / 1e6) << " to " << (rate / 1e6)); + } + + const double current_rate = get_tick_rate(); + if (math::frequencies_are_equal(current_rate, rate)) { + RFNOC_LOG_DEBUG("Rate is already at " << rate << " MHz. Skipping set_rate()"); + return current_rate; + } + + std::lock_guard<std::mutex> l(_set_lock); + // Now commit to device. First, disable LOs. + _lo_disable(_tx_lo); + _lo_disable(_rx_lo); + _master_clock_rate = _ad9371->set_master_clock_rate(rate); + _n3xx_timekeeper->update_tick_rate(_master_clock_rate); + radio_control_impl::set_rate(_master_clock_rate); + // Frequency settings apply to both channels, no loop needed. Will also + // re-enable the lowband LOs if they were used. + set_rx_frequency(get_rx_frequency(0), 0); + set_tx_frequency(get_tx_frequency(0), 0); + // Gain and bandwidth need to be looped: + for (size_t radio_idx = 0; radio_idx < MAGNESIUM_NUM_CHANS; radio_idx++) { + set_rx_gain(radio_control_impl::get_rx_gain(radio_idx), radio_idx); + set_tx_gain(radio_control_impl::get_rx_gain(radio_idx), radio_idx); + set_rx_bandwidth(get_rx_bandwidth(radio_idx), radio_idx); + set_tx_bandwidth(get_tx_bandwidth(radio_idx), radio_idx); + } + set_tick_rate(_master_clock_rate); + return _master_clock_rate; +} + +void magnesium_radio_control_impl::set_tx_antenna(const std::string& ant, const size_t chan) +{ + if (ant != get_tx_antenna(chan)) { + throw uhd::value_error( + str(boost::format("[%s] Requesting invalid TX antenna value: %s") + % get_unique_id() % ant)); + } + // We can't actually set the TX antenna, so let's stop here. +} + +void magnesium_radio_control_impl::set_rx_antenna(const std::string& ant, const size_t chan) +{ + UHD_ASSERT_THROW(chan <= MAGNESIUM_NUM_CHANS); + if (std::find(MAGNESIUM_RX_ANTENNAS.begin(), MAGNESIUM_RX_ANTENNAS.end(), ant) + == MAGNESIUM_RX_ANTENNAS.end()) { + throw uhd::value_error( + str(boost::format("[%s] Requesting invalid RX antenna value: %s") + % get_unique_id() % ant)); + } + RFNOC_LOG_TRACE("Setting RX antenna to " << ant << " for chan " << chan); + magnesium_cpld_ctrl::chan_sel_t chan_sel = chan == 0 ? magnesium_cpld_ctrl::CHAN1 + : magnesium_cpld_ctrl::CHAN2; + _update_atr_switches(chan_sel, RX_DIRECTION, ant); + + radio_control_impl::set_rx_antenna(ant, chan); +} + +double magnesium_radio_control_impl::set_tx_frequency(const double req_freq, const size_t chan) +{ + const double freq = MAGNESIUM_FREQ_RANGE.clip(req_freq); + RFNOC_LOG_TRACE("set_tx_frequency(f=" << freq << ", chan=" << chan << ")"); + _desired_rf_freq[TX_DIRECTION] = freq; + std::lock_guard<std::mutex> l(_set_lock); + // We need to set the switches on both channels, because they share an LO. + // This way, if we tune channel 0 it will not put channel 1 into a bad + // state. + _update_tx_freq_switches(freq, _tx_bypass_amp, magnesium_cpld_ctrl::BOTH); + const std::string ad9371_source = this->get_tx_lo_source(MAGNESIUM_LO1, chan); + const std::string adf4351_source = this->get_tx_lo_source(MAGNESIUM_LO2, chan); + UHD_ASSERT_THROW(adf4351_source == "internal"); + double coerced_if_freq = freq; + + if (_map_freq_to_tx_band(_tx_band_map, freq) == tx_band::LOWBAND) { + _is_low_band[TX_DIRECTION] = true; + const double desired_low_freq = MAGNESIUM_TX_IF_FREQ - freq; + coerced_if_freq = + this->_set_tx_lo_freq(adf4351_source, MAGNESIUM_LO2, desired_low_freq, chan) + + freq; + RFNOC_LOG_TRACE("coerced_if_freq = " << coerced_if_freq); + } else { + _is_low_band[TX_DIRECTION] = false; + _lo_disable(_tx_lo); + } + // external LO required to tune at 2xdesired_frequency. + const double desired_if_freq = (ad9371_source == "internal") ? coerced_if_freq + : 2 * coerced_if_freq; + + this->_set_tx_lo_freq(ad9371_source, MAGNESIUM_LO1, desired_if_freq, chan); + this->_update_freq(chan, TX_DIRECTION); + this->_update_gain(chan, TX_DIRECTION); + return radio_control_impl::get_tx_frequency(chan); +} + +void magnesium_radio_control_impl::_update_gain(const size_t chan, const uhd::direction_t dir) +{ + const std::string fe = (dir == TX_DIRECTION) ? "tx_frontends" : "rx_frontends"; + const double freq = (dir == TX_DIRECTION) ? this->get_tx_frequency(chan) + : this->get_rx_frequency(chan); + this->_set_all_gain(this->_get_all_gain(chan, dir), freq, chan, dir); +} + +void magnesium_radio_control_impl::_update_freq(const size_t chan, const uhd::direction_t dir) +{ + const std::string ad9371_source = dir == TX_DIRECTION + ? this->get_tx_lo_source(MAGNESIUM_LO1, chan) + : this->get_rx_lo_source(MAGNESIUM_LO1, chan); + + const double ad9371_freq = ad9371_source == "external" ? _ad9371_freq[dir] / 2 + : _ad9371_freq[dir]; + const double rf_freq = _is_low_band[dir] ? ad9371_freq - _adf4351_freq[dir] + : ad9371_freq; + + RFNOC_LOG_TRACE("RF freq = " << rf_freq); + UHD_ASSERT_THROW(fp_compare_epsilon<double>(rf_freq) >= 0); + UHD_ASSERT_THROW(fp_compare_epsilon<double>(std::abs(rf_freq - _desired_rf_freq[dir])) + <= _master_clock_rate / 2); + if (dir == RX_DIRECTION) { + radio_control_impl::set_rx_frequency(rf_freq, chan); + } else if (dir == TX_DIRECTION) { + radio_control_impl::set_tx_frequency(rf_freq, chan); + } else { + UHD_THROW_INVALID_CODE_PATH(); + } +} + +double magnesium_radio_control_impl::set_rx_frequency(const double req_freq, const size_t chan) +{ + const double freq = MAGNESIUM_FREQ_RANGE.clip(req_freq); + RFNOC_LOG_TRACE("set_rx_frequency(f=" << freq << ", chan=" << chan << ")"); + _desired_rf_freq[RX_DIRECTION] = freq; + std::lock_guard<std::mutex> l(_set_lock); + // We need to set the switches on both channels, because they share an LO. + // This way, if we tune channel 0 it will not put channel 1 into a bad + // state. + _update_rx_freq_switches(freq, _rx_bypass_lnas, magnesium_cpld_ctrl::BOTH); + const std::string ad9371_source = this->get_rx_lo_source(MAGNESIUM_LO1, chan); + const std::string adf4351_source = this->get_rx_lo_source(MAGNESIUM_LO2, chan); + UHD_ASSERT_THROW(adf4351_source == "internal"); + double coerced_if_freq = freq; + + if (_map_freq_to_rx_band(_rx_band_map, freq) == rx_band::LOWBAND) { + _is_low_band[RX_DIRECTION] = true; + const double desired_low_freq = MAGNESIUM_RX_IF_FREQ - freq; + coerced_if_freq = + this->_set_rx_lo_freq(adf4351_source, MAGNESIUM_LO2, desired_low_freq, chan) + + freq; + RFNOC_LOG_TRACE("coerced_if_freq = " << coerced_if_freq); + } else { + _is_low_band[RX_DIRECTION] = false; + _lo_disable(_rx_lo); + } + // external LO required to tune at 2xdesired_frequency. + const double desired_if_freq = ad9371_source == "internal" ? coerced_if_freq + : 2 * coerced_if_freq; + + this->_set_rx_lo_freq(ad9371_source, MAGNESIUM_LO1, desired_if_freq, chan); + + this->_update_freq(chan, RX_DIRECTION); + this->_update_gain(chan, RX_DIRECTION); + + return radio_control_impl::get_rx_frequency(chan); +} + +double magnesium_radio_control_impl::set_rx_bandwidth( + const double bandwidth, const size_t chan) +{ + std::lock_guard<std::mutex> l(_set_lock); + _ad9371->set_bandwidth(bandwidth, chan, RX_DIRECTION); + // FIXME: setting analog bandwidth on AD9371 take no effect. + // Remove this warning when ADI can confirm that it works. + RFNOC_LOG_WARNING("set_rx_bandwidth take no effect on AD9371. " + "Default analog bandwidth is 100MHz"); + return AD9371_RX_MAX_BANDWIDTH; +} + +double magnesium_radio_control_impl::set_tx_bandwidth( + const double bandwidth, const size_t chan) +{ + std::lock_guard<std::mutex> l(_set_lock); + _ad9371->set_bandwidth(bandwidth, chan, TX_DIRECTION); + // FIXME: setting analog bandwidth on AD9371 take no effect. + // Remove this warning when ADI can confirm that it works. + RFNOC_LOG_WARNING("set_tx_bandwidth take no effect on AD9371. " + "Default analog bandwidth is 100MHz"); + return AD9371_TX_MAX_BANDWIDTH; +} + +void magnesium_radio_control_impl::set_tx_gain_profile( + const std::string& profile, const size_t) +{ + if (std::find( + MAGNESIUM_GP_OPTIONS.begin(), MAGNESIUM_GP_OPTIONS.end(), profile) + == MAGNESIUM_GP_OPTIONS.end()) { + RFNOC_LOG_ERROR("Invalid TX gain profile: " << profile); + throw uhd::key_error("Invalid TX gain profile!"); + } + _gain_profile[TX_DIRECTION] = profile; +} + +void magnesium_radio_control_impl::set_rx_gain_profile( + const std::string& profile, const size_t) +{ + if (std::find( + MAGNESIUM_GP_OPTIONS.begin(), MAGNESIUM_GP_OPTIONS.end(), profile) + == MAGNESIUM_GP_OPTIONS.end()) { + RFNOC_LOG_ERROR("Invalid RX gain profile: " << profile); + throw uhd::key_error("Invalid RX gain profile!"); + } + _gain_profile[RX_DIRECTION] = profile; +} + +double magnesium_radio_control_impl::set_tx_gain(const double gain, const size_t chan) +{ + std::lock_guard<std::mutex> l(_set_lock); + RFNOC_LOG_TRACE("set_tx_gain(gain=" << gain << ", chan=" << chan << ")"); + const double coerced_gain = + _set_all_gain(gain, this->get_tx_frequency(chan), chan, TX_DIRECTION); + radio_control_impl::set_tx_gain(coerced_gain, chan); + return coerced_gain; +} + +double magnesium_radio_control_impl::_set_tx_gain( + const std::string& name, const double gain, const size_t chan) +{ + std::lock_guard<std::mutex> l(_set_lock); + RFNOC_LOG_TRACE( + "_set_tx_gain(name=" << name << ", gain=" << gain << ", chan=" << chan << ")"); + RFNOC_LOG_TRACE( + "_set_tx_gain(name=" << name << ", gain=" << gain << ", chan=" << chan << ")"); + double clip_gain = 0; + if (name == MAGNESIUM_GAIN1) { + clip_gain = uhd::clip(gain, AD9371_MIN_TX_GAIN, AD9371_MAX_TX_GAIN); + _ad9371_att[TX_DIRECTION] = clip_gain; + } else if (name == MAGNESIUM_GAIN2) { + clip_gain = uhd::clip(gain, DSA_MIN_GAIN, DSA_MAX_GAIN); + _dsa_att[TX_DIRECTION] = clip_gain; + } else if (name == MAGNESIUM_AMP) { + clip_gain = gain > 0.0 ? AMP_MAX_GAIN : AMP_MIN_GAIN; + _amp_bypass[TX_DIRECTION] = clip_gain == 0.0; + } else { + throw uhd::value_error("Could not find gain element " + name); + } + RFNOC_LOG_TRACE("_set_tx_gain calling update gain"); + this->_set_all_gain(this->_get_all_gain(chan, TX_DIRECTION), + this->get_tx_frequency(chan), + chan, + TX_DIRECTION); + return clip_gain; +} + +double magnesium_radio_control_impl::_get_tx_gain( + const std::string& name, const size_t /*chan*/ +) +{ + std::lock_guard<std::mutex> l(_set_lock); + if (name == MAGNESIUM_GAIN1) { + return _ad9371_att[TX_DIRECTION]; + } else if (name == MAGNESIUM_GAIN2) { + return _dsa_att[TX_DIRECTION]; + } else if (name == MAGNESIUM_AMP) { + return _amp_bypass[TX_DIRECTION] ? AMP_MIN_GAIN : AMP_MAX_GAIN; + } else { + throw uhd::value_error("Could not find gain element " + name); + } +} + +double magnesium_radio_control_impl::set_rx_gain(const double gain, const size_t chan) +{ + std::lock_guard<std::mutex> l(_set_lock); + RFNOC_LOG_TRACE("set_rx_gain(gain=" << gain << ", chan=" << chan << ")"); + const double coerced_gain = + _set_all_gain(gain, this->get_rx_frequency(chan), chan, RX_DIRECTION); + radio_control_impl::set_rx_gain(coerced_gain, chan); + return coerced_gain; +} + +double magnesium_radio_control_impl::_set_rx_gain( + const std::string& name, const double gain, const size_t chan) +{ + std::lock_guard<std::mutex> l(_set_lock); + RFNOC_LOG_TRACE( + "_set_rx_gain(name=" << name << ", gain=" << gain << ", chan=" << chan << ")"); + double clip_gain = 0; + if (name == MAGNESIUM_GAIN1) { + clip_gain = uhd::clip(gain, AD9371_MIN_RX_GAIN, AD9371_MAX_RX_GAIN); + _ad9371_att[RX_DIRECTION] = clip_gain; + } else if (name == MAGNESIUM_GAIN2) { + clip_gain = uhd::clip(gain, DSA_MIN_GAIN, DSA_MAX_GAIN); + _dsa_att[RX_DIRECTION] = clip_gain; + } else if (name == MAGNESIUM_AMP) { + clip_gain = gain > 0.0 ? AMP_MAX_GAIN : AMP_MIN_GAIN; + _amp_bypass[RX_DIRECTION] = clip_gain == 0.0; + } else { + throw uhd::value_error("Could not find gain element " + name); + } + RFNOC_LOG_TRACE("_set_rx_gain calling update gain"); + this->_set_all_gain(this->_get_all_gain(chan, RX_DIRECTION), + this->get_rx_frequency(chan), + chan, + RX_DIRECTION); + return clip_gain; // not really any coerced here (only clip) for individual gain +} + +double magnesium_radio_control_impl::_get_rx_gain( + const std::string& name, const size_t /*chan*/ +) +{ + std::lock_guard<std::mutex> l(_set_lock); + + if (name == MAGNESIUM_GAIN1) { + return _ad9371_att[RX_DIRECTION]; + } else if (name == MAGNESIUM_GAIN2) { + return _dsa_att[RX_DIRECTION]; + } else if (name == MAGNESIUM_AMP) { + return _amp_bypass[RX_DIRECTION] ? AMP_MIN_GAIN : AMP_MAX_GAIN; + } else { + throw uhd::value_error("Could not find gain element " + name); + } +} + +double magnesium_radio_control_impl::set_tx_gain( + const double gain, const std::string& name, const size_t chan) +{ + if (get_tx_gain_profile(chan) == "manual") { + if (name == "all" || name == ALL_GAINS) { + RFNOC_LOG_ERROR("Setting overall gain is not supported in manual gain mode!"); + throw uhd::key_error( + "Setting overall gain is not supported in manual gain mode!"); + } + if (name != MAGNESIUM_GAIN1 && name != MAGNESIUM_GAIN2 && name != MAGNESIUM_AMP) { + RFNOC_LOG_ERROR("Invalid TX gain name: " << name); + throw uhd::key_error("Invalid TX gain name!"); + } + const double coerced_gain = get_tx_gain_range(name, chan).clip(gain, true); + if (name == MAGNESIUM_GAIN1) { + _ad9371_att[TX_DIRECTION] = AD9371_MAX_TX_GAIN - coerced_gain; + } else if (name == MAGNESIUM_GAIN2) { + _dsa_set_att(AD9371_MAX_TX_GAIN - coerced_gain, chan, TX_DIRECTION); + } else if (name == MAGNESIUM_AMP) { + _amp_bypass[TX_DIRECTION] = (coerced_gain == AMP_MIN_GAIN); + } else { + throw uhd::value_error("Could not find gain element " + name); + } + _set_all_gain(coerced_gain /* this value doesn't actuall matter */, + get_tx_frequency(chan), + chan, + TX_DIRECTION); + return coerced_gain; + } + + if (name == "all" || name == ALL_GAINS) { + return set_tx_gain(gain, chan); + } + RFNOC_LOG_ERROR("Setting individual TX gains is only supported in manual gain mode!"); + throw uhd::key_error( + "Setting individual TX gains is only supported in manual gain mode!"); +} + +double magnesium_radio_control_impl::set_rx_gain( + const double gain, const std::string& name, const size_t chan) +{ + if (get_rx_gain_profile(chan) == "manual") { + if (name == "all" || name == ALL_GAINS) { + RFNOC_LOG_ERROR("Setting overall gain is not supported in manual gain mode!"); + throw uhd::key_error( + "Setting overall gain is not supported in manual gain mode!"); + } + if (name != MAGNESIUM_GAIN1 && name != MAGNESIUM_GAIN2 && name != MAGNESIUM_AMP) { + RFNOC_LOG_ERROR("Invalid RX gain name: " << name); + throw uhd::key_error("Invalid RX gain name!"); + } + const double coerced_gain = get_rx_gain_range(name, chan).clip(gain, true); + if (name == MAGNESIUM_GAIN1) { + _ad9371_att[RX_DIRECTION] = AD9371_MAX_RX_GAIN - coerced_gain; + } else if (name == MAGNESIUM_GAIN2) { + _dsa_set_att(AD9371_MAX_RX_GAIN - coerced_gain, chan, RX_DIRECTION); + } else if (name == MAGNESIUM_AMP) { + _amp_bypass[RX_DIRECTION] = (coerced_gain == AMP_MIN_GAIN); + } else { + throw uhd::value_error("Could not find gain element " + name); + } + _set_all_gain(coerced_gain /* this value doesn't actuall matter */, + get_rx_frequency(chan), + chan, + RX_DIRECTION); + return coerced_gain; + } + + if (name == "all" || name == ALL_GAINS) { + return set_rx_gain(gain, chan); + } + RFNOC_LOG_ERROR("Setting individual RX gains is only supported in manual gain mode!"); + throw uhd::key_error( + "Setting individual RX gains is only supported in manual gain mode!"); +} + +std::vector<std::string> magnesium_radio_control_impl::get_tx_antennas(const size_t) const +{ + return {"TX/RX"}; +} + +std::vector<std::string> magnesium_radio_control_impl::get_rx_antennas(const size_t) const +{ + return MAGNESIUM_RX_ANTENNAS; +} + +uhd::freq_range_t magnesium_radio_control_impl::get_tx_frequency_range(const size_t) const +{ + return meta_range_t(MAGNESIUM_MIN_FREQ, MAGNESIUM_MAX_FREQ, 1.0); +} + +uhd::freq_range_t magnesium_radio_control_impl::get_rx_frequency_range(const size_t) const +{ + return meta_range_t(MAGNESIUM_MIN_FREQ, MAGNESIUM_MAX_FREQ, 1.0); +} + +std::vector<std::string> magnesium_radio_control_impl::get_tx_gain_names(const size_t) const +{ + return {MAGNESIUM_GAIN1, MAGNESIUM_GAIN2, MAGNESIUM_AMP}; +} + +std::vector<std::string> magnesium_radio_control_impl::get_rx_gain_names(const size_t) const +{ + return {MAGNESIUM_GAIN1, MAGNESIUM_GAIN2, MAGNESIUM_AMP}; +} + +double magnesium_radio_control_impl::get_tx_gain( + const std::string& name, const size_t chan) +{ + if (name == MAGNESIUM_GAIN1 || name == MAGNESIUM_GAIN2 || name == MAGNESIUM_AMP) { + return _get_tx_gain(name, chan); + } + if (name == "all" || name == ALL_GAINS) { + return radio_control_impl::get_tx_gain(chan); + } + RFNOC_LOG_ERROR("Invalid TX gain name: " << name); + throw uhd::key_error("Invalid TX gain name!"); +} + +double magnesium_radio_control_impl::get_rx_gain( + const std::string& name, const size_t chan) +{ + if (name == MAGNESIUM_GAIN1 || name == MAGNESIUM_GAIN2 || name == MAGNESIUM_AMP) { + return _get_rx_gain(name, chan); + } + if (name == "all" || name == ALL_GAINS) { + return radio_control_impl::get_rx_gain(chan); + } + RFNOC_LOG_ERROR("Invalid RX gain name: " << name); + throw uhd::key_error("Invalid RX gain name!"); +} + +uhd::gain_range_t magnesium_radio_control_impl::get_tx_gain_range(const size_t chan) const +{ + if (get_tx_gain_profile(chan) == "manual") { + return meta_range_t(0.0, 0.0, 0.0); + } + return meta_range_t(ALL_TX_MIN_GAIN, ALL_TX_MAX_GAIN, ALL_TX_GAIN_STEP); +} + +uhd::gain_range_t magnesium_radio_control_impl::get_tx_gain_range( + const std::string& name, const size_t chan) const +{ + if (get_tx_gain_profile(chan) == "manual") { + if (name == "all" || name == ALL_GAINS) { + return meta_range_t(0.0, 0.0, 0.0); + } + if (name == MAGNESIUM_GAIN1) { + return meta_range_t( + AD9371_MIN_TX_GAIN, AD9371_MAX_TX_GAIN, AD9371_TX_GAIN_STEP); + } + if (name == MAGNESIUM_GAIN2) { + return meta_range_t(DSA_MIN_GAIN, DSA_MAX_GAIN, DSA_GAIN_STEP); + } + if (name == MAGNESIUM_AMP) { + return meta_range_t(AMP_MIN_GAIN, AMP_MAX_GAIN, AMP_GAIN_STEP); + } + RFNOC_LOG_ERROR("Invalid TX gain name: " << name); + throw uhd::key_error("Invalid TX gain name!"); + } + if (name == "all" || name == ALL_GAINS) { + return get_tx_gain_range(chan); + } + if (name == MAGNESIUM_GAIN1 || name == MAGNESIUM_GAIN2 || name == MAGNESIUM_AMP) { + return meta_range_t(0.0, 0.0, 0.0); + } + RFNOC_LOG_ERROR("Invalid TX gain name: " << name); + throw uhd::key_error("Invalid TX gain name!"); +} + +uhd::gain_range_t magnesium_radio_control_impl::get_rx_gain_range(const size_t chan) const +{ + if (get_rx_gain_profile(chan) == "manual") { + return meta_range_t(0.0, 0.0, 0.0); + } + return meta_range_t(ALL_RX_MIN_GAIN, ALL_RX_MAX_GAIN, ALL_RX_GAIN_STEP); +} + +uhd::gain_range_t magnesium_radio_control_impl::get_rx_gain_range( + const std::string& name, const size_t chan) const +{ + if (get_rx_gain_profile(chan) == "manual") { + if (name == "all" || name == ALL_GAINS) { + return meta_range_t(0.0, 0.0, 0.0); + } + if (name == MAGNESIUM_GAIN1) { + return meta_range_t( + AD9371_MIN_RX_GAIN, AD9371_MAX_RX_GAIN, AD9371_RX_GAIN_STEP); + } + if (name == MAGNESIUM_GAIN2) { + return meta_range_t(DSA_MIN_GAIN, DSA_MAX_GAIN, DSA_GAIN_STEP); + } + if (name == MAGNESIUM_AMP) { + return meta_range_t(AMP_MIN_GAIN, AMP_MAX_GAIN, AMP_GAIN_STEP); + } + RFNOC_LOG_ERROR("Invalid RX gain name: " << name); + throw uhd::key_error("Invalid RX gain name!"); + } + if (name == "all" || name == ALL_GAINS) { + return get_rx_gain_range(chan); + } + if (name == MAGNESIUM_GAIN1 || name == MAGNESIUM_GAIN2 || name == MAGNESIUM_AMP) { + return meta_range_t(0.0, 0.0, 0.0); + } + RFNOC_LOG_ERROR("Invalid RX gain name: " << name); + throw uhd::key_error("Invalid RX gain name!"); +} + +std::vector<std::string> magnesium_radio_control_impl::get_tx_gain_profile_names( + const size_t) const +{ + return MAGNESIUM_GP_OPTIONS; +} + +std::vector<std::string> magnesium_radio_control_impl::get_rx_gain_profile_names(const size_t ) const +{ + return MAGNESIUM_GP_OPTIONS; +} + +std::string magnesium_radio_control_impl::get_tx_gain_profile(const size_t) const +{ + return _gain_profile.at(TX_DIRECTION); +} + +std::string magnesium_radio_control_impl::get_rx_gain_profile(const size_t) const +{ + return _gain_profile.at(RX_DIRECTION); +} + +meta_range_t magnesium_radio_control_impl::get_tx_bandwidth_range(size_t) const +{ + return meta_range_t(AD9371_TX_MIN_BANDWIDTH, AD9371_TX_MAX_BANDWIDTH); +} + +meta_range_t magnesium_radio_control_impl::get_rx_bandwidth_range(size_t) const +{ + return meta_range_t(AD9371_TX_MIN_BANDWIDTH, AD9371_TX_MAX_BANDWIDTH); +} + + +/****************************************************************************** + * LO Controls + *****************************************************************************/ +std::vector<std::string> magnesium_radio_control_impl::get_rx_lo_names( + const size_t /*chan*/ + ) const +{ + return std::vector<std::string>{MAGNESIUM_LO1, MAGNESIUM_LO2}; +} + +std::vector<std::string> magnesium_radio_control_impl::get_rx_lo_sources( + const std::string& name, const size_t /*chan*/ + ) const +{ + if (name == MAGNESIUM_LO2) { + return std::vector<std::string>{"internal"}; + } else if (name == MAGNESIUM_LO1) { + return std::vector<std::string>{"internal", "external"}; + } else { + throw uhd::value_error("Could not find LO stage " + name); + } +} + +freq_range_t magnesium_radio_control_impl::get_rx_lo_freq_range( + const std::string& name, const size_t /*chan*/ +) const +{ + if (name == MAGNESIUM_LO1) { + return freq_range_t{ADF4351_MIN_FREQ, ADF4351_MAX_FREQ}; + } else if (name == MAGNESIUM_LO2) { + return freq_range_t{AD9371_MIN_FREQ, AD9371_MAX_FREQ}; + } else { + throw uhd::value_error("Could not find LO stage " + name); + } +} + +void magnesium_radio_control_impl::set_rx_lo_source( + const std::string& src, const std::string& name, const size_t /*chan*/ +) +{ + // TODO: checking what options are there + std::lock_guard<std::mutex> l(_set_lock); + RFNOC_LOG_TRACE("Setting RX LO " << name << " to " << src); + + if (name == MAGNESIUM_LO1) { + _ad9371->set_lo_source(src, RX_DIRECTION); + } else { + RFNOC_LOG_ERROR( + "RX LO " << name << " does not support setting source to " << src); + } +} + +const std::string magnesium_radio_control_impl::get_rx_lo_source( + const std::string& name, const size_t /*chan*/ +) const +{ + if (name == MAGNESIUM_LO1) { + // TODO: should we use this from cache? + return _ad9371->get_lo_source(RX_DIRECTION); + } + return "internal"; +} + +double magnesium_radio_control_impl::_set_rx_lo_freq(const std::string source, + const std::string name, + const double freq, + const size_t chan) +{ + double coerced_lo_freq = freq; + if (source != "internal") { + RFNOC_LOG_WARNING( + "LO source is not internal. This set frequency will be ignored"); + if (name == MAGNESIUM_LO1) { + // handle ad9371 external LO case + coerced_lo_freq = freq; + _ad9371_freq[RX_DIRECTION] = coerced_lo_freq; + } + } else { + if (name == MAGNESIUM_LO1) { + coerced_lo_freq = _ad9371->set_frequency(freq, chan, RX_DIRECTION); + _ad9371_freq[RX_DIRECTION] = coerced_lo_freq; + } else if (name == MAGNESIUM_LO2) { + // TODO: no hardcode the init_n_mode + coerced_lo_freq = _lo_enable(_rx_lo, freq, _master_clock_rate, false); + _adf4351_freq[RX_DIRECTION] = coerced_lo_freq; + } else { + RFNOC_LOG_WARNING("There's no LO with this name of " + << name + << " in the system. This set rx lo freq will be ignored"); + }; + } + return coerced_lo_freq; +} + +double magnesium_radio_control_impl::set_rx_lo_freq( + double freq, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("set_rx_lo_freq(freq=" << freq << ", name=" << name << ")"); + std::lock_guard<std::mutex> l(_set_lock); + std::string source = this->get_rx_lo_source(name, chan); + const double coerced_lo_freq = this->_set_rx_lo_freq(source, name, freq, chan); + this->_update_freq(chan, RX_DIRECTION); + this->_update_gain(chan, RX_DIRECTION); + return coerced_lo_freq; +} + +double magnesium_radio_control_impl::get_rx_lo_freq( + const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_rx_lo_freq(name=" << name << ")"); + std::string source = this->get_rx_lo_source(name, chan); + if (name == MAGNESIUM_LO1) { + return _ad9371_freq.at(RX_DIRECTION); + } else if (name == "adf4531") { + return _adf4351_freq.at(RX_DIRECTION); + } else { + RFNOC_LOG_ERROR("get_rx_lo_freq(): No such LO: " << name); + } + UHD_THROW_INVALID_CODE_PATH(); +} + +// TX LO +std::vector<std::string> magnesium_radio_control_impl::get_tx_lo_names( + const size_t /*chan*/ +) const +{ + return std::vector<std::string>{MAGNESIUM_LO1, MAGNESIUM_LO2}; +} + +std::vector<std::string> magnesium_radio_control_impl::get_tx_lo_sources( + const std::string& name, const size_t /*chan*/ +) const +{ + if (name == MAGNESIUM_LO2) { + return std::vector<std::string>{"internal"}; + } else if (name == MAGNESIUM_LO1) { + return std::vector<std::string>{"internal", "external"}; + } else { + throw uhd::value_error("Could not find LO stage " + name); + } +} + +freq_range_t magnesium_radio_control_impl::get_tx_lo_freq_range( + const std::string& name, const size_t /*chan*/ +) +{ + if (name == MAGNESIUM_LO2) { + return freq_range_t{ADF4351_MIN_FREQ, ADF4351_MAX_FREQ}; + } else if (name == MAGNESIUM_LO1) { + return freq_range_t{AD9371_MIN_FREQ, AD9371_MAX_FREQ}; + } else { + throw uhd::value_error("Could not find LO stage " + name); + } +} + +void magnesium_radio_control_impl::set_tx_lo_source( + const std::string& src, const std::string& name, const size_t /*chan*/ +) +{ + // TODO: checking what options are there + std::lock_guard<std::mutex> l(_set_lock); + RFNOC_LOG_TRACE("set_tx_lo_source(name=" << name << ", src=" << src << ")"); + if (name == MAGNESIUM_LO1) { + _ad9371->set_lo_source(src, TX_DIRECTION); + } else { + RFNOC_LOG_ERROR( + "TX LO " << name << " does not support setting source to " << src); + } +} + +const std::string magnesium_radio_control_impl::get_tx_lo_source( + const std::string& name, const size_t /*chan*/ +) +{ + if (name == MAGNESIUM_LO1) { + // TODO: should we use this from cache? + return _ad9371->get_lo_source(TX_DIRECTION); + } + return "internal"; +} + +double magnesium_radio_control_impl::_set_tx_lo_freq(const std::string source, + const std::string name, + const double freq, + const size_t chan) +{ + double coerced_lo_freq = freq; + if (source != "internal") { + RFNOC_LOG_WARNING( + "LO source is not internal. This set frequency will be ignored"); + if (name == MAGNESIUM_LO1) { + // handle ad9371 external LO case + coerced_lo_freq = freq; + _ad9371_freq[TX_DIRECTION] = coerced_lo_freq; + } + } else { + if (name == MAGNESIUM_LO1) { + coerced_lo_freq = _ad9371->set_frequency(freq, chan, TX_DIRECTION); + _ad9371_freq[TX_DIRECTION] = coerced_lo_freq; + } else if (name == MAGNESIUM_LO2) { + // TODO: no hardcode the int_n_mode + const bool int_n_mode = false; + coerced_lo_freq = _lo_enable(_tx_lo, freq, _master_clock_rate, int_n_mode); + _adf4351_freq[TX_DIRECTION] = coerced_lo_freq; + } else { + RFNOC_LOG_WARNING("There's no LO with this name of " + << name + << " in the system. This set tx lo freq will be ignored"); + }; + } + return coerced_lo_freq; +} + +double magnesium_radio_control_impl::set_tx_lo_freq( + double freq, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("set_tx_lo_freq(freq=" << freq << ", name=" << name << ")"); + std::string source = this->get_tx_lo_source(name, chan); + const double return_freq = this->_set_tx_lo_freq(source, name, freq, chan); + this->_update_freq(chan, TX_DIRECTION); + this->_update_gain(chan, TX_DIRECTION); + return return_freq; +} + +double magnesium_radio_control_impl::get_tx_lo_freq(const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_tx_lo_freq(name=" << name << ")"); + std::string source = this->get_tx_lo_source(name, chan); + if (name == MAGNESIUM_LO1) { + return _ad9371_freq[TX_DIRECTION]; + } else if (name == MAGNESIUM_LO2) { + return _adf4351_freq[TX_DIRECTION]; + } else { + RFNOC_LOG_ERROR("get_tx_lo_freq(): No such LO: " << name); + }; + + UHD_THROW_INVALID_CODE_PATH(); +} + +void magnesium_radio_control_impl::_remap_band_limits( + const std::string band_map, const uhd::direction_t dir) +{ + const size_t dflt_band_size = (dir == RX_DIRECTION) ? _rx_band_map.size() + : _tx_band_map.size(); + + std::vector<std::string> band_map_split; + double band_lim; + + RFNOC_LOG_DEBUG("Using user specified frequency band limits"); + boost::split(band_map_split, band_map, boost::is_any_of(";")); + if (band_map_split.size() != dflt_band_size) { + throw uhd::runtime_error(( + boost::format( + "size %s of given frequency band map doesn't match the required size: %s") + % band_map_split.size() % dflt_band_size) + .str()); + } + RFNOC_LOG_DEBUG("newly used band limits: "); + for (size_t i = 0; i < band_map_split.size(); i++) { + try { + band_lim = std::stod(band_map_split.at(i)); + } catch (...) { + throw uhd::value_error( + (boost::format("error while converting given frequency string %s " + "to a double value") + % band_map_split.at(i)) + .str()); + } + RFNOC_LOG_DEBUG("band " << i << " limit: " << band_lim << "Hz"); + if (dir == RX_DIRECTION) + _rx_band_map.at(i) = band_lim; + else + _tx_band_map.at(i) = band_lim; + } +} + + +bool magnesium_radio_control_impl::get_lo_lock_status(const direction_t dir) +{ + if (not(bool(_rpcc))) { + RFNOC_LOG_WARNING("Reported no LO lock due to lack of RPC connection."); + return false; + } + + const std::string trx = (dir == RX_DIRECTION) ? "rx" : "tx"; + const size_t chan = 0; // They're the same after all + const double freq = (dir == RX_DIRECTION) ? get_rx_frequency(chan) + : get_tx_frequency(chan); + + bool lo_lock = + _rpcc->request_with_token<bool>(_rpc_prefix + "get_ad9371_lo_lock", trx); + RFNOC_LOG_TRACE("AD9371 " << trx << " LO reports lock: " << (lo_lock ? "Yes" : "No")); + if (lo_lock and _map_freq_to_rx_band(_rx_band_map, freq) == rx_band::LOWBAND) { + lo_lock = + lo_lock + && _rpcc->request_with_token<bool>(_rpc_prefix + "get_lowband_lo_lock", trx); + RFNOC_LOG_TRACE( + "ADF4351 " << trx << " LO reports lock: " << (lo_lock ? "Yes" : "No")); + } + + return lo_lock; +} + +/************************************************************************** + * GPIO Controls + *************************************************************************/ +std::vector<std::string> magnesium_radio_control_impl::get_gpio_banks() const +{ + return {MAGNESIUM_FPGPIO_BANK}; +} + +void magnesium_radio_control_impl::set_gpio_attr( + const std::string& bank, const std::string& attr, const uint32_t value) +{ + if (bank != MAGNESIUM_FPGPIO_BANK) { + RFNOC_LOG_ERROR("Invalid GPIO bank: " << bank); + throw uhd::key_error("Invalid GPIO bank!"); + } + if (!gpio_atr::gpio_attr_rev_map.count(attr)) { + RFNOC_LOG_ERROR("Invalid GPIO attr: " << attr); + throw uhd::key_error("Invalid GPIO attr!"); + } + + const gpio_atr::gpio_attr_t gpio_attr = gpio_atr::gpio_attr_rev_map.at(attr); + + if (gpio_attr == gpio_atr::GPIO_READBACK) { + RFNOC_LOG_WARNING("Cannot set READBACK attr."); + return; + } + + _fp_gpio->set_gpio_attr(gpio_attr, value); +} + +uint32_t magnesium_radio_control_impl::get_gpio_attr( + const std::string& bank, const std::string& attr) +{ + if (bank != MAGNESIUM_FPGPIO_BANK) { + RFNOC_LOG_ERROR("Invalid GPIO bank: " << bank); + throw uhd::key_error("Invalid GPIO bank!"); + } + + return _fp_gpio->get_attr_reg(usrp::gpio_atr::gpio_attr_rev_map.at(attr)); +} + +/****************************************************************************** + * EEPROM API + *****************************************************************************/ +void magnesium_radio_control_impl::set_db_eeprom(const eeprom_map_t& db_eeprom) +{ + const size_t db_idx = get_block_id().get_block_count(); + _rpcc->notify_with_token("set_db_eeprom", db_idx, db_eeprom); +} + +eeprom_map_t magnesium_radio_control_impl::get_db_eeprom() +{ + const size_t db_idx = get_block_id().get_block_count(); + return this->_rpcc->request_with_token<eeprom_map_t>("get_db_eeprom", db_idx); +} + +/************************************************************************** + * Sensor API + *************************************************************************/ +std::vector<std::string> magnesium_radio_control_impl::get_rx_sensor_names(size_t) +{ + auto sensor_names = _rpcc->request_with_token<std::vector<std::string>>( + this->_rpc_prefix + "get_sensors", "RX"); + sensor_names.push_back("lo_locked"); + return sensor_names; +} + +sensor_value_t magnesium_radio_control_impl::get_rx_sensor(const std::string& name, size_t chan) +{ + if (name == "lo_locked") { + return sensor_value_t( + "all_los", this->get_lo_lock_status(RX_DIRECTION), "locked", "unlocked"); + } + return sensor_value_t(_rpcc->request_with_token<sensor_value_t::sensor_map_t>( + _rpc_prefix + "get_sensor", "RX", name, chan)); +} + +std::vector<std::string> magnesium_radio_control_impl::get_tx_sensor_names(size_t) +{ + auto sensor_names = _rpcc->request_with_token<std::vector<std::string>>( + this->_rpc_prefix + "get_sensors", "TX"); + sensor_names.push_back("lo_locked"); + return sensor_names; +} + +sensor_value_t magnesium_radio_control_impl::get_tx_sensor(const std::string& name, size_t chan) +{ + if (name == "lo_locked") { + return sensor_value_t( + "all_los", this->get_lo_lock_status(TX_DIRECTION), "locked", "unlocked"); + } + return sensor_value_t(_rpcc->request_with_token<sensor_value_t::sensor_map_t>( + _rpc_prefix + "get_sensor", "TX", name, chan)); +} + +/************************************************************************** + * node_t API Calls + *************************************************************************/ +void magnesium_radio_control_impl::set_command_time(uhd::time_spec_t time, const size_t chan) +{ + node_t::set_command_time(time, chan); + _wb_ifaces.at(chan)->set_time(time); +} + +/************************************************************************** + * Radio Identification API Calls + *************************************************************************/ +size_t magnesium_radio_control_impl::get_chan_from_dboard_fe( + const std::string& fe, const uhd::direction_t) const +{ + if (fe == "0") { + return 0; + } + if (fe == "1") { + return 1; + } + throw uhd::key_error(std::string("[N300] Invalid frontend: ") + fe); +} + +std::string magnesium_radio_control_impl::get_dboard_fe_from_chan( + const size_t chan, const uhd::direction_t) const +{ + if (chan == 0) { + return "0"; + } + if (chan == 1) { + return "1"; + } + throw uhd::lookup_error( + std::string("[N300] Invalid channel: ") + std::to_string(chan)); +} + +std::string magnesium_radio_control_impl::get_fe_name( + const size_t, const uhd::direction_t) const +{ + return MAGNESIUM_FE_NAME; +} + +// Register the block +UHD_RFNOC_BLOCK_REGISTER_FOR_DEVICE_DIRECT( + magnesium_radio_control, RADIO_BLOCK, N300, "Radio", true, "radio_clk", "bus_clk"); diff --git a/host/lib/usrp/dboard/magnesium/magnesium_radio_ctrl_impl.hpp b/host/lib/usrp/dboard/magnesium/magnesium_radio_control.hpp index 165e3c996..d7c721c3b 100644 --- a/host/lib/usrp/dboard/magnesium/magnesium_radio_ctrl_impl.hpp +++ b/host/lib/usrp/dboard/magnesium/magnesium_radio_control.hpp @@ -14,12 +14,14 @@ #include "magnesium_ad9371_iface.hpp" #include "magnesium_cpld_ctrl.hpp" #include "magnesium_cpld_regs.hpp" +#include <iostream> #include <uhd/types/serial.hpp> #include <uhd/usrp/dboard_manager.hpp> #include <uhd/usrp/gpio_defs.hpp> -#include <uhdlib/rfnoc/radio_ctrl_impl.hpp> -#include <uhdlib/rfnoc/rpc_block_ctrl.hpp> +#include <uhd/types/eeprom.hpp> +#include <uhdlib/rfnoc/radio_control_impl.hpp> #include <uhdlib/usrp/common/adf435x.hpp> +#include <uhdlib/usrp/common/mpmd_mb_controller.hpp> #include <uhdlib/usrp/cores/gpio_atr_3000.hpp> #include <mutex> @@ -29,13 +31,9 @@ namespace uhd { namespace rfnoc { * * This daughterboard is used on the USRP N310 and N300. */ - - -class magnesium_radio_ctrl_impl : public radio_ctrl_impl, public rpc_block_ctrl +class magnesium_radio_control_impl : public radio_control_impl { public: - typedef boost::shared_ptr<magnesium_radio_ctrl_impl> sptr; - //! Frequency bands for RX. Bands are a function of the analog filter banks enum class rx_band { INVALID_BAND, @@ -68,66 +66,109 @@ public: /************************************************************************ * Structors ***********************************************************************/ - UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR_DECL(magnesium_radio_ctrl) - virtual ~magnesium_radio_ctrl_impl(); + magnesium_radio_control_impl(make_args_ptr make_args); + virtual ~magnesium_radio_control_impl(); /************************************************************************ - * API calls + * RF API calls ***********************************************************************/ // Note: We use the cached values in radio_ctrl_impl, so most getters are // not reimplemented here double set_rate(double rate); - void set_tx_antenna(const std::string& ant, const size_t chan); - void set_rx_antenna(const std::string& ant, const size_t chan); - + // Setters + void set_tx_antenna(const std::string &ant, const size_t chan); + void set_rx_antenna(const std::string &ant, const size_t chan); double set_tx_frequency(const double freq, const size_t chan); double set_rx_frequency(const double freq, const size_t chan); - double get_rx_frequency(const size_t chan); - double get_tx_frequency(const size_t chan); + double set_tx_gain(const double gain, const size_t chan); + double set_tx_gain(const double gain, const std::string& name, const size_t chan); + double set_rx_gain(const double gain, const size_t chan); + double set_rx_gain(const double gain, const std::string& name, const size_t chan); double set_tx_bandwidth(const double bandwidth, const size_t chan); double set_rx_bandwidth(const double bandwidth, const size_t chan); + void set_tx_gain_profile(const std::string& profile, const size_t chan); + void set_rx_gain_profile(const std::string& profile, const size_t chan); + + // Getters + std::vector<std::string> get_tx_antennas(const size_t chan) const; + std::vector<std::string> get_rx_antennas(const size_t chan) const; + uhd::freq_range_t get_tx_frequency_range(const size_t chan) const; + uhd::freq_range_t get_rx_frequency_range(const size_t chan) const; + std::vector<std::string> get_tx_gain_names(const size_t) const; + std::vector<std::string> get_rx_gain_names(const size_t) const; + double get_tx_gain(const std::string&, size_t); + double get_rx_gain(const std::string&, size_t); + uhd::gain_range_t get_tx_gain_range(const size_t) const; + uhd::gain_range_t get_tx_gain_range(const std::string&, const size_t) const; + uhd::gain_range_t get_rx_gain_range(const size_t) const; + uhd::gain_range_t get_rx_gain_range(const std::string&, const size_t) const; + std::vector<std::string> get_tx_gain_profile_names(const size_t chan) const; + std::vector<std::string> get_rx_gain_profile_names(const size_t chan) const; + std::string get_tx_gain_profile(const size_t chan) const; + std::string get_rx_gain_profile(const size_t chan) const; + uhd::meta_range_t get_tx_bandwidth_range(size_t chan) const; + uhd::meta_range_t get_rx_bandwidth_range(size_t chan) const; - // RX LO - std::vector<std::string> get_rx_lo_names(const size_t chan); + /************************************************************************** + * LO Controls + *************************************************************************/ + std::vector<std::string> get_rx_lo_names(const size_t chan) const; std::vector<std::string> get_rx_lo_sources( - const std::string& name, const size_t chan); - freq_range_t get_rx_lo_freq_range(const std::string& name, const size_t chan); - + const std::string& name, const size_t chan) const; + freq_range_t get_rx_lo_freq_range(const std::string& name, const size_t chan) const; void set_rx_lo_source( const std::string& src, const std::string& name, const size_t chan); - const std::string get_rx_lo_source(const std::string& name, const size_t chan); - + const std::string get_rx_lo_source(const std::string& name, const size_t chan) const; double set_rx_lo_freq(double freq, const std::string& name, const size_t chan); double get_rx_lo_freq(const std::string& name, const size_t chan); - - // TX LO - std::vector<std::string> get_tx_lo_names(const size_t chan); + std::vector<std::string> get_tx_lo_names(const size_t chan) const; std::vector<std::string> get_tx_lo_sources( - const std::string& name, const size_t chan); + const std::string& name, const size_t chan) const; freq_range_t get_tx_lo_freq_range(const std::string& name, const size_t chan); - void set_tx_lo_source( const std::string& src, const std::string& name, const size_t chan); const std::string get_tx_lo_source(const std::string& name, const size_t chan); - - double set_tx_lo_freq(double freq, const std::string& name, const size_t chan); + double set_tx_lo_freq(const double freq, const std::string& name, const size_t chan); double get_tx_lo_freq(const std::string& name, const size_t chan); - // gain - double set_tx_gain(const double gain, const size_t chan); - double set_rx_gain(const double gain, const size_t chan); - void set_tx_gain_source( - const std::string& src, const std::string& name, const size_t chan); - std::string get_tx_gain_source(const std::string& name, const size_t chan); - void set_rx_gain_source( - const std::string& src, const std::string& name, const size_t chan); - std::string get_rx_gain_source(const std::string& name, const size_t chan); + /************************************************************************** + * GPIO Controls + *************************************************************************/ + std::vector<std::string> get_gpio_banks() const; + void set_gpio_attr( + const std::string& bank, const std::string& attr, const uint32_t value); + uint32_t get_gpio_attr(const std::string& bank, const std::string& attr); + + /************************************************************************** + * EEPROM API + *************************************************************************/ + void set_db_eeprom(const uhd::eeprom_map_t& db_eeprom); + uhd::eeprom_map_t get_db_eeprom(); + + /************************************************************************** + * Sensor API + *************************************************************************/ + std::vector<std::string> get_rx_sensor_names(size_t chan); + uhd::sensor_value_t get_rx_sensor(const std::string& name, size_t chan); + std::vector<std::string> get_tx_sensor_names(size_t chan); + uhd::sensor_value_t get_tx_sensor(const std::string& name, size_t chan); - size_t get_chan_from_dboard_fe(const std::string& fe, const direction_t dir); - std::string get_dboard_fe_from_chan(const size_t chan, const direction_t dir); + /************************************************************************** + * Radio Identification API Calls + *************************************************************************/ + std::string get_slot_name() const { return _radio_slot; } + size_t get_chan_from_dboard_fe( + const std::string& fe, const uhd::direction_t direction) const; + std::string get_dboard_fe_from_chan( + const size_t chan, const uhd::direction_t direction) const; + std::string get_fe_name( + const size_t chan, const uhd::direction_t direction) const; - void set_rpc_client(uhd::rpc_client::sptr rpcc, const uhd::device_addr_t& block_args); + /************************************************************************** + * node_t API Calls + *************************************************************************/ + void set_command_time(uhd::time_spec_t time, const size_t chan); private: /************************************************************************** @@ -157,6 +198,10 @@ private: //! 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); //! Map a frequency in Hz to an rx_band value. Will return @@ -240,12 +285,21 @@ private: //! Additional block args; gets set during set_rpc_client() uhd::device_addr_t _block_args; + //! Reference to the MB controller + mpmd_mb_controller::sptr _n310_mb_control; + + //! Reference to the MB timekeeper + uhd::rfnoc::mpmd_mb_controller::mpmd_timekeeper::sptr _n3xx_timekeeper; + //! Reference to the RPC client uhd::rpc_client::sptr _rpcc; //! Reference to the SPI core uhd::spi_iface::sptr _spi; + //! Reference to wb_iface compat adapters (one per channel) + std::vector<uhd::timed_wb_iface::sptr> _wb_ifaces; + //! Reference to the TX LO adf435x_iface::sptr _tx_lo; diff --git a/host/lib/usrp/dboard/magnesium/magnesium_radio_ctrl_cpld.cpp b/host/lib/usrp/dboard/magnesium/magnesium_radio_control_cpld.cpp index 679816af8..41f99cd68 100644 --- a/host/lib/usrp/dboard/magnesium/magnesium_radio_ctrl_cpld.cpp +++ b/host/lib/usrp/dboard/magnesium/magnesium_radio_control_cpld.cpp @@ -6,14 +6,14 @@ #include "magnesium_constants.hpp" #include "magnesium_cpld_ctrl.hpp" -#include "magnesium_radio_ctrl_impl.hpp" +#include "magnesium_radio_control.hpp" #include <uhd/utils/log.hpp> using namespace uhd; using namespace uhd::usrp; using namespace uhd::rfnoc; -void magnesium_radio_ctrl_impl::_identify_with_leds(const int identify_duration) +void magnesium_radio_control_impl::_identify_with_leds(const int identify_duration) { auto end_time = std::chrono::steady_clock::now() + std::chrono::seconds(identify_duration); @@ -36,7 +36,7 @@ void magnesium_radio_ctrl_impl::_identify_with_leds(const int identify_duration) _cpld->reset(); } -void magnesium_radio_ctrl_impl::_update_atr_switches( +void magnesium_radio_control_impl::_update_atr_switches( const magnesium_cpld_ctrl::chan_sel_t chan, const direction_t dir, const std::string& ant) @@ -51,8 +51,7 @@ void magnesium_radio_ctrl_impl::_update_atr_switches( // RX SW1. In all other cases, a TX state toggle (on to idle or vice // versa) won't trigger a change of the TRX switch. auto sw_trx = _sw_trx[chan]; - UHD_LOG_TRACE( - unique_id(), "Updating all RX-ATR related switches for antenna==" << ant); + RFNOC_LOG_TRACE("Updating all RX-ATR related switches for antenna==" << ant); if (ant == "TX/RX") { rx_sw1 = magnesium_cpld_ctrl::RX_SW1_TRXSWITCHOUTPUT; sw_trx = magnesium_cpld_ctrl::SW_TRX_RXCHANNELPATH; @@ -96,7 +95,7 @@ void magnesium_radio_ctrl_impl::_update_atr_switches( ); } if (dir == TX_DIRECTION or dir == DX_DIRECTION) { - UHD_LOG_TRACE(unique_id(), "Updating all TX-ATR related switches..."); + RFNOC_LOG_TRACE("Updating all TX-ATR related switches..."); _cpld->set_tx_atr_bits(chan, magnesium_cpld_ctrl::ON, true, /* LED on */ @@ -117,11 +116,11 @@ void magnesium_radio_ctrl_impl::_update_atr_switches( }; } -void magnesium_radio_ctrl_impl::_update_rx_freq_switches(const double freq, +void magnesium_radio_control_impl::_update_rx_freq_switches(const double freq, const bool bypass_lnas, const magnesium_cpld_ctrl::chan_sel_t chan_sel) { - UHD_LOG_TRACE(unique_id(), + RFNOC_LOG_TRACE( "Update all RX freq related switches. f=" << freq << " Hz, " "bypass LNAS: " @@ -142,7 +141,7 @@ void magnesium_radio_ctrl_impl::_update_rx_freq_switches(const double freq, not bypass_lnas and (band == rx_band::BAND4 or band == rx_band::BAND5 or band == rx_band::BAND6); const bool rx_lna2_enable = not bypass_lnas and not rx_lna1_enable; - UHD_LOG_TRACE(unique_id(), + RFNOC_LOG_TRACE( " Enabling LNA1: " << (rx_lna1_enable ? "Yes" : "No") << " Enabling LNA2: " << (rx_lna2_enable ? "Yes" : "No")); // All the defaults are OK when using the bypass path. @@ -199,7 +198,7 @@ void magnesium_radio_ctrl_impl::_update_rx_freq_switches(const double freq, rx_sw6 = magnesium_cpld_ctrl::RX_SW6_UPPERFILTERBANKFROMSWITCH4; break; case rx_band::INVALID_BAND: - UHD_LOG_ERROR(unique_id(), "Cannot map RX frequency to band: " << freq); + RFNOC_LOG_ERROR("Cannot map RX frequency to band: " << freq); break; default: UHD_THROW_INVALID_CODE_PATH(); @@ -222,16 +221,15 @@ void magnesium_radio_ctrl_impl::_update_rx_freq_switches(const double freq, enable_lowband_mixer); } -void magnesium_radio_ctrl_impl::_update_tx_freq_switches(const double freq, +void magnesium_radio_control_impl::_update_tx_freq_switches(const double freq, const bool bypass_amp, const magnesium_cpld_ctrl::chan_sel_t chan_sel) { - UHD_LOG_TRACE(unique_id(), - "Update all TX freq related switches. f=" << freq - << " Hz, " - "bypass amp: " - << (bypass_amp ? "Yes" : "No") - << ", chan=" << chan_sel); + RFNOC_LOG_TRACE("Update all TX freq related switches. f=" + << freq + << " Hz, " + "bypass amp: " + << (bypass_amp ? "Yes" : "No") << ", chan=" << chan_sel); auto tx_sw1 = magnesium_cpld_ctrl::TX_SW1_SHUTDOWNTXSW1; auto tx_sw2 = magnesium_cpld_ctrl::TX_SW2_TOTXFILTERLP6400MHZ; auto tx_sw3 = magnesium_cpld_ctrl::TX_SW3_BYPASSPATHTOTRXSW; @@ -279,7 +277,7 @@ void magnesium_radio_ctrl_impl::_update_tx_freq_switches(const double freq, tx_sw3 = magnesium_cpld_ctrl::TX_SW3_TOTXFILTERBANKS; break; case tx_band::INVALID_BAND: - UHD_LOG_ERROR(unique_id(), "Cannot map TX frequency to band: " << freq); + RFNOC_LOG_ERROR("Cannot map TX frequency to band: " << freq); break; default: UHD_THROW_INVALID_CODE_PATH(); diff --git a/host/lib/usrp/dboard/magnesium/magnesium_radio_ctrl_gain.cpp b/host/lib/usrp/dboard/magnesium/magnesium_radio_control_gain.cpp index b66bd2efd..f515b2e33 100644 --- a/host/lib/usrp/dboard/magnesium/magnesium_radio_ctrl_gain.cpp +++ b/host/lib/usrp/dboard/magnesium/magnesium_radio_control_gain.cpp @@ -6,63 +6,62 @@ #include "magnesium_constants.hpp" #include "magnesium_gain_table.hpp" -#include "magnesium_radio_ctrl_impl.hpp" -#include <uhd/exception.hpp> +#include "magnesium_radio_control.hpp" #include <uhd/utils/log.hpp> +#include <uhd/exception.hpp> using namespace uhd; using namespace uhd::usrp; using namespace uhd::rfnoc; using namespace magnesium; -double magnesium_radio_ctrl_impl::_set_all_gain( +double magnesium_radio_control_impl::_set_all_gain( const double gain, const double freq, const size_t chan, const direction_t dir) { - UHD_LOG_TRACE(unique_id(), - __func__ << "(gain=" << gain - << "dB, " - "freq=" - << freq - << " Hz, " - "chan=" - << chan - << ", " - "dir=" - << dir << ")"); + RFNOC_LOG_TRACE(__func__ << "(gain=" << gain + << "dB, " + "freq=" + << freq + << " Hz, " + "chan=" + << chan + << ", " + "dir=" + << dir); const size_t ad9371_chan = chan; auto chan_sel = static_cast<magnesium_cpld_ctrl::chan_sel_t>(chan); gain_tuple_t gain_tuple; std::string gp = _gain_profile[dir]; - UHD_LOG_TRACE(unique_id(), "Gain profile: " << gp); + RFNOC_LOG_TRACE("Gain profile: " << gp); if (gp == "manual") { - UHD_LOG_TRACE(unique_id(), "Manual gain mode. Getting gain from property tree."); + RFNOC_LOG_TRACE("Manual gain mode. Getting gain from property tree."); gain_tuple = {DSA_MAX_GAIN - _dsa_att[dir], ((dir == RX_DIRECTION) ? AD9371_MAX_RX_GAIN : AD9371_MAX_TX_GAIN) - _ad9371_att[dir], _amp_bypass[dir]}; } else if (gp.find("default") != gp.npos) { - UHD_LOG_TRACE(unique_id(), "Getting gain from gain table."); + RFNOC_LOG_TRACE("Getting gain from gain table."); gain_tuple = (dir == RX_DIRECTION) ? get_rx_gain_tuple(gain, _map_freq_to_rx_band(_rx_band_map, freq)) : get_tx_gain_tuple(gain, _map_freq_to_tx_band(_tx_band_map, freq)); if (gp == "default_rf_filter_bypass_always_on") { - UHD_LOG_TRACE(unique_id(), "Enable filter bypass for all gains"); + RFNOC_LOG_TRACE("Enable filter bypass for all gains"); gain_tuple.bypass = true; } else if (gp == "default_rf_filter_bypass_always_off") { - UHD_LOG_TRACE(unique_id(), "Disable filter bypass for all gains"); + RFNOC_LOG_TRACE("Disable filter bypass for all gains"); gain_tuple.bypass = false; } } else { - UHD_LOG_ERROR(unique_id(), "Unsupported gain mode: " << gp); + RFNOC_LOG_ERROR("Unsupported gain mode: " << gp); throw uhd::value_error( - str(boost::format("[%s] Unsupported gain mode: %s") % unique_id() % gp)); + str(boost::format("[%s] Unsupported gain mode: %s") % get_unique_id() % gp)); } const double ad9371_gain = ((dir == RX_DIRECTION) ? AD9371_MAX_RX_GAIN : AD9371_MAX_TX_GAIN) - gain_tuple.ad9371_att; - UHD_LOG_TRACE(unique_id(), + RFNOC_LOG_TRACE( "AD9371 attenuation==" << gain_tuple.ad9371_att << " dB, " "AD9371 gain==" @@ -86,10 +85,10 @@ double magnesium_radio_ctrl_impl::_set_all_gain( return gain; } -double magnesium_radio_ctrl_impl::_get_all_gain( +double magnesium_radio_control_impl::_get_all_gain( const size_t /* chan */, const direction_t dir) { - UHD_LOG_TRACE(unique_id(), "Getting all gain "); + RFNOC_LOG_TRACE("_get_all_gain()"); if (dir == RX_DIRECTION) { return _all_rx_gain; } @@ -99,11 +98,12 @@ double magnesium_radio_ctrl_impl::_get_all_gain( /****************************************************************************** * DSA Controls *****************************************************************************/ -double magnesium_radio_ctrl_impl::_dsa_set_att( +double magnesium_radio_control_impl::_dsa_set_att( const double att, const size_t chan, const direction_t dir) { - UHD_LOG_TRACE(unique_id(), - __func__ << "(att=" << att << "dB, chan=" << chan << ", dir=" << dir << ")") + RFNOC_LOG_TRACE( + __func__ << "(att=" + << "att dB, chan=" << chan << ", dir=" << dir << ")") const uint32_t dsa_val = 2 * att; _set_dsa_val(chan, dir, dsa_val); @@ -116,7 +116,7 @@ double magnesium_radio_ctrl_impl::_dsa_set_att( return att; } -double magnesium_radio_ctrl_impl::_dsa_get_att( +double magnesium_radio_control_impl::_dsa_get_att( const size_t /*chan*/, const direction_t dir) { if (dir == RX_DIRECTION) { @@ -125,21 +125,19 @@ double magnesium_radio_ctrl_impl::_dsa_get_att( return _dsa_tx_att; } -void magnesium_radio_ctrl_impl::_set_dsa_val( +void magnesium_radio_control_impl::_set_dsa_val( const size_t chan, const direction_t dir, const uint32_t dsa_val) { // The DSA register holds 12 bits. The lower 6 bits are for RX, the upper // 6 bits are for TX. if (dir == RX_DIRECTION or dir == DX_DIRECTION) { - UHD_LOG_TRACE(unique_id(), - __func__ << "(chan=" << chan << ", dir=RX" - << ", dsa_val=" << dsa_val << ")") + RFNOC_LOG_TRACE(__func__ << "(chan=" << chan << ", dir=RX" + << ", dsa_val=" << dsa_val << ")") _gpio[chan]->set_gpio_out(dsa_val, 0x003F); } if (dir == TX_DIRECTION or dir == DX_DIRECTION) { - UHD_LOG_TRACE(unique_id(), - __func__ << "(chan=" << chan << ", dir=TX" - << ", dsa_val=" << dsa_val << ")") + RFNOC_LOG_TRACE(__func__ << "(chan=" << chan << ", dir=TX" + << ", dsa_val=" << dsa_val << ")") _gpio[chan]->set_gpio_out(dsa_val << 6, 0x0FC0); } } diff --git a/host/lib/usrp/dboard/magnesium/magnesium_radio_control_init.cpp b/host/lib/usrp/dboard/magnesium/magnesium_radio_control_init.cpp new file mode 100644 index 000000000..db2ec9494 --- /dev/null +++ b/host/lib/usrp/dboard/magnesium/magnesium_radio_control_init.cpp @@ -0,0 +1,446 @@ +// +// Copyright 2017 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "magnesium_constants.hpp" +#include "magnesium_radio_control.hpp" +#include <uhd/transport/chdr.hpp> +#include <uhd/types/eeprom.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/utils/log.hpp> +#include <uhdlib/usrp/cores/spi_core_3000.hpp> +#include <uhdlib/rfnoc/reg_iface_adapter.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/algorithm/string/case_conv.hpp> +#include <boost/algorithm/string/split.hpp> +#include <boost/make_shared.hpp> +#include <string> +#include <vector> + +using namespace uhd; +using namespace uhd::rfnoc; + +namespace { + +enum slave_select_t { SEN_CPLD = 1, SEN_TX_LO = 2, SEN_RX_LO = 4, SEN_PHASE_DAC = 8 }; + +constexpr double MAGNESIUM_DEFAULT_FREQ = 2.5e9; // Hz +constexpr double MAGNESIUM_DEFAULT_BANDWIDTH = 100e6; // Hz + +} // namespace + +void magnesium_radio_control_impl::_init_defaults() +{ + RFNOC_LOG_TRACE("_init_defaults()"); + for (size_t chan = 0; chan < get_num_output_ports(); chan++) { + radio_control_impl::set_rx_frequency(MAGNESIUM_DEFAULT_FREQ, chan); + radio_control_impl::set_rx_gain(0, chan); + radio_control_impl::set_rx_antenna(MAGNESIUM_DEFAULT_RX_ANTENNA, chan); + radio_control_impl::set_rx_bandwidth(MAGNESIUM_DEFAULT_BANDWIDTH, chan); + } + + for (size_t chan = 0; chan < get_num_input_ports(); chan++) { + radio_control_impl::set_tx_frequency(MAGNESIUM_DEFAULT_FREQ, chan); + radio_control_impl::set_tx_gain(0, chan); + radio_control_impl::set_tx_antenna(MAGNESIUM_DEFAULT_TX_ANTENNA, chan); + radio_control_impl::set_tx_bandwidth(MAGNESIUM_DEFAULT_BANDWIDTH, chan); + } + + const auto block_args = get_block_args(); + if (block_args.has_key("tx_gain_profile")) { + RFNOC_LOG_INFO("Using user specified TX gain profile: " << block_args.get( + "tx_gain_profile")); + _gain_profile[TX_DIRECTION] = block_args.get("tx_gain_profile"); + } + + if (block_args.has_key("rx_gain_profile")) { + RFNOC_LOG_INFO("Using user specified RX gain profile: " << block_args.get( + "rx_gain_profile")); + _gain_profile[RX_DIRECTION] = block_args.get("rx_gain_profile"); + } + + if (block_args.has_key("rx_band_map")) { + RFNOC_LOG_INFO("Using user specified RX band limits"); + _remap_band_limits(block_args.get("rx_band_map"), RX_DIRECTION); + } + + if (block_args.has_key("tx_band_map")) { + RFNOC_LOG_INFO("Using user specified TX band limits"); + _remap_band_limits(block_args.get("tx_band_map"), TX_DIRECTION); + } +} + +void magnesium_radio_control_impl::_init_peripherals() +{ + RFNOC_LOG_TRACE("Initializing peripherals..."); + RFNOC_LOG_TRACE("Initializing SPI core..."); + _spi = spi_core_3000::make( + [this](uint32_t addr, uint32_t data){ regs().poke32(addr, data, get_command_time(0)); }, + [this](uint32_t addr){ return regs().peek32(addr, get_command_time(0)); }, + regmap::REG_SPI_W, + 8, + regmap::REG_SPI_R); + RFNOC_LOG_TRACE("Initializing CPLD..."); + RFNOC_LOG_TRACE("Creating new CPLD object..."); + spi_config_t spi_config; + spi_config.use_custom_divider = true; + spi_config.divider = 125; + spi_config.mosi_edge = spi_config_t::EDGE_RISE; + spi_config.miso_edge = spi_config_t::EDGE_FALL; + RFNOC_LOG_TRACE("Making CPLD object..."); + _cpld = std::make_shared<magnesium_cpld_ctrl>( + [this, spi_config](const uint32_t transaction) { // Write functor + this->_spi->write_spi(SEN_CPLD, spi_config, transaction, 24); + }, + [this, spi_config](const uint32_t transaction) { // Read functor + return this->_spi->read_spi(SEN_CPLD, spi_config, transaction, 24); + }); + _update_atr_switches( + magnesium_cpld_ctrl::BOTH, DX_DIRECTION, radio_control_impl::get_rx_antenna(0)); + RFNOC_LOG_TRACE("Initializing TX LO..."); + _tx_lo = adf435x_iface::make_adf4351([this]( + const std::vector<uint32_t> transactions) { + for (const uint32_t transaction : transactions) { + this->_spi->write_spi(SEN_TX_LO, spi_config_t::EDGE_RISE, transaction, 32); + } + }); + RFNOC_LOG_TRACE("Initializing RX LO..."); + _rx_lo = adf435x_iface::make_adf4351([this]( + const std::vector<uint32_t> transactions) { + for (const uint32_t transaction : transactions) { + this->_spi->write_spi(SEN_RX_LO, spi_config_t::EDGE_RISE, transaction, 32); + } + }); + + _gpio.clear(); // Following the as-if rule, this can get optimized out + for (size_t radio_idx = 0; radio_idx < get_num_input_ports(); radio_idx++) { + _wb_ifaces.push_back(RFNOC_MAKE_WB_IFACE(0, radio_idx)); + RFNOC_LOG_TRACE("Initializing GPIOs for channel " << radio_idx); + _gpio.emplace_back(usrp::gpio_atr::gpio_atr_3000::make( + _wb_ifaces.back(), + n310_regs::DB_GPIO_BASE + radio_idx * n310_regs::DB_GPIO_OFFSET, + n310_regs::DB_GPIO_RB + radio_idx * n310_regs::DB_GPIO_OFFSET)); + // DSA and AD9371 gain bits do *not* toggle on ATR modes. If we ever + // connect anything else to this core, we might need to set_atr_mode() + // to MODE_ATR on those bits. For now, all bits simply do what they're + // told, and don't toggle on RX/TX state changes. + _gpio.back()->set_atr_mode(usrp::gpio_atr::MODE_GPIO, // Disable ATR mode + usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL); + _gpio.back()->set_gpio_ddr(usrp::gpio_atr::DDR_OUTPUT, // Make all GPIOs outputs + usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL); + } + RFNOC_LOG_TRACE("Initializing front-panel GPIO control...") + _fp_gpio = usrp::gpio_atr::gpio_atr_3000::make(_wb_ifaces.front(), + n310_regs::FP_GPIO, n310_regs::RB_FP_GPIO); +} + +void magnesium_radio_control_impl::_init_frontend_subtree( + uhd::property_tree::sptr subtree, const size_t chan_idx) +{ + const fs_path tx_fe_path = fs_path("tx_frontends") / chan_idx; + const fs_path rx_fe_path = fs_path("rx_frontends") / chan_idx; + RFNOC_LOG_TRACE("Adding non-RFNoC block properties for channel " + << chan_idx << " to prop tree path " << tx_fe_path << " and " + << rx_fe_path); + // TX Standard attributes + subtree->create<std::string>(tx_fe_path / "name").set(get_fe_name(chan_idx, TX_DIRECTION)); + subtree->create<std::string>(tx_fe_path / "connection").set("IQ"); + // RX Standard attributes + subtree->create<std::string>(rx_fe_path / "name").set(get_fe_name(chan_idx, RX_DIRECTION)); + subtree->create<std::string>(rx_fe_path / "connection").set("IQ"); + // TX Antenna + subtree->create<std::string>(tx_fe_path / "antenna" / "value") + .add_coerced_subscriber([this, chan_idx](const std::string& ant) { + this->set_tx_antenna(ant, chan_idx); + }) + .set_publisher([this, chan_idx]() { return this->get_tx_antenna(chan_idx); }); + subtree->create<std::vector<std::string>>(tx_fe_path / "antenna" / "options") + .set_publisher([this](){ return get_tx_antennas(0); }) + .add_coerced_subscriber([](const std::vector<std::string>&) { + throw uhd::runtime_error("Attempting to update antenna options!"); + }); + // RX Antenna + subtree->create<std::string>(rx_fe_path / "antenna" / "value") + .add_coerced_subscriber([this, chan_idx](const std::string& ant) { + this->set_rx_antenna(ant, chan_idx); + }) + .set_publisher([this, chan_idx]() { return this->get_rx_antenna(chan_idx); }); + subtree->create<std::vector<std::string>>(rx_fe_path / "antenna" / "options") + .set_publisher([this](){ return get_rx_antennas(0); }) + .add_coerced_subscriber([](const std::vector<std::string>&) { + throw uhd::runtime_error("Attempting to update antenna options!"); + }); + // TX frequency + subtree->create<double>(tx_fe_path / "freq" / "value") + .set_coercer([this, chan_idx](const double freq) { + return this->set_tx_frequency(freq, chan_idx); + }) + .set_publisher([this, chan_idx]() { return this->get_tx_frequency(chan_idx); }); + subtree->create<meta_range_t>(tx_fe_path / "freq" / "range") + .set_publisher([this, chan_idx](){ return get_tx_frequency_range(chan_idx); }) + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update freq range!"); + }); + // RX frequency + subtree->create<double>(rx_fe_path / "freq" / "value") + .set_coercer([this, chan_idx](const double freq) { + return this->set_rx_frequency(freq, chan_idx); + }) + .set_publisher([this, chan_idx]() { return this->get_rx_frequency(chan_idx); }); + subtree->create<meta_range_t>(rx_fe_path / "freq" / "range") + .set_publisher([this, chan_idx](){ return get_rx_frequency_range(chan_idx); }) + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update freq range!"); + }); + // TX bandwidth + subtree->create<double>(tx_fe_path / "bandwidth" / "value") + .set(AD9371_TX_MAX_BANDWIDTH) + .set_coercer([this, chan_idx](const double bw) { + return this->set_tx_bandwidth(bw, chan_idx); + }) + .set_publisher([this, chan_idx]() { return this->get_tx_bandwidth(chan_idx); }); + subtree->create<meta_range_t>(tx_fe_path / "bandwidth" / "range") + .set_publisher([this, chan_idx](){ return get_tx_bandwidth_range(chan_idx); }) + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update bandwidth range!"); + }); + // RX bandwidth + subtree->create<double>(rx_fe_path / "bandwidth" / "value") + .set(AD9371_RX_MAX_BANDWIDTH) + .set_coercer([this, chan_idx](const double bw) { + return this->set_rx_bandwidth(bw, chan_idx); + }) + .set_publisher([this, chan_idx]() { return this->get_rx_bandwidth(chan_idx); }); + subtree->create<meta_range_t>(rx_fe_path / "bandwidth" / "range") + .set_publisher([this, chan_idx](){ return get_rx_bandwidth_range(chan_idx); }) + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update bandwidth range!"); + }); + + // TX gains + std::vector<std::string> tx_gain_names = get_tx_gain_names(chan_idx); + tx_gain_names.push_back("all"); + for (const auto gain_name : tx_gain_names) { + subtree->create<double>(tx_fe_path / "gains" / gain_name / "value") + .set_coercer([this, chan_idx, gain_name](const double gain) { + return this->set_tx_gain(gain, gain_name, chan_idx); + }) + .set_publisher( + [this, chan_idx, gain_name]() { return get_tx_gain(gain_name, chan_idx); }); + subtree->create<meta_range_t>(tx_fe_path / "gains" / gain_name / "range") + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update gain range!"); + }) + .set_publisher([this, gain_name, chan_idx]() { return get_tx_gain_range(gain_name, chan_idx); }); + } + subtree->create<std::vector<std::string>>(tx_fe_path / "gains/all/profile/options") + .set_publisher( + [this, chan_idx]() { return get_tx_gain_profile_names(chan_idx); }); + subtree->create<std::string>(tx_fe_path / "gains/all/profile/value") + .set_coercer([this, chan_idx](const std::string& profile) { + set_tx_gain_profile(profile, chan_idx); + return profile; + }) + .set_publisher([this, chan_idx]() { return get_tx_gain_profile(chan_idx); }); + + // RX gains + std::vector<std::string> rx_gain_names = get_rx_gain_names(chan_idx); + rx_gain_names.push_back("all"); + for (const auto gain_name : rx_gain_names) { + subtree->create<double>(rx_fe_path / "gains" / gain_name / "value") + .set_coercer([this, chan_idx, gain_name](const double gain) { + return this->set_rx_gain(gain, gain_name, chan_idx); + }) + .set_publisher( + [this, chan_idx, gain_name]() { return get_rx_gain(gain_name, chan_idx); }); + subtree->create<meta_range_t>(rx_fe_path / "gains" / gain_name / "range") + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update gain range!"); + }) + .set_publisher([this, gain_name, chan_idx]() { return get_rx_gain_range(gain_name, chan_idx); }); + } + subtree->create<std::vector<std::string>>(rx_fe_path / "gains/all/profile/options") + .set_publisher( + [this, chan_idx]() { return get_rx_gain_profile_names(chan_idx); }); + subtree->create<std::string>(rx_fe_path / "gains/all/profile/value") + .set_coercer([this, chan_idx](const std::string& profile) { + set_rx_gain_profile(profile, chan_idx); + return profile; + }) + .set_publisher([this, chan_idx]() { return get_rx_gain_profile(chan_idx); }); + + // LO Specific + // RX LO + subtree->create<meta_range_t>(rx_fe_path / "los" / MAGNESIUM_LO1 / "freq/range") + .set_publisher([this, chan_idx]() { + return this->get_rx_lo_freq_range(MAGNESIUM_LO1, chan_idx); + }); + subtree + ->create<std::vector<std::string>>( + rx_fe_path / "los" / MAGNESIUM_LO1 / "source/options") + .set_publisher([this, chan_idx]() { + return this->get_rx_lo_sources(MAGNESIUM_LO1, chan_idx); + }); + subtree->create<std::string>(rx_fe_path / "los" / MAGNESIUM_LO1 / "source/value") + .add_coerced_subscriber([this, chan_idx](std::string src) { + this->set_rx_lo_source(src, MAGNESIUM_LO1, chan_idx); + }) + .set_publisher([this, chan_idx]() { + return this->get_rx_lo_source(MAGNESIUM_LO1, chan_idx); + }); + subtree->create<double>(rx_fe_path / "los" / MAGNESIUM_LO1 / "freq/value") + .set_publisher( + [this, chan_idx]() { return this->get_rx_lo_freq(MAGNESIUM_LO1, chan_idx); }) + .set_coercer([this, chan_idx](const double freq) { + return this->set_rx_lo_freq(freq, MAGNESIUM_LO1, chan_idx); + }); + subtree->create<meta_range_t>(rx_fe_path / "los" / MAGNESIUM_LO2 / "freq/range") + .set_publisher([this, chan_idx]() { + return this->get_rx_lo_freq_range(MAGNESIUM_LO2, chan_idx); + }); + subtree + ->create<std::vector<std::string>>( + rx_fe_path / "los" / MAGNESIUM_LO2 / "source/options") + .set_publisher([this, chan_idx]() { + return this->get_rx_lo_sources(MAGNESIUM_LO2, chan_idx); + }); + + subtree->create<std::string>(rx_fe_path / "los" / MAGNESIUM_LO2 / "source/value") + .add_coerced_subscriber([this, chan_idx](std::string src) { + this->set_rx_lo_source(src, MAGNESIUM_LO2, chan_idx); + }) + .set_publisher([this, chan_idx]() { + return this->get_rx_lo_source(MAGNESIUM_LO2, chan_idx); + }); + subtree->create<double>(rx_fe_path / "los" / MAGNESIUM_LO2 / "freq/value") + .set_publisher( + [this, chan_idx]() { return this->get_rx_lo_freq(MAGNESIUM_LO2, chan_idx); }) + .set_coercer([this, chan_idx](double freq) { + return this->set_rx_lo_freq(freq, MAGNESIUM_LO2, chan_idx); + }); + // TX LO + subtree->create<meta_range_t>(tx_fe_path / "los" / MAGNESIUM_LO1 / "freq/range") + .set_publisher([this, chan_idx]() { + return this->get_rx_lo_freq_range(MAGNESIUM_LO1, chan_idx); + }); + subtree + ->create<std::vector<std::string>>( + tx_fe_path / "los" / MAGNESIUM_LO1 / "source/options") + .set_publisher([this, chan_idx]() { + return this->get_tx_lo_sources(MAGNESIUM_LO1, chan_idx); + }); + subtree->create<std::string>(tx_fe_path / "los" / MAGNESIUM_LO1 / "source/value") + .add_coerced_subscriber([this, chan_idx](std::string src) { + this->set_tx_lo_source(src, MAGNESIUM_LO1, chan_idx); + }) + .set_publisher([this, chan_idx]() { + return this->get_tx_lo_source(MAGNESIUM_LO1, chan_idx); + }); + subtree->create<double>(tx_fe_path / "los" / MAGNESIUM_LO1 / "freq/value ") + .set_publisher( + [this, chan_idx]() { return this->get_tx_lo_freq(MAGNESIUM_LO1, chan_idx); }) + .set_coercer([this, chan_idx](double freq) { + return this->set_tx_lo_freq(freq, MAGNESIUM_LO1, chan_idx); + }); + subtree->create<meta_range_t>(tx_fe_path / "los" / MAGNESIUM_LO2 / "freq/range") + .set_publisher([this, chan_idx]() { + return this->get_tx_lo_freq_range(MAGNESIUM_LO2, chan_idx); + }); + subtree + ->create<std::vector<std::string>>( + tx_fe_path / "los" / MAGNESIUM_LO2 / "source/options") + .set_publisher([this, chan_idx]() { + return this->get_tx_lo_sources(MAGNESIUM_LO2, chan_idx); + }); + subtree->create<std::string>(tx_fe_path / "los" / MAGNESIUM_LO2 / "source/value") + .add_coerced_subscriber([this, chan_idx](std::string src) { + this->set_tx_lo_source(src, MAGNESIUM_LO2, chan_idx); + }) + .set_publisher([this, chan_idx]() { + return this->get_tx_lo_source(MAGNESIUM_LO2, chan_idx); + }); + subtree->create<double>(tx_fe_path / "los" / MAGNESIUM_LO2 / "freq/value") + .set_publisher( + [this, chan_idx]() { return this->get_tx_lo_freq(MAGNESIUM_LO2, chan_idx); }) + .set_coercer([this, chan_idx](double freq) { + return this->set_tx_lo_freq(freq, MAGNESIUM_LO2, chan_idx); + }); + + // Sensors + auto rx_sensor_names = get_rx_sensor_names(chan_idx); + for (const auto& sensor_name : rx_sensor_names) { + RFNOC_LOG_TRACE("Adding RX sensor " << sensor_name); + get_tree()->create<sensor_value_t>(rx_fe_path / "sensors" / sensor_name) + .add_coerced_subscriber([](const sensor_value_t&) { + throw uhd::runtime_error("Attempting to write to sensor!"); + }) + .set_publisher([this, sensor_name, chan_idx]() { + return get_rx_sensor(sensor_name, chan_idx); + }); + } + auto tx_sensor_names = get_tx_sensor_names(chan_idx); + for (const auto& sensor_name : tx_sensor_names) { + RFNOC_LOG_TRACE("Adding TX sensor " << sensor_name); + get_tree()->create<sensor_value_t>(tx_fe_path / "sensors" / sensor_name) + .add_coerced_subscriber([](const sensor_value_t&) { + throw uhd::runtime_error("Attempting to write to sensor!"); + }) + .set_publisher([this, sensor_name, chan_idx]() { + return get_tx_sensor(sensor_name, chan_idx); + }); + } +} + +void magnesium_radio_control_impl::_init_prop_tree() +{ + for (size_t chan_idx = 0; chan_idx < MAGNESIUM_NUM_CHANS; chan_idx++) { + this->_init_frontend_subtree(get_tree()->subtree(DB_PATH), chan_idx); + } + + // DB EEPROM + get_tree()->create<eeprom_map_t>("eeprom") + .add_coerced_subscriber( + [this](const eeprom_map_t& db_eeprom) { set_db_eeprom(db_eeprom); }) + .set_publisher([this]() { return get_db_eeprom(); }); +} + +void magnesium_radio_control_impl::_init_mpm() +{ + auto block_args = get_block_args(); + RFNOC_LOG_TRACE("Instantiating AD9371 control object..."); + _ad9371 = magnesium_ad9371_iface::uptr( + new magnesium_ad9371_iface(_rpcc, (_radio_slot == "A") ? 0 : 1)); + + if (block_args.has_key("identify")) { + const std::string identify_val = block_args.get("identify"); + int identify_duration = std::atoi(identify_val.c_str()); + if (identify_duration == 0) { + identify_duration = 5; + } + RFNOC_LOG_INFO("Running LED identification process for " << identify_duration + << " seconds."); + _identify_with_leds(identify_duration); + } + + // Note: MCR gets set during the init() call (prior to this), which takes + // in arguments from the device args. So if block_args contains a + // master_clock_rate key, then it should better be whatever the device is + // configured to do. + _master_clock_rate = + _rpcc->request_with_token<double>(_rpc_prefix + "get_master_clock_rate"); + if (block_args.cast<double>("master_clock_rate", _master_clock_rate) + != _master_clock_rate) { + throw uhd::runtime_error(str( + boost::format("Master clock rate mismatch. Device returns %f MHz, " + "but should have been %f MHz.") + % (_master_clock_rate / 1e6) + % (block_args.cast<double>("master_clock_rate", _master_clock_rate) / 1e6))); + } + RFNOC_LOG_DEBUG("Master Clock Rate is: " << (_master_clock_rate / 1e6) << " MHz."); + set_tick_rate(_master_clock_rate); + _n3xx_timekeeper->update_tick_rate(_master_clock_rate); + radio_control_impl::set_rate(_master_clock_rate); +} + diff --git a/host/lib/usrp/dboard/magnesium/magnesium_radio_ctrl_impl.cpp b/host/lib/usrp/dboard/magnesium/magnesium_radio_ctrl_impl.cpp deleted file mode 100644 index 405d5955e..000000000 --- a/host/lib/usrp/dboard/magnesium/magnesium_radio_ctrl_impl.cpp +++ /dev/null @@ -1,847 +0,0 @@ -// -// Copyright 2017 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "magnesium_radio_ctrl_impl.hpp" -#include "magnesium_constants.hpp" -#include "magnesium_gain_table.hpp" -#include <uhd/exception.hpp> -#include <uhd/rfnoc/node_ctrl_base.hpp> -#include <uhd/transport/chdr.hpp> -#include <uhd/types/direction.hpp> -#include <uhd/types/eeprom.hpp> -#include <uhd/utils/algorithm.hpp> -#include <uhd/utils/log.hpp> -#include <uhd/utils/math.hpp> -#include <boost/algorithm/string.hpp> -#include <boost/format.hpp> -#include <boost/make_shared.hpp> -#include <cmath> -#include <cstdlib> -#include <sstream> - -using namespace uhd; -using namespace uhd::usrp; -using namespace uhd::rfnoc; -using namespace uhd::math::fp_compare; - -namespace { -/************************************************************************** - * ADF4351 Controls - *************************************************************************/ -/*! - * \param lo_iface Reference to the LO object - * \param freq Frequency (in Hz) of the tone to be generated from the LO - * \param ref_clock_freq Frequency (in Hz) of the reference clock at the - * PLL input of the LO - * \param int_n_mode Integer-N mode on or off - */ -double _lo_set_frequency(adf435x_iface::sptr lo_iface, - const double freq, - const double ref_clock_freq, - const bool int_n_mode) -{ - UHD_LOG_TRACE("MG/ADF4351", - "Attempting to tune low band LO to " << freq << " Hz with ref clock freq " - << ref_clock_freq); - lo_iface->set_feedback_select(adf435x_iface::FB_SEL_DIVIDED); - lo_iface->set_reference_freq(ref_clock_freq); - lo_iface->set_prescaler(adf435x_iface::PRESCALER_4_5); - const double actual_freq = lo_iface->set_frequency(freq, int_n_mode); - lo_iface->set_output_power( - adf435x_iface::RF_OUTPUT_A, adf435x_iface::OUTPUT_POWER_2DBM); - lo_iface->set_output_power( - adf435x_iface::RF_OUTPUT_B, adf435x_iface::OUTPUT_POWER_2DBM); - lo_iface->set_charge_pump_current(adf435x_iface::CHARGE_PUMP_CURRENT_0_31MA); - return actual_freq; -} - -/*! Configure and enable LO - * - * Will tune it to requested frequency and enable outputs. - * - * \param lo_iface Reference to the LO object - * \param lo_freq Frequency (in Hz) of the tone to be generated from the LO - * \param ref_clock_freq Frequency (in Hz) of the reference clock at the - * PLL input of the LO - * \param int_n_mode Integer-N mode on or off - * \returns the actual frequency the LO is running at - */ -double _lo_enable(adf435x_iface::sptr lo_iface, - const double lo_freq, - const double ref_clock_freq, - const bool int_n_mode) -{ - const double actual_lo_freq = - _lo_set_frequency(lo_iface, lo_freq, ref_clock_freq, int_n_mode); - lo_iface->set_output_enable(adf435x_iface::RF_OUTPUT_A, true); - lo_iface->set_output_enable(adf435x_iface::RF_OUTPUT_B, true); - lo_iface->commit(); - return actual_lo_freq; -} - -/*! Disable LO - */ -void _lo_disable(adf435x_iface::sptr lo_iface) -{ - lo_iface->set_output_enable(adf435x_iface::RF_OUTPUT_A, false); - lo_iface->set_output_enable(adf435x_iface::RF_OUTPUT_B, false); - lo_iface->commit(); -} -} // namespace - - -/****************************************************************************** - * Structors - *****************************************************************************/ -UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR(magnesium_radio_ctrl) -{ - UHD_LOG_TRACE(unique_id(), "Entering magnesium_radio_ctrl_impl ctor..."); - const char radio_slot_name[2] = {'A', 'B'}; - _radio_slot = radio_slot_name[get_block_id().get_block_count()]; - UHD_LOG_TRACE(unique_id(), "Radio slot: " << _radio_slot); - _rpc_prefix = (_radio_slot == "A") ? "db_0_" : "db_1_"; - - _init_defaults(); - _init_peripherals(); - _init_prop_tree(); -} - -magnesium_radio_ctrl_impl::~magnesium_radio_ctrl_impl() -{ - UHD_LOG_TRACE(unique_id(), "magnesium_radio_ctrl_impl::dtor() "); -} - - -/****************************************************************************** - * API Calls - *****************************************************************************/ -double magnesium_radio_ctrl_impl::set_rate(double requested_rate) -{ - meta_range_t rates; - for (const double rate : MAGNESIUM_RADIO_RATES) { - rates.push_back(range_t(rate)); - } - - const double rate = rates.clip(requested_rate); - if (!math::frequencies_are_equal(requested_rate, rate)) { - UHD_LOG_WARNING(unique_id(), - "Coercing requested sample rate from " << (requested_rate / 1e6) << " to " - << (rate / 1e6)); - } - - const double current_rate = get_rate(); - if (math::frequencies_are_equal(current_rate, rate)) { - UHD_LOG_DEBUG( - unique_id(), "Rate is already at " << rate << ". Skipping set_rate()"); - return current_rate; - } - - std::lock_guard<std::mutex> l(_set_lock); - // Now commit to device. First, disable LOs. - _lo_disable(_tx_lo); - _lo_disable(_rx_lo); - const double new_rate = _ad9371->set_master_clock_rate(rate); - // Frequency settings apply to both channels, no loop needed. Will also - // re-enable the lowband LOs if they were used. - set_rx_frequency(get_rx_frequency(0), 0); - set_tx_frequency(get_tx_frequency(0), 0); - // Gain and bandwidth need to be looped: - for (size_t radio_idx = 0; radio_idx < _get_num_radios(); radio_idx++) { - set_rx_gain(get_rx_gain(radio_idx), radio_idx); - set_tx_gain(get_rx_gain(radio_idx), radio_idx); - set_rx_bandwidth(get_rx_bandwidth(radio_idx), radio_idx); - set_tx_bandwidth(get_tx_bandwidth(radio_idx), radio_idx); - } - radio_ctrl_impl::set_rate(new_rate); - return new_rate; -} - -void magnesium_radio_ctrl_impl::set_tx_antenna(const std::string& ant, const size_t chan) -{ - if (ant != get_tx_antenna(chan)) { - throw uhd::value_error( - str(boost::format("[%s] Requesting invalid TX antenna value: %s") - % unique_id() % ant)); - } - // We can't actually set the TX antenna, so let's stop here. -} - -void magnesium_radio_ctrl_impl::set_rx_antenna(const std::string& ant, const size_t chan) -{ - UHD_ASSERT_THROW(chan <= MAGNESIUM_NUM_CHANS); - if (std::find(MAGNESIUM_RX_ANTENNAS.begin(), MAGNESIUM_RX_ANTENNAS.end(), ant) - == MAGNESIUM_RX_ANTENNAS.end()) { - throw uhd::value_error( - str(boost::format("[%s] Requesting invalid RX antenna value: %s") - % unique_id() % ant)); - } - UHD_LOG_TRACE(unique_id(), "Setting RX antenna to " << ant << " for chan " << chan); - magnesium_cpld_ctrl::chan_sel_t chan_sel = chan == 0 ? magnesium_cpld_ctrl::CHAN1 - : magnesium_cpld_ctrl::CHAN2; - _update_atr_switches(chan_sel, RX_DIRECTION, ant); - - radio_ctrl_impl::set_rx_antenna(ant, chan); -} - -double magnesium_radio_ctrl_impl::set_tx_frequency( - const double req_freq, const size_t chan) -{ - const double freq = MAGNESIUM_FREQ_RANGE.clip(req_freq); - UHD_LOG_TRACE(unique_id(), "set_tx_frequency(f=" << freq << ", chan=" << chan << ")"); - _desired_rf_freq[TX_DIRECTION] = freq; - std::lock_guard<std::mutex> l(_set_lock); - // We need to set the switches on both channels, because they share an LO. - // This way, if we tune channel 0 it will not put channel 1 into a bad - // state. - _update_tx_freq_switches(freq, _tx_bypass_amp, magnesium_cpld_ctrl::BOTH); - const std::string ad9371_source = this->get_tx_lo_source(MAGNESIUM_LO1, chan); - const std::string adf4351_source = this->get_tx_lo_source(MAGNESIUM_LO2, chan); - UHD_ASSERT_THROW(adf4351_source == "internal"); - double coerced_if_freq = freq; - - if (_map_freq_to_tx_band(_tx_band_map, freq) == tx_band::LOWBAND) { - _is_low_band[TX_DIRECTION] = true; - const double desired_low_freq = MAGNESIUM_TX_IF_FREQ - freq; - coerced_if_freq = - this->_set_tx_lo_freq(adf4351_source, MAGNESIUM_LO2, desired_low_freq, chan) - + freq; - UHD_LOG_TRACE(unique_id(), "coerced_if_freq = " << coerced_if_freq); - } else { - _is_low_band[TX_DIRECTION] = false; - _lo_disable(_tx_lo); - } - // external LO required to tune at 2xdesired_frequency. - const double desired_if_freq = (ad9371_source == "internal") ? coerced_if_freq - : 2 * coerced_if_freq; - - this->_set_tx_lo_freq(ad9371_source, MAGNESIUM_LO1, desired_if_freq, chan); - this->_update_freq(chan, TX_DIRECTION); - this->_update_gain(chan, TX_DIRECTION); - return radio_ctrl_impl::get_tx_frequency(chan); -} - -void magnesium_radio_ctrl_impl::_update_gain( - const size_t chan, const uhd::direction_t dir) -{ - const std::string fe = (dir == TX_DIRECTION) ? "tx_frontends" : "rx_frontends"; - const double freq = (dir == TX_DIRECTION) ? this->get_tx_frequency(chan) - : this->get_rx_frequency(chan); - this->_set_all_gain(this->_get_all_gain(chan, dir), freq, chan, dir); -} - -void magnesium_radio_ctrl_impl::_update_freq( - const size_t chan, const uhd::direction_t dir) -{ - const std::string ad9371_source = dir == TX_DIRECTION - ? this->get_tx_lo_source(MAGNESIUM_LO1, chan) - : this->get_rx_lo_source(MAGNESIUM_LO1, chan); - - const double ad9371_freq = ad9371_source == "external" ? _ad9371_freq[dir] / 2 - : _ad9371_freq[dir]; - const double rf_freq = _is_low_band[dir] ? ad9371_freq - _adf4351_freq[dir] - : ad9371_freq; - - UHD_LOG_TRACE(unique_id(), "RF freq = " << rf_freq); - UHD_ASSERT_THROW(fp_compare_epsilon<double>(rf_freq) >= 0); - UHD_ASSERT_THROW(fp_compare_epsilon<double>(std::abs(rf_freq - _desired_rf_freq[dir])) - <= _master_clock_rate / 2); - if (dir == RX_DIRECTION) { - radio_ctrl_impl::set_rx_frequency(rf_freq, chan); - } else if (dir == TX_DIRECTION) { - radio_ctrl_impl::set_tx_frequency(rf_freq, chan); - } else { - UHD_THROW_INVALID_CODE_PATH(); - } -} - -double magnesium_radio_ctrl_impl::set_rx_frequency( - const double req_freq, const size_t chan) -{ - const double freq = MAGNESIUM_FREQ_RANGE.clip(req_freq); - UHD_LOG_TRACE(unique_id(), "set_rx_frequency(f=" << freq << ", chan=" << chan << ")"); - _desired_rf_freq[RX_DIRECTION] = freq; - std::lock_guard<std::mutex> l(_set_lock); - // We need to set the switches on both channels, because they share an LO. - // This way, if we tune channel 0 it will not put channel 1 into a bad - // state. - _update_rx_freq_switches(freq, _rx_bypass_lnas, magnesium_cpld_ctrl::BOTH); - const std::string ad9371_source = this->get_rx_lo_source(MAGNESIUM_LO1, chan); - const std::string adf4351_source = this->get_rx_lo_source(MAGNESIUM_LO2, chan); - UHD_ASSERT_THROW(adf4351_source == "internal"); - double coerced_if_freq = freq; - - if (_map_freq_to_rx_band(_rx_band_map, freq) == rx_band::LOWBAND) { - _is_low_band[RX_DIRECTION] = true; - const double desired_low_freq = MAGNESIUM_RX_IF_FREQ - freq; - coerced_if_freq = - this->_set_rx_lo_freq(adf4351_source, MAGNESIUM_LO2, desired_low_freq, chan) - + freq; - UHD_LOG_TRACE(unique_id(), "coerced_if_freq = " << coerced_if_freq); - } else { - _is_low_band[RX_DIRECTION] = false; - _lo_disable(_rx_lo); - } - // external LO required to tune at 2xdesired_frequency. - const double desired_if_freq = ad9371_source == "internal" ? coerced_if_freq - : 2 * coerced_if_freq; - - this->_set_rx_lo_freq(ad9371_source, MAGNESIUM_LO1, desired_if_freq, chan); - - this->_update_freq(chan, RX_DIRECTION); - this->_update_gain(chan, RX_DIRECTION); - - return radio_ctrl_impl::get_rx_frequency(chan); -} - -double magnesium_radio_ctrl_impl::get_tx_frequency(const size_t chan) -{ - UHD_LOG_TRACE(unique_id(), "get_tx_frequency(chan=" << chan << ")"); - return radio_ctrl_impl::get_tx_frequency(chan); -} - -double magnesium_radio_ctrl_impl::get_rx_frequency(const size_t chan) -{ - UHD_LOG_TRACE(unique_id(), "get_rx_frequency(chan=" << chan << ")"); - return radio_ctrl_impl::get_rx_frequency(chan); -} -double magnesium_radio_ctrl_impl::set_rx_bandwidth( - const double bandwidth, const size_t chan) -{ - std::lock_guard<std::mutex> l(_set_lock); - _ad9371->set_bandwidth(bandwidth, chan, RX_DIRECTION); - // FIXME: setting analog bandwidth on AD9371 take no effect. - // Remove this warning when ADI can confirm that it works. - UHD_LOG_WARNING(unique_id(), - "set_rx_bandwidth take no effect on AD9371. " - "Default analog bandwidth is 100MHz"); - return AD9371_RX_MAX_BANDWIDTH; -} - -double magnesium_radio_ctrl_impl::set_tx_bandwidth( - const double bandwidth, const size_t chan) -{ - std::lock_guard<std::mutex> l(_set_lock); - _ad9371->set_bandwidth(bandwidth, chan, TX_DIRECTION); - // FIXME: setting analog bandwidth on AD9371 take no effect. - // Remove this warning when ADI can confirm that it works. - UHD_LOG_WARNING(unique_id(), - "set_tx_bandwidth take no effect on AD9371. " - "Default analog bandwidth is 100MHz"); - return AD9371_TX_MAX_BANDWIDTH; -} - -double magnesium_radio_ctrl_impl::set_tx_gain(const double gain, const size_t chan) -{ - std::lock_guard<std::mutex> l(_set_lock); - UHD_LOG_TRACE(unique_id(), "set_tx_gain(gain=" << gain << ", chan=" << chan << ")"); - const double coerced_gain = - _set_all_gain(gain, this->get_tx_frequency(chan), chan, TX_DIRECTION); - radio_ctrl_impl::set_tx_gain(coerced_gain, chan); - return coerced_gain; -} - -double magnesium_radio_ctrl_impl::_set_tx_gain( - const std::string& name, const double gain, const size_t chan) -{ - std::lock_guard<std::mutex> l(_set_lock); - UHD_LOG_TRACE(unique_id(), - "_set_tx_gain(name=" << name << ", gain=" << gain << ", chan=" << chan << ")"); - UHD_LOG_TRACE(unique_id(), - "_set_tx_gain(name=" << name << ", gain=" << gain << ", chan=" << chan << ")"); - double clip_gain = 0; - if (name == MAGNESIUM_GAIN1) { - clip_gain = uhd::clip(gain, AD9371_MIN_TX_GAIN, AD9371_MAX_TX_GAIN); - _ad9371_att[TX_DIRECTION] = clip_gain; - } else if (name == MAGNESIUM_GAIN2) { - clip_gain = uhd::clip(gain, DSA_MIN_GAIN, DSA_MAX_GAIN); - _dsa_att[TX_DIRECTION] = clip_gain; - } else if (name == MAGNESIUM_AMP) { - clip_gain = gain > 0.0 ? AMP_MAX_GAIN : AMP_MIN_GAIN; - _amp_bypass[TX_DIRECTION] = clip_gain == 0.0; - } else { - throw uhd::value_error("Could not find gain element " + name); - } - UHD_LOG_TRACE(unique_id(), "_set_tx_gain calling update gain"); - this->_set_all_gain(this->_get_all_gain(chan, TX_DIRECTION), - this->get_tx_frequency(chan), - chan, - TX_DIRECTION); - return clip_gain; -} - -double magnesium_radio_ctrl_impl::_get_tx_gain( - const std::string& name, const size_t /*chan*/ -) -{ - std::lock_guard<std::mutex> l(_set_lock); - if (name == MAGNESIUM_GAIN1) { - return _ad9371_att[TX_DIRECTION]; - } else if (name == MAGNESIUM_GAIN2) { - return _dsa_att[TX_DIRECTION]; - } else if (name == MAGNESIUM_AMP) { - return _amp_bypass[TX_DIRECTION] ? AMP_MIN_GAIN : AMP_MAX_GAIN; - } else { - throw uhd::value_error("Could not find gain element " + name); - } -} - -double magnesium_radio_ctrl_impl::set_rx_gain(const double gain, const size_t chan) -{ - std::lock_guard<std::mutex> l(_set_lock); - UHD_LOG_TRACE(unique_id(), "set_rx_gain(gain=" << gain << ", chan=" << chan << ")"); - const double coerced_gain = - _set_all_gain(gain, this->get_rx_frequency(chan), chan, RX_DIRECTION); - radio_ctrl_impl::set_rx_gain(coerced_gain, chan); - return coerced_gain; -} - -double magnesium_radio_ctrl_impl::_set_rx_gain( - const std::string& name, const double gain, const size_t chan) -{ - std::lock_guard<std::mutex> l(_set_lock); - UHD_LOG_TRACE(unique_id(), - "_set_rx_gain(name=" << name << ", gain=" << gain << ", chan=" << chan << ")"); - double clip_gain = 0; - if (name == MAGNESIUM_GAIN1) { - clip_gain = uhd::clip(gain, AD9371_MIN_RX_GAIN, AD9371_MAX_RX_GAIN); - _ad9371_att[RX_DIRECTION] = clip_gain; - } else if (name == MAGNESIUM_GAIN2) { - clip_gain = uhd::clip(gain, DSA_MIN_GAIN, DSA_MAX_GAIN); - _dsa_att[RX_DIRECTION] = clip_gain; - } else if (name == MAGNESIUM_AMP) { - clip_gain = gain > 0.0 ? AMP_MAX_GAIN : AMP_MIN_GAIN; - _amp_bypass[RX_DIRECTION] = clip_gain == 0.0; - } else { - throw uhd::value_error("Could not find gain element " + name); - } - UHD_LOG_TRACE(unique_id(), "_set_rx_gain calling update gain"); - this->_set_all_gain(this->_get_all_gain(chan, RX_DIRECTION), - this->get_rx_frequency(chan), - chan, - RX_DIRECTION); - return clip_gain; // not really any coerced here (only clip) for individual gain -} - -double magnesium_radio_ctrl_impl::_get_rx_gain( - const std::string& name, const size_t /*chan*/ -) -{ - std::lock_guard<std::mutex> l(_set_lock); - - if (name == MAGNESIUM_GAIN1) { - return _ad9371_att[RX_DIRECTION]; - } else if (name == MAGNESIUM_GAIN2) { - return _dsa_att[RX_DIRECTION]; - } else if (name == MAGNESIUM_AMP) { - return _amp_bypass[RX_DIRECTION] ? AMP_MIN_GAIN : AMP_MAX_GAIN; - } else { - throw uhd::value_error("Could not find gain element " + name); - } -} - -std::vector<std::string> magnesium_radio_ctrl_impl::get_rx_lo_names(const size_t /*chan*/ -) -{ - return std::vector<std::string>{MAGNESIUM_LO1, MAGNESIUM_LO2}; -} - -std::vector<std::string> magnesium_radio_ctrl_impl::get_rx_lo_sources( - const std::string& name, const size_t /*chan*/ -) -{ - if (name == MAGNESIUM_LO2) { - return std::vector<std::string>{"internal"}; - } else if (name == MAGNESIUM_LO1) { - return std::vector<std::string>{"internal", "external"}; - } else { - throw uhd::value_error("Could not find LO stage " + name); - } -} - -freq_range_t magnesium_radio_ctrl_impl::get_rx_lo_freq_range( - const std::string& name, const size_t /*chan*/ -) -{ - if (name == MAGNESIUM_LO1) { - return freq_range_t{ADF4351_MIN_FREQ, ADF4351_MAX_FREQ}; - } else if (name == MAGNESIUM_LO2) { - return freq_range_t{AD9371_MIN_FREQ, AD9371_MAX_FREQ}; - } else { - throw uhd::value_error("Could not find LO stage " + name); - } -} - -void magnesium_radio_ctrl_impl::set_rx_lo_source( - const std::string& src, const std::string& name, const size_t /*chan*/ -) -{ - // TODO: checking what options are there - std::lock_guard<std::mutex> l(_set_lock); - UHD_LOG_TRACE(unique_id(), "Setting RX LO " << name << " to " << src); - - if (name == MAGNESIUM_LO1) { - _ad9371->set_lo_source(src, RX_DIRECTION); - } else { - UHD_LOG_ERROR(unique_id(), - "RX LO " << name << " does not support setting source to " << src); - } -} - -const std::string magnesium_radio_ctrl_impl::get_rx_lo_source( - const std::string& name, const size_t /*chan*/ -) -{ - if (name == MAGNESIUM_LO1) { - // TODO: should we use this from cache? - return _ad9371->get_lo_source(RX_DIRECTION); - } - return "internal"; -} - -double magnesium_radio_ctrl_impl::_set_rx_lo_freq(const std::string source, - const std::string name, - const double freq, - const size_t chan) -{ - double coerced_lo_freq = freq; - if (source != "internal") { - UHD_LOG_WARNING( - unique_id(), "LO source is not internal. This set frequency will be ignored"); - if (name == MAGNESIUM_LO1) { - // handle ad9371 external LO case - coerced_lo_freq = freq; - _ad9371_freq[RX_DIRECTION] = coerced_lo_freq; - } - } else { - if (name == MAGNESIUM_LO1) { - coerced_lo_freq = _ad9371->set_frequency(freq, chan, RX_DIRECTION); - _ad9371_freq[RX_DIRECTION] = coerced_lo_freq; - } else if (name == MAGNESIUM_LO2) { - // TODO: no hardcode the init_n_mode - coerced_lo_freq = _lo_enable(_rx_lo, freq, _master_clock_rate, false); - _adf4351_freq[RX_DIRECTION] = coerced_lo_freq; - } else { - UHD_LOG_WARNING(unique_id(), - "There's no LO with this name of " - << name << " in the system. This set rx lo freq will be ignored"); - }; - } - return coerced_lo_freq; -} - -double magnesium_radio_ctrl_impl::set_rx_lo_freq( - double freq, const std::string& name, const size_t chan) -{ - UHD_LOG_TRACE( - unique_id(), "Setting rx lo frequency for " << name << " with freq = " << freq); - std::lock_guard<std::mutex> l(_set_lock); - std::string source = this->get_rx_lo_source(name, chan); - const double coerced_lo_freq = this->_set_rx_lo_freq(source, name, freq, chan); - this->_update_freq(chan, RX_DIRECTION); - this->_update_gain(chan, RX_DIRECTION); - return coerced_lo_freq; -} - -double magnesium_radio_ctrl_impl::get_rx_lo_freq( - const std::string& name, const size_t chan) -{ - UHD_LOG_TRACE(unique_id(), "Getting rx lo frequency for " << name); - std::string source = this->get_rx_lo_source(name, chan); - if (name == MAGNESIUM_LO1) { - return _ad9371_freq[RX_DIRECTION]; - } else if (name == "adf4531") { - return _adf4351_freq[RX_DIRECTION]; - } else { - UHD_LOG_ERROR(unique_id(), - "There's no LO with this name of " - << name << " in the system. This set rx lo freq will be ignored"); - } - UHD_THROW_INVALID_CODE_PATH(); -} - -// TX LO -std::vector<std::string> magnesium_radio_ctrl_impl::get_tx_lo_names(const size_t /*chan*/ -) -{ - return std::vector<std::string>{MAGNESIUM_LO1, MAGNESIUM_LO2}; -} - -std::vector<std::string> magnesium_radio_ctrl_impl::get_tx_lo_sources( - const std::string& name, const size_t /*chan*/ -) -{ - if (name == MAGNESIUM_LO2) { - return std::vector<std::string>{"internal"}; - } else if (name == MAGNESIUM_LO1) { - return std::vector<std::string>{"internal", "external"}; - } else { - throw uhd::value_error("Could not find LO stage " + name); - } -} - -freq_range_t magnesium_radio_ctrl_impl::get_tx_lo_freq_range( - const std::string& name, const size_t /*chan*/ -) -{ - if (name == MAGNESIUM_LO2) { - return freq_range_t{ADF4351_MIN_FREQ, ADF4351_MAX_FREQ}; - } else if (name == MAGNESIUM_LO1) { - return freq_range_t{AD9371_MIN_FREQ, AD9371_MAX_FREQ}; - } else { - throw uhd::value_error("Could not find LO stage " + name); - } -} - -void magnesium_radio_ctrl_impl::set_tx_lo_source( - const std::string& src, const std::string& name, const size_t /*chan*/ -) -{ - // TODO: checking what options are there - std::lock_guard<std::mutex> l(_set_lock); - UHD_LOG_TRACE(unique_id(), "Setting TX LO " << name << " to " << src); - if (name == MAGNESIUM_LO1) { - _ad9371->set_lo_source(src, TX_DIRECTION); - } else { - UHD_LOG_ERROR(unique_id(), - "TX LO " << name << " does not support setting source to " << src); - } -} - -const std::string magnesium_radio_ctrl_impl::get_tx_lo_source( - const std::string& name, const size_t /*chan*/ -) -{ - if (name == MAGNESIUM_LO1) { - // TODO: should we use this from cache? - return _ad9371->get_lo_source(TX_DIRECTION); - } - return "internal"; -} - -double magnesium_radio_ctrl_impl::_set_tx_lo_freq(const std::string source, - const std::string name, - const double freq, - const size_t chan) -{ - double coerced_lo_freq = freq; - if (source != "internal") { - UHD_LOG_WARNING( - unique_id(), "LO source is not internal. This set frequency will be ignored"); - if (name == MAGNESIUM_LO1) { - // handle ad9371 external LO case - coerced_lo_freq = freq; - _ad9371_freq[TX_DIRECTION] = coerced_lo_freq; - } - } else { - if (name == MAGNESIUM_LO1) { - coerced_lo_freq = _ad9371->set_frequency(freq, chan, TX_DIRECTION); - _ad9371_freq[TX_DIRECTION] = coerced_lo_freq; - } else if (name == MAGNESIUM_LO2) { - // TODO: no hardcode the int_n_mode - const bool int_n_mode = false; - coerced_lo_freq = _lo_enable(_tx_lo, freq, _master_clock_rate, int_n_mode); - _adf4351_freq[TX_DIRECTION] = coerced_lo_freq; - } else { - UHD_LOG_WARNING(unique_id(), - "There's no LO with this name of " - << name << " in the system. This set tx lo freq will be ignored"); - }; - } - return coerced_lo_freq; -} - -double magnesium_radio_ctrl_impl::set_tx_lo_freq( - double freq, const std::string& name, const size_t chan) -{ - UHD_LOG_TRACE( - unique_id(), "Setting tx lo frequency for " << name << " with freq = " << freq); - std::string source = this->get_tx_lo_source(name, chan); - const double return_freq = this->_set_tx_lo_freq(source, name, freq, chan); - this->_update_freq(chan, TX_DIRECTION); - this->_update_gain(chan, TX_DIRECTION); - return return_freq; -} - -double magnesium_radio_ctrl_impl::get_tx_lo_freq( - const std::string& name, const size_t chan) -{ - UHD_LOG_TRACE(unique_id(), "Getting tx lo frequency for " << name); - std::string source = this->get_tx_lo_source(name, chan); - if (name == MAGNESIUM_LO1) { - return _ad9371_freq[TX_DIRECTION]; - } else if (name == MAGNESIUM_LO2) { - return _adf4351_freq[TX_DIRECTION]; - } else { - UHD_LOG_ERROR( - unique_id(), "There's no LO with this name of " << name << " in the system."); - }; - - UHD_THROW_INVALID_CODE_PATH(); -} - - -size_t magnesium_radio_ctrl_impl::get_chan_from_dboard_fe( - const std::string& fe, const direction_t /* dir */ -) -{ - return boost::lexical_cast<size_t>(fe); -} - -std::string magnesium_radio_ctrl_impl::get_dboard_fe_from_chan( - const size_t chan, const direction_t /* dir */ -) -{ - return std::to_string(chan); -} - - -void magnesium_radio_ctrl_impl::_remap_band_limits( - const std::string band_map, const uhd::direction_t dir) -{ - const size_t dflt_band_size = (dir == RX_DIRECTION) ? _rx_band_map.size() - : _tx_band_map.size(); - - std::vector<std::string> band_map_split; - double band_lim; - - UHD_LOG_DEBUG(unique_id(), "Using user specified frequency band limits"); - boost::split(band_map_split, band_map, boost::is_any_of(";")); - if (band_map_split.size() != dflt_band_size) { - throw uhd::runtime_error(( - boost::format( - "size %s of given frequency band map doesn't match the required size: %s") - % band_map_split.size() % dflt_band_size) - .str()); - } - UHD_LOG_DEBUG(unique_id(), "newly used band limits: "); - for (size_t i = 0; i < band_map_split.size(); i++) { - try { - band_lim = std::stod(band_map_split.at(i)); - } catch (...) { - throw uhd::value_error( - (boost::format("error while converting given frequency string %s " - "to a double value") - % band_map_split.at(i)) - .str()); - } - UHD_LOG_DEBUG(unique_id(), "band " << i << " limit: " << band_lim << "Hz"); - if (dir == RX_DIRECTION) - _rx_band_map.at(i) = band_lim; - else - _tx_band_map.at(i) = band_lim; - } -} - - -void magnesium_radio_ctrl_impl::set_rpc_client( - uhd::rpc_client::sptr rpcc, const uhd::device_addr_t& block_args) -{ - _rpcc = rpcc; - _block_args = block_args; - UHD_LOG_TRACE(unique_id(), "Instantiating AD9371 control object..."); - _ad9371 = magnesium_ad9371_iface::uptr( - new magnesium_ad9371_iface(_rpcc, (_radio_slot == "A") ? 0 : 1)); - - if (block_args.has_key("identify")) { - const std::string identify_val = block_args.get("identify"); - int identify_duration = std::atoi(identify_val.c_str()); - if (identify_duration == 0) { - identify_duration = 5; - } - UHD_LOG_INFO(unique_id(), - "Running LED identification process for " << identify_duration - << " seconds."); - _identify_with_leds(identify_duration); - } - - if (block_args.has_key("tx_gain_profile")) { - UHD_LOG_INFO(unique_id(), - "Using user specified TX gain profile: " << block_args.get( - "tx_gain_profile")); - _gain_profile[TX_DIRECTION] = block_args.get("tx_gain_profile"); - } - - if (block_args.has_key("rx_gain_profile")) { - UHD_LOG_INFO(unique_id(), - "Using user specified RX gain profile: " << block_args.get( - "rx_gain_profile")); - _gain_profile[RX_DIRECTION] = block_args.get("rx_gain_profile"); - } - - if (block_args.has_key("rx_band_map")) { - UHD_LOG_INFO(unique_id(), "Using user specified RX band limits"); - _remap_band_limits(block_args.get("rx_band_map"), RX_DIRECTION); - } - - if (block_args.has_key("tx_band_map")) { - UHD_LOG_INFO(unique_id(), "Using user specified TX band limits"); - _remap_band_limits(block_args.get("tx_band_map"), TX_DIRECTION); - } - - // Note: MCR gets set during the init() call (prior to this), which takes - // in arguments from the device args. So if block_args contains a - // master_clock_rate key, then it should better be whatever the device is - // configured to do. - _master_clock_rate = - _rpcc->request_with_token<double>(_rpc_prefix + "get_master_clock_rate"); - if (block_args.cast<double>("master_clock_rate", _master_clock_rate) - != _master_clock_rate) { - throw uhd::runtime_error(str( - boost::format("Master clock rate mismatch. Device returns %f MHz, " - "but should have been %f MHz.") - % (_master_clock_rate / 1e6) - % (block_args.cast<double>("master_clock_rate", _master_clock_rate) / 1e6))); - } - UHD_LOG_DEBUG( - unique_id(), "Master Clock Rate is: " << (_master_clock_rate / 1e6) << " MHz."); - radio_ctrl_impl::set_rate(_master_clock_rate); - - // EEPROM paths subject to change FIXME - const size_t db_idx = get_block_id().get_block_count(); - _tree->access<eeprom_map_t>(_root_path / "eeprom") - .add_coerced_subscriber([this, db_idx](const eeprom_map_t& db_eeprom) { - this->_rpcc->notify_with_token("set_db_eeprom", db_idx, db_eeprom); - }) - .set_publisher([this, db_idx]() { - return this->_rpcc->request_with_token<eeprom_map_t>("get_db_eeprom", db_idx); - }); - - // Init sensors - for (const auto& dir : std::vector<direction_t>{RX_DIRECTION, TX_DIRECTION}) { - for (size_t chan_idx = 0; chan_idx < MAGNESIUM_NUM_CHANS; chan_idx++) { - _init_mpm_sensors(dir, chan_idx); - } - } -} - -bool magnesium_radio_ctrl_impl::get_lo_lock_status(const direction_t dir) -{ - if (not(bool(_rpcc))) { - UHD_LOG_DEBUG(unique_id(), "Reported no LO lock due to lack of RPC connection."); - return false; - } - - const std::string trx = (dir == RX_DIRECTION) ? "rx" : "tx"; - const size_t chan = 0; // They're the same after all - const double freq = (dir == RX_DIRECTION) ? get_rx_frequency(chan) - : get_tx_frequency(chan); - - bool lo_lock = - _rpcc->request_with_token<bool>(_rpc_prefix + "get_ad9371_lo_lock", trx); - UHD_LOG_TRACE(unique_id(), - "AD9371 " << trx << " LO reports lock: " << (lo_lock ? "Yes" : "No")); - if (lo_lock and _map_freq_to_rx_band(_rx_band_map, freq) == rx_band::LOWBAND) { - lo_lock = - lo_lock - && _rpcc->request_with_token<bool>(_rpc_prefix + "get_lowband_lo_lock", trx); - UHD_LOG_TRACE(unique_id(), - "ADF4351 " << trx << " LO reports lock: " << (lo_lock ? "Yes" : "No")); - } - - return lo_lock; -} - -UHD_RFNOC_BLOCK_REGISTER(magnesium_radio_ctrl, "MagnesiumRadio"); diff --git a/host/lib/usrp/dboard/magnesium/magnesium_radio_ctrl_init.cpp b/host/lib/usrp/dboard/magnesium/magnesium_radio_ctrl_init.cpp deleted file mode 100644 index 89db61428..000000000 --- a/host/lib/usrp/dboard/magnesium/magnesium_radio_ctrl_init.cpp +++ /dev/null @@ -1,713 +0,0 @@ -// -// Copyright 2017 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "magnesium_constants.hpp" -#include "magnesium_radio_ctrl_impl.hpp" -#include <uhd/transport/chdr.hpp> -#include <uhd/types/eeprom.hpp> -#include <uhd/types/sensors.hpp> -#include <uhd/utils/log.hpp> -#include <uhdlib/usrp/cores/spi_core_3000.hpp> -#include <boost/algorithm/string.hpp> -#include <boost/algorithm/string/case_conv.hpp> -#include <boost/algorithm/string/split.hpp> -#include <string> -#include <vector> -using namespace uhd; -using namespace uhd::rfnoc; - -namespace { -enum slave_select_t { SEN_CPLD = 1, SEN_TX_LO = 2, SEN_RX_LO = 4, SEN_PHASE_DAC = 8 }; - -constexpr double MAGNESIUM_DEFAULT_FREQ = 2.5e9; // Hz -constexpr double MAGNESIUM_DEFAULT_BANDWIDTH = 100e6; // Hz -constexpr char MAGNESIUM_DEFAULT_RX_ANTENNA[] = "RX2"; -constexpr char MAGNESIUM_DEFAULT_TX_ANTENNA[] = "TX/RX"; - -//! Magnesium gain profile options -const std::vector<std::string> MAGNESIUM_GP_OPTIONS = {"manual", - "default", - "default_rf_filter_bypass_always_on", - "default_rf_filter_bypass_always_off"}; -} // namespace - -//! Helper function to extract single value of port number. -// -// Each GPIO pins can be controlled by each radio output ports. -// This function convert the format of attribute "Radio_N_M" -// to a single value port number = N*number_of_port_per_radio + M - -uint32_t extract_port_number(std::string radio_src_string, uhd::property_tree::sptr ptree) -{ - std::string s_val = "0"; - std::vector<std::string> radio_strings; - boost::algorithm::split(radio_strings, - radio_src_string, - boost::is_any_of("_/"), - boost::token_compress_on); - boost::to_lower(radio_strings[0]); - if (radio_strings.size() < 3) { - throw uhd::runtime_error(str( - boost::format("%s is an invalid GPIO source string.") % radio_src_string)); - } - size_t radio_num = std::stoi(radio_strings[1]); - size_t port_num = std::stoi(radio_strings[2]); - if (radio_strings[0] != "radio") { - throw uhd::runtime_error( - "Front panel GPIO bank can only accept a radio block as its driver."); - } - std::string radio_port_out = "Radio_" + radio_strings[1] + "/ports/out"; - std::string radio_port_path = radio_port_out + "/" + radio_strings[2]; - auto found = ptree->exists(fs_path("xbar") / radio_port_path); - if (not found) { - throw uhd::runtime_error( - str(boost::format("Could not find radio port %s.\n") % radio_port_path)); - } - size_t port_size = ptree->list(fs_path("xbar") / radio_port_out).size(); - return radio_num * port_size + port_num; -} - -void magnesium_radio_ctrl_impl::_init_defaults() -{ - UHD_LOG_TRACE(unique_id(), "Initializing defaults..."); - const size_t num_rx_chans = get_output_ports().size(); - const size_t num_tx_chans = get_input_ports().size(); - - UHD_LOG_TRACE(unique_id(), - "Num TX chans: " << num_tx_chans << " Num RX chans: " << num_rx_chans); - - for (size_t chan = 0; chan < num_rx_chans; chan++) { - radio_ctrl_impl::set_rx_frequency(MAGNESIUM_DEFAULT_FREQ, chan); - radio_ctrl_impl::set_rx_gain(0, chan); - radio_ctrl_impl::set_rx_antenna(MAGNESIUM_DEFAULT_RX_ANTENNA, chan); - radio_ctrl_impl::set_rx_bandwidth(MAGNESIUM_DEFAULT_BANDWIDTH, chan); - } - - for (size_t chan = 0; chan < num_tx_chans; chan++) { - radio_ctrl_impl::set_tx_frequency(MAGNESIUM_DEFAULT_FREQ, chan); - radio_ctrl_impl::set_tx_gain(0, chan); - radio_ctrl_impl::set_tx_antenna(MAGNESIUM_DEFAULT_TX_ANTENNA, chan); - radio_ctrl_impl::set_tx_bandwidth(MAGNESIUM_DEFAULT_BANDWIDTH, chan); - } - - - /** Update default SPP (overwrites the default value from the XML file) **/ - const size_t max_bytes_header = - uhd::transport::vrt::chdr::max_if_hdr_words64 * sizeof(uint64_t); - const size_t default_spp = - (_tree->access<size_t>("mtu/recv").get() - max_bytes_header) - / (2 * sizeof(int16_t)); - UHD_LOG_DEBUG(unique_id(), "Setting default spp to " << default_spp); - _tree->access<int>(get_arg_path("spp") / "value").set(default_spp); -} - -void magnesium_radio_ctrl_impl::_init_peripherals() -{ - UHD_LOG_TRACE(unique_id(), "Initializing peripherals..."); - UHD_LOG_TRACE(unique_id(), "Initializing SPI core..."); - _spi = spi_core_3000::make( - _get_ctrl(0), regs::sr_addr(regs::SPI), regs::rb_addr(regs::RB_SPI)); - UHD_LOG_TRACE(unique_id(), "Initializing CPLD..."); - UHD_LOG_TRACE(unique_id(), "Creating new CPLD object..."); - spi_config_t spi_config; - spi_config.use_custom_divider = true; - spi_config.divider = 125; - spi_config.mosi_edge = spi_config_t::EDGE_RISE; - spi_config.miso_edge = spi_config_t::EDGE_FALL; - UHD_LOG_TRACE(unique_id(), "Making CPLD object..."); - _cpld = std::make_shared<magnesium_cpld_ctrl>( - [this, spi_config](const uint32_t transaction) { // Write functor - this->_spi->write_spi(SEN_CPLD, spi_config, transaction, 24); - }, - [this, spi_config](const uint32_t transaction) { // Read functor - return this->_spi->read_spi(SEN_CPLD, spi_config, transaction, 24); - }); - _update_atr_switches( - magnesium_cpld_ctrl::BOTH, DX_DIRECTION, radio_ctrl_impl::get_rx_antenna(0)); - UHD_LOG_TRACE(unique_id(), "Initializing TX LO..."); - _tx_lo = adf435x_iface::make_adf4351([this]( - const std::vector<uint32_t> transactions) { - for (const uint32_t transaction : transactions) { - this->_spi->write_spi(SEN_TX_LO, spi_config_t::EDGE_RISE, transaction, 32); - } - }); - UHD_LOG_TRACE(unique_id(), "Initializing RX LO..."); - _rx_lo = adf435x_iface::make_adf4351([this]( - const std::vector<uint32_t> transactions) { - for (const uint32_t transaction : transactions) { - this->_spi->write_spi(SEN_RX_LO, spi_config_t::EDGE_RISE, transaction, 32); - } - }); - - _gpio.clear(); // Following the as-if rule, this can get optimized out - for (size_t radio_idx = 0; radio_idx < _get_num_radios(); radio_idx++) { - UHD_LOG_TRACE(unique_id(), "Initializing GPIOs for channel " << radio_idx); - _gpio.emplace_back(usrp::gpio_atr::gpio_atr_3000::make(_get_ctrl(radio_idx), - regs::sr_addr(regs::GPIO), - regs::rb_addr(regs::RB_DB_GPIO))); - // DSA and AD9371 gain bits do *not* toggle on ATR modes. If we ever - // connect anything else to this core, we might need to set_atr_mode() - // to MODE_ATR on those bits. For now, all bits simply do what they're - // told, and don't toggle on RX/TX state changes. - _gpio.back()->set_atr_mode(usrp::gpio_atr::MODE_GPIO, // Disable ATR mode - usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL); - _gpio.back()->set_gpio_ddr(usrp::gpio_atr::DDR_OUTPUT, // Make all GPIOs outputs - usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL); - } - UHD_LOG_TRACE(unique_id(), "Initializing front-panel GPIO control...") - _fp_gpio = usrp::gpio_atr::gpio_atr_3000::make( - _get_ctrl(0), regs::sr_addr(regs::FP_GPIO), regs::rb_addr(regs::RB_FP_GPIO)); -} - -void magnesium_radio_ctrl_impl::_init_frontend_subtree( - uhd::property_tree::sptr subtree, const size_t chan_idx) -{ - const fs_path tx_fe_path = fs_path("tx_frontends") / chan_idx; - const fs_path rx_fe_path = fs_path("rx_frontends") / chan_idx; - UHD_LOG_TRACE(unique_id(), - "Adding non-RFNoC block properties for channel " - << chan_idx << " to prop tree path " << tx_fe_path << " and " << rx_fe_path); - // TX Standard attributes - subtree->create<std::string>(tx_fe_path / "name") - .set(str(boost::format("Magnesium"))); - subtree->create<std::string>(tx_fe_path / "connection").set("IQ"); - // RX Standard attributes - subtree->create<std::string>(rx_fe_path / "name") - .set(str(boost::format("Magnesium"))); - subtree->create<std::string>(rx_fe_path / "connection").set("IQ"); - // TX Antenna - subtree->create<std::string>(tx_fe_path / "antenna" / "value") - .add_coerced_subscriber([this, chan_idx](const std::string& ant) { - this->set_tx_antenna(ant, chan_idx); - }) - .set_publisher([this, chan_idx]() { return this->get_tx_antenna(chan_idx); }); - subtree->create<std::vector<std::string>>(tx_fe_path / "antenna" / "options") - .set({MAGNESIUM_DEFAULT_TX_ANTENNA}) - .add_coerced_subscriber([](const std::vector<std::string>&) { - throw uhd::runtime_error("Attempting to update antenna options!"); - }); - // RX Antenna - subtree->create<std::string>(rx_fe_path / "antenna" / "value") - .add_coerced_subscriber([this, chan_idx](const std::string& ant) { - this->set_rx_antenna(ant, chan_idx); - }) - .set_publisher([this, chan_idx]() { return this->get_rx_antenna(chan_idx); }); - subtree->create<std::vector<std::string>>(rx_fe_path / "antenna" / "options") - .set(MAGNESIUM_RX_ANTENNAS) - .add_coerced_subscriber([](const std::vector<std::string>&) { - throw uhd::runtime_error("Attempting to update antenna options!"); - }); - // TX frequency - subtree->create<double>(tx_fe_path / "freq" / "value") - .set_coercer([this, chan_idx](const double freq) { - return this->set_tx_frequency(freq, chan_idx); - }) - .set_publisher([this, chan_idx]() { return this->get_tx_frequency(chan_idx); }); - subtree->create<meta_range_t>(tx_fe_path / "freq" / "range") - .set(meta_range_t(MAGNESIUM_MIN_FREQ, MAGNESIUM_MAX_FREQ, 1.0)) - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update freq range!"); - }); - // RX frequency - subtree->create<double>(rx_fe_path / "freq" / "value") - .set_coercer([this, chan_idx](const double freq) { - return this->set_rx_frequency(freq, chan_idx); - }) - .set_publisher([this, chan_idx]() { return this->get_rx_frequency(chan_idx); }); - subtree->create<meta_range_t>(rx_fe_path / "freq" / "range") - .set(meta_range_t(MAGNESIUM_MIN_FREQ, MAGNESIUM_MAX_FREQ, 1.0)) - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update freq range!"); - }); - // TX bandwidth - subtree->create<double>(tx_fe_path / "bandwidth" / "value") - .set(AD9371_TX_MAX_BANDWIDTH) - .set_coercer([this, chan_idx](const double bw) { - return this->set_tx_bandwidth(bw, chan_idx); - }) - .set_publisher([this, chan_idx]() { return this->get_tx_bandwidth(chan_idx); }); - subtree->create<meta_range_t>(tx_fe_path / "bandwidth" / "range") - .set(meta_range_t(AD9371_TX_MIN_BANDWIDTH, AD9371_TX_MAX_BANDWIDTH)) - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update bandwidth range!"); - }); - // RX bandwidth - subtree->create<double>(rx_fe_path / "bandwidth" / "value") - .set(AD9371_RX_MAX_BANDWIDTH) - .set_coercer([this, chan_idx](const double bw) { - return this->set_rx_bandwidth(bw, chan_idx); - }); - subtree->create<meta_range_t>(rx_fe_path / "bandwidth" / "range") - .set(meta_range_t(AD9371_RX_MIN_BANDWIDTH, AD9371_RX_MAX_BANDWIDTH)) - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update bandwidth range!"); - }); - // TX gains - subtree->create<double>(tx_fe_path / "gains" / "all" / "value") - .set_coercer([this, chan_idx]( - const double gain) { return this->set_tx_gain(gain, chan_idx); }) - .set_publisher( - [this, chan_idx]() { return radio_ctrl_impl::get_tx_gain(chan_idx); }); - subtree->create<meta_range_t>(tx_fe_path / "gains" / "all" / "range") - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update gain range!"); - }) - .set_publisher([this]() { - if (_gain_profile[TX_DIRECTION] == "manual") { - return meta_range_t(0.0, 0.0, 0.0); - } else { - return meta_range_t(ALL_TX_MIN_GAIN, ALL_TX_MAX_GAIN, ALL_TX_GAIN_STEP); - } - }); - - subtree->create<std::vector<std::string>>(tx_fe_path / "gains/all/profile/options") - .set(MAGNESIUM_GP_OPTIONS); - - subtree->create<std::string>(tx_fe_path / "gains/all/profile/value") - .set_coercer([this](const std::string& profile) { - // check if given profile is valid, otherwise use default profile - std::string return_profile = profile; - if (std::find( - MAGNESIUM_GP_OPTIONS.begin(), MAGNESIUM_GP_OPTIONS.end(), profile) - == MAGNESIUM_GP_OPTIONS.end()) { - return_profile = "default"; - } - _gain_profile[TX_DIRECTION] = return_profile; - return return_profile; - }) - .set_publisher([this]() { return _gain_profile[TX_DIRECTION]; }); - - // RX gains - subtree->create<double>(rx_fe_path / "gains" / "all" / "value") - .set_coercer([this, chan_idx]( - const double gain) { return this->set_rx_gain(gain, chan_idx); }) - .set_publisher( - [this, chan_idx]() { return radio_ctrl_impl::get_rx_gain(chan_idx); }); - - subtree->create<meta_range_t>(rx_fe_path / "gains" / "all" / "range") - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update gain range!"); - }) - .set_publisher([this]() { - if (_gain_profile[RX_DIRECTION] == "manual") { - return meta_range_t(0.0, 0.0, 0.0); - } else { - return meta_range_t(ALL_RX_MIN_GAIN, ALL_RX_MAX_GAIN, ALL_RX_GAIN_STEP); - } - }); - - subtree->create<std::vector<std::string>>(rx_fe_path / "gains/all/profile/options") - .set(MAGNESIUM_GP_OPTIONS); - - subtree->create<std::string>(rx_fe_path / "gains/all/profile/value") - .set_coercer([this](const std::string& profile) { - // check if given profile is valid, otherwise use default profile - std::string return_profile = profile; - if (std::find( - MAGNESIUM_GP_OPTIONS.begin(), MAGNESIUM_GP_OPTIONS.end(), profile) - == MAGNESIUM_GP_OPTIONS.end()) { - return_profile = "default"; - } - _gain_profile[RX_DIRECTION] = return_profile; - return return_profile; - }) - .set_publisher([this]() { return _gain_profile[RX_DIRECTION]; }); - - // TX mykonos attenuation - subtree->create<double>(tx_fe_path / "gains" / MAGNESIUM_GAIN1 / "value") - .set_coercer([this, chan_idx](const double gain) { - return _set_tx_gain(MAGNESIUM_GAIN1, gain, chan_idx); - }) - .set_publisher( - [this, chan_idx]() { return this->_get_tx_gain(MAGNESIUM_GAIN1, chan_idx); }); - - subtree->create<meta_range_t>(tx_fe_path / "gains" / MAGNESIUM_GAIN1 / "range") - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update gain range!"); - }) - .set_publisher([this]() { - if (_gain_profile[TX_DIRECTION] == "manual") { - return meta_range_t( - AD9371_MIN_TX_GAIN, AD9371_MAX_TX_GAIN, AD9371_TX_GAIN_STEP); - } else { - return meta_range_t(0.0, 0.0, 0.0); - } - }); - // TX DSA - subtree->create<double>(tx_fe_path / "gains" / MAGNESIUM_GAIN2 / "value") - .set_coercer([this, chan_idx](const double gain) { - return this->_set_tx_gain(MAGNESIUM_GAIN2, gain, chan_idx); - }) - .set_publisher( - [this, chan_idx]() { return this->_get_tx_gain(MAGNESIUM_GAIN2, chan_idx); }); - - subtree->create<meta_range_t>(tx_fe_path / "gains" / MAGNESIUM_GAIN2 / "range") - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update gain range!"); - }) - .set_publisher([this]() { - if (_gain_profile[TX_DIRECTION] == "manual") { - return meta_range_t(DSA_MIN_GAIN, DSA_MAX_GAIN, DSA_GAIN_STEP); - } else { - return meta_range_t(0.0, 0.0, 0.0); - } - }); - // TX amp - subtree->create<double>(tx_fe_path / "gains" / MAGNESIUM_AMP / "value") - .set_coercer([this, chan_idx](const double gain) { - return this->_set_tx_gain(MAGNESIUM_AMP, gain, chan_idx); - }) - .set_publisher( - [this, chan_idx]() { return this->_get_tx_gain(MAGNESIUM_AMP, chan_idx); }); - - subtree->create<meta_range_t>(tx_fe_path / "gains" / MAGNESIUM_AMP / "range") - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update gain range!"); - }) - .set_publisher([this]() { - if (_gain_profile[TX_DIRECTION] == "manual") { - return meta_range_t(AMP_MIN_GAIN, AMP_MAX_GAIN, AMP_GAIN_STEP); - } else { - return meta_range_t(0.0, 0.0, 0.0); - } - }); - - // RX mykonos attenuation - subtree->create<double>(rx_fe_path / "gains" / MAGNESIUM_GAIN1 / "value") - .set_coercer([this, chan_idx](const double gain) { - UHD_VAR(gain); - return this->_set_rx_gain(MAGNESIUM_GAIN1, gain, chan_idx); - }) - .set_publisher( - [this, chan_idx]() { return this->_get_rx_gain(MAGNESIUM_GAIN1, chan_idx); }); - - subtree->create<meta_range_t>(rx_fe_path / "gains" / MAGNESIUM_GAIN1 / "range") - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update gain range!"); - }) - .set_publisher([this]() { - if (_gain_profile[RX_DIRECTION] == "manual") { - return meta_range_t( - AD9371_MIN_RX_GAIN, AD9371_MAX_RX_GAIN, AD9371_RX_GAIN_STEP); - } else { - return meta_range_t(0.0, 0.0, 0.0); - } - }); - // RX DSA - subtree->create<double>(rx_fe_path / "gains" / MAGNESIUM_GAIN2 / "value") - .set_coercer([this, chan_idx](const double gain) { - UHD_VAR(gain); - return this->_set_rx_gain(MAGNESIUM_GAIN2, gain, chan_idx); - }) - .set_publisher( - [this, chan_idx]() { return this->_get_rx_gain(MAGNESIUM_GAIN2, chan_idx); }); - - subtree->create<meta_range_t>(rx_fe_path / "gains" / MAGNESIUM_GAIN2 / "range") - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update gain range!"); - }) - .set_publisher([this]() { - if (_gain_profile[RX_DIRECTION] == "manual") { - return meta_range_t(DSA_MIN_GAIN, DSA_MAX_GAIN, DSA_MAX_GAIN); - } else { - return meta_range_t(0.0, 0.0, 0.0); - } - }); - - // RX amp - subtree->create<double>(rx_fe_path / "gains" / MAGNESIUM_AMP / "value") - .set_coercer([this, chan_idx](const double gain) { - return this->_set_rx_gain(MAGNESIUM_AMP, gain, chan_idx); - }) - .set_publisher( - [this, chan_idx]() { return this->_get_rx_gain(MAGNESIUM_AMP, chan_idx); }); - - subtree->create<meta_range_t>(rx_fe_path / "gains" / MAGNESIUM_AMP / "range") - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update gain range!"); - }) - .set_publisher([this]() { - if (_gain_profile[RX_DIRECTION] == "manual") { - return meta_range_t(AMP_MIN_GAIN, AMP_MAX_GAIN, AMP_GAIN_STEP); - } else { - return meta_range_t(0.0, 0.0, 0.0); - } - }); - - // TX LO lock sensor ////////////////////////////////////////////////////// - // Note: The lowband and AD9371 LO lock sensors are generated - // programmatically in set_rpc_client(). The actual lo_locked publisher is - // also set there. - subtree->create<sensor_value_t>(tx_fe_path / "sensors" / "lo_locked") - .set(sensor_value_t("all_los", false, "locked", "unlocked")) - .add_coerced_subscriber([](const sensor_value_t&) { - throw uhd::runtime_error("Attempting to write to sensor!"); - }) - .set_publisher([this]() { - return sensor_value_t( - "all_los", this->get_lo_lock_status(TX_DIRECTION), "locked", "unlocked"); - }); - // RX LO lock sensor (see not on TX LO lock sensor) - subtree->create<sensor_value_t>(rx_fe_path / "sensors" / "lo_locked") - .set(sensor_value_t("all_los", false, "locked", "unlocked")) - .add_coerced_subscriber([](const sensor_value_t&) { - throw uhd::runtime_error("Attempting to write to sensor!"); - }) - .set_publisher([this]() { - return sensor_value_t( - "all_los", this->get_lo_lock_status(RX_DIRECTION), "locked", "unlocked"); - }); - // LO Specific - // RX LO - subtree->create<meta_range_t>(rx_fe_path / "los" / MAGNESIUM_LO1 / "freq/range") - .set_publisher([this, chan_idx]() { - return this->get_rx_lo_freq_range(MAGNESIUM_LO1, chan_idx); - }); - subtree - ->create<std::vector<std::string>>( - rx_fe_path / "los" / MAGNESIUM_LO1 / "source/options") - .set_publisher([this, chan_idx]() { - return this->get_rx_lo_sources(MAGNESIUM_LO1, chan_idx); - }); - subtree->create<std::string>(rx_fe_path / "los" / MAGNESIUM_LO1 / "source/value") - .add_coerced_subscriber([this, chan_idx](std::string src) { - this->set_rx_lo_source(src, MAGNESIUM_LO1, chan_idx); - }) - .set_publisher([this, chan_idx]() { - return this->get_rx_lo_source(MAGNESIUM_LO1, chan_idx); - }); - subtree->create<double>(rx_fe_path / "los" / MAGNESIUM_LO1 / "freq/value") - .set_publisher( - [this, chan_idx]() { return this->get_rx_lo_freq(MAGNESIUM_LO1, chan_idx); }) - .set_coercer([this, chan_idx](const double freq) { - return this->set_rx_lo_freq(freq, MAGNESIUM_LO1, chan_idx); - }); - - subtree->create<meta_range_t>(rx_fe_path / "los" / MAGNESIUM_LO2 / "freq/range") - .set_publisher([this, chan_idx]() { - return this->get_rx_lo_freq_range(MAGNESIUM_LO2, chan_idx); - }); - subtree - ->create<std::vector<std::string>>( - rx_fe_path / "los" / MAGNESIUM_LO2 / "source/options") - .set_publisher([this, chan_idx]() { - return this->get_rx_lo_sources(MAGNESIUM_LO2, chan_idx); - }); - - subtree->create<std::string>(rx_fe_path / "los" / MAGNESIUM_LO2 / "source/value") - .add_coerced_subscriber([this, chan_idx](std::string src) { - this->set_rx_lo_source(src, MAGNESIUM_LO2, chan_idx); - }) - .set_publisher([this, chan_idx]() { - return this->get_rx_lo_source(MAGNESIUM_LO2, chan_idx); - }); - subtree->create<double>(rx_fe_path / "los" / MAGNESIUM_LO2 / "freq/value") - .set_publisher( - [this, chan_idx]() { return this->get_rx_lo_freq(MAGNESIUM_LO2, chan_idx); }) - .set_coercer([this, chan_idx](double freq) { - return this->set_rx_lo_freq(freq, MAGNESIUM_LO2, chan_idx); - }); - // TX LO - subtree->create<meta_range_t>(tx_fe_path / "los" / MAGNESIUM_LO1 / "freq/range") - .set_publisher([this, chan_idx]() { - return this->get_rx_lo_freq_range(MAGNESIUM_LO1, chan_idx); - }); - subtree - ->create<std::vector<std::string>>( - tx_fe_path / "los" / MAGNESIUM_LO1 / "source/options") - .set_publisher([this, chan_idx]() { - return this->get_tx_lo_sources(MAGNESIUM_LO1, chan_idx); - }); - subtree->create<std::string>(tx_fe_path / "los" / MAGNESIUM_LO1 / "source/value") - .add_coerced_subscriber([this, chan_idx](std::string src) { - this->set_tx_lo_source(src, MAGNESIUM_LO1, chan_idx); - }) - .set_publisher([this, chan_idx]() { - return this->get_tx_lo_source(MAGNESIUM_LO1, chan_idx); - }); - subtree->create<double>(tx_fe_path / "los" / MAGNESIUM_LO1 / "freq/value ") - .set_publisher( - [this, chan_idx]() { return this->get_tx_lo_freq(MAGNESIUM_LO1, chan_idx); }) - .set_coercer([this, chan_idx](double freq) { - return this->set_tx_lo_freq(freq, MAGNESIUM_LO1, chan_idx); - }); - - subtree->create<meta_range_t>(tx_fe_path / "los" / MAGNESIUM_LO2 / "freq/range") - .set_publisher([this, chan_idx]() { - return this->get_tx_lo_freq_range(MAGNESIUM_LO2, chan_idx); - }); - subtree - ->create<std::vector<std::string>>( - tx_fe_path / "los" / MAGNESIUM_LO2 / "source/options") - .set_publisher([this, chan_idx]() { - return this->get_tx_lo_sources(MAGNESIUM_LO2, chan_idx); - }); - - subtree->create<std::string>(tx_fe_path / "los" / MAGNESIUM_LO2 / "source/value") - .add_coerced_subscriber([this, chan_idx](std::string src) { - this->set_tx_lo_source(src, MAGNESIUM_LO2, chan_idx); - }) - .set_publisher([this, chan_idx]() { - return this->get_tx_lo_source(MAGNESIUM_LO2, chan_idx); - }); - subtree->create<double>(tx_fe_path / "los" / MAGNESIUM_LO2 / "freq/value") - .set_publisher( - [this, chan_idx]() { return this->get_tx_lo_freq(MAGNESIUM_LO2, chan_idx); }) - .set_coercer([this, chan_idx](double freq) { - return this->set_tx_lo_freq(freq, MAGNESIUM_LO2, chan_idx); - }); -} - -void magnesium_radio_ctrl_impl::_init_prop_tree() -{ - const fs_path fe_base = fs_path("dboards") / _radio_slot; - for (size_t chan_idx = 0; chan_idx < MAGNESIUM_NUM_CHANS; chan_idx++) { - this->_init_frontend_subtree(_tree->subtree(fe_base), chan_idx); - } - - // EEPROM paths subject to change FIXME - _tree->create<eeprom_map_t>(_root_path / "eeprom").set(eeprom_map_t()); - - // TODO change codec names - _tree->create<int>("rx_codecs" / _radio_slot / "gains"); - _tree->create<int>("tx_codecs" / _radio_slot / "gains"); - _tree->create<std::string>("rx_codecs" / _radio_slot / "name").set("AD9371 Dual ADC"); - _tree->create<std::string>("tx_codecs" / _radio_slot / "name").set("AD9371 Dual DAC"); - - // TODO remove this dirty hack - if (not _tree->exists("tick_rate")) { - _tree->create<double>("tick_rate").set_publisher([this]() { - return this->get_rate(); - }); - } - - // *****FP_GPIO************************ - for (const auto& attr : usrp::gpio_atr::gpio_attr_map) { - if (not _tree->exists(fs_path("gpio") / "FP0" / attr.second)) { - switch (attr.first) { - case usrp::gpio_atr::GPIO_SRC: - // FIXME: move this creation of this branch of ptree out side of - // radio impl; - // since there's no data dependency between radio and SRC setting for - // FP0 - _tree - ->create<std::vector<std::string>>( - fs_path("gpio") / "FP0" / attr.second) - .set(std::vector<std::string>( - 32, usrp::gpio_atr::default_attr_value_map.at(attr.first))) - .add_coerced_subscriber( - [this, attr](const std::vector<std::string> str_val) { - uint32_t radio_src_value = 0; - uint32_t master_value = 0; - for (size_t i = 0; i < str_val.size(); i++) { - if (str_val[i] == "PS") { - master_value += 1 << i; - ; - } else { - auto port_num = - extract_port_number(str_val[i], _tree); - radio_src_value = - (1 << (2 * i)) * port_num + radio_src_value; - } - } - _rpcc->notify_with_token( - "set_fp_gpio_master", master_value); - _rpcc->notify_with_token( - "set_fp_gpio_radio_src", radio_src_value); - }); - break; - case usrp::gpio_atr::GPIO_CTRL: - case usrp::gpio_atr::GPIO_DDR: - _tree - ->create<std::vector<std::string>>( - fs_path("gpio") / "FP0" / attr.second) - .set(std::vector<std::string>( - 32, usrp::gpio_atr::default_attr_value_map.at(attr.first))) - .add_coerced_subscriber( - [this, attr](const std::vector<std::string> str_val) { - uint32_t val = 0; - for (size_t i = 0; i < str_val.size(); i++) { - val += usrp::gpio_atr::gpio_attr_value_pair - .at(attr.second) - .at(str_val[i]) - << i; - } - _fp_gpio->set_gpio_attr(attr.first, val); - }); - break; - case usrp::gpio_atr::GPIO_READBACK: { - _tree->create<uint32_t>(fs_path("gpio") / "FP0" / attr.second) - .set_publisher([this]() { return _fp_gpio->read_gpio(); }); - } break; - default: - _tree->create<uint32_t>(fs_path("gpio") / "FP0" / attr.second) - .set(0) - .add_coerced_subscriber([this, attr](const uint32_t val) { - _fp_gpio->set_gpio_attr(attr.first, val); - }); - } - } else { - switch (attr.first) { - case usrp::gpio_atr::GPIO_SRC: - break; - case usrp::gpio_atr::GPIO_CTRL: - case usrp::gpio_atr::GPIO_DDR: - _tree - ->access<std::vector<std::string>>( - fs_path("gpio") / "FP0" / attr.second) - .set(std::vector<std::string>( - 32, usrp::gpio_atr::default_attr_value_map.at(attr.first))) - .add_coerced_subscriber( - [this, attr](const std::vector<std::string> str_val) { - uint32_t val = 0; - for (size_t i = 0; i < str_val.size(); i++) { - val += usrp::gpio_atr::gpio_attr_value_pair - .at(attr.second) - .at(str_val[i]) - << i; - } - _fp_gpio->set_gpio_attr(attr.first, val); - }); - break; - case usrp::gpio_atr::GPIO_READBACK: - break; - default: - _tree->access<uint32_t>(fs_path("gpio") / "FP0" / attr.second) - .set(0) - .add_coerced_subscriber([this, attr](const uint32_t val) { - _fp_gpio->set_gpio_attr(attr.first, val); - }); - } - } - } -} - - -void magnesium_radio_ctrl_impl::_init_mpm_sensors( - const direction_t dir, const size_t chan_idx) -{ - const std::string trx = (dir == RX_DIRECTION) ? "RX" : "TX"; - const fs_path fe_path = fs_path("dboards") / _radio_slot - / (dir == RX_DIRECTION ? "rx_frontends" : "tx_frontends") - / chan_idx; - auto sensor_list = _rpcc->request_with_token<std::vector<std::string>>( - this->_rpc_prefix + "get_sensors", trx); - UHD_LOG_TRACE(unique_id(), - "Chan " << chan_idx << ": Found " << sensor_list.size() << " " << trx - << " sensors."); - for (const auto& sensor_name : sensor_list) { - UHD_LOG_TRACE(unique_id(), "Adding " << trx << " sensor " << sensor_name); - _tree->create<sensor_value_t>(fe_path / "sensors" / sensor_name) - .add_coerced_subscriber([](const sensor_value_t&) { - throw uhd::runtime_error("Attempting to write to sensor!"); - }) - .set_publisher([this, trx, sensor_name, chan_idx]() { - return sensor_value_t( - this->_rpcc->request_with_token<sensor_value_t::sensor_map_t>( - this->_rpc_prefix + "get_sensor", trx, sensor_name, chan_idx)); - }); - } -} diff --git a/host/lib/usrp/dboard/rhodium/CMakeLists.txt b/host/lib/usrp/dboard/rhodium/CMakeLists.txt index 2b2e9744c..3159037c4 100644 --- a/host/lib/usrp/dboard/rhodium/CMakeLists.txt +++ b/host/lib/usrp/dboard/rhodium/CMakeLists.txt @@ -6,10 +6,10 @@ if(ENABLE_MPMD) list(APPEND RHODIUM_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/rhodium_radio_ctrl_impl.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/rhodium_radio_ctrl_init.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/rhodium_radio_ctrl_cpld.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/rhodium_radio_ctrl_lo.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rhodium_radio_control.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rhodium_radio_control_init.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rhodium_radio_control_cpld.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rhodium_radio_control_lo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rhodium_bands.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rhodium_cpld_ctrl.cpp ) diff --git a/host/lib/usrp/dboard/rhodium/rhodium_bands.cpp b/host/lib/usrp/dboard/rhodium/rhodium_bands.cpp index ffa206195..9e0a1d3d3 100644 --- a/host/lib/usrp/dboard/rhodium/rhodium_bands.cpp +++ b/host/lib/usrp/dboard/rhodium/rhodium_bands.cpp @@ -4,8 +4,8 @@ // SPDX-License-Identifier: GPL-3.0-or-later // -#include "rhodium_radio_ctrl_impl.hpp" #include "rhodium_constants.hpp" +#include "rhodium_radio_control.hpp" #include <uhd/utils/math.hpp> using namespace uhd; @@ -30,7 +30,7 @@ namespace { * chosen to allow as much of the full bandwidth through unattenuated. * * Switch selection logic for these bands can be found in - * rhodium_radio_ctrl_impl::_update_rx_freq_switches() + * rhodium_radio_control_impl::_update_rx_freq_switches() */ constexpr double RHODIUM_RX_BAND0_MIN_FREQ = RHODIUM_MIN_FREQ; constexpr double RHODIUM_RX_BAND1_MIN_FREQ = 450e6; @@ -55,7 +55,7 @@ namespace { * bandwidth through unattenuated. * * Switch selection logic for these bands can be found in - * rhodium_radio_ctrl_impl::_update_tx_freq_switches() + * rhodium_radio_control_impl::_update_tx_freq_switches() */ constexpr double RHODIUM_TX_BAND0_MIN_FREQ = RHODIUM_MIN_FREQ; constexpr double RHODIUM_TX_BAND1_MIN_FREQ = 450e6; @@ -67,9 +67,9 @@ namespace { constexpr double RHODIUM_TX_BAND7_MIN_FREQ = 4100e6; } -rhodium_radio_ctrl_impl::rx_band -rhodium_radio_ctrl_impl::_map_freq_to_rx_band(const double freq) { - +rhodium_radio_control_impl::rx_band rhodium_radio_control_impl::_map_freq_to_rx_band( + const double freq) +{ auto freq_compare = fp_compare_epsilon<double>(freq, RHODIUM_FREQ_COMPARE_EPSILON); if (freq_compare < RHODIUM_RX_BAND0_MIN_FREQ) { @@ -95,9 +95,9 @@ rhodium_radio_ctrl_impl::_map_freq_to_rx_band(const double freq) { } } -rhodium_radio_ctrl_impl::tx_band -rhodium_radio_ctrl_impl::_map_freq_to_tx_band(const double freq) { - +rhodium_radio_control_impl::tx_band rhodium_radio_control_impl::_map_freq_to_tx_band( + const double freq) +{ auto freq_compare = fp_compare_epsilon<double>(freq, RHODIUM_FREQ_COMPARE_EPSILON); if (freq_compare < RHODIUM_TX_BAND0_MIN_FREQ) { @@ -123,12 +123,12 @@ rhodium_radio_ctrl_impl::_map_freq_to_tx_band(const double freq) { } } -bool rhodium_radio_ctrl_impl::_is_rx_lowband(const double freq) +bool rhodium_radio_control_impl::_is_rx_lowband(const double freq) { return _map_freq_to_rx_band(freq) == rx_band::RX_BAND_0; } -bool rhodium_radio_ctrl_impl::_is_tx_lowband(const double freq) +bool rhodium_radio_control_impl::_is_tx_lowband(const double freq) { return _map_freq_to_tx_band(freq) == tx_band::TX_BAND_0; } diff --git a/host/lib/usrp/dboard/rhodium/rhodium_constants.hpp b/host/lib/usrp/dboard/rhodium/rhodium_constants.hpp index c52a73bca..69e6bf676 100644 --- a/host/lib/usrp/dboard/rhodium/rhodium_constants.hpp +++ b/host/lib/usrp/dboard/rhodium/rhodium_constants.hpp @@ -48,6 +48,8 @@ static constexpr double LO_MIN_POWER = 0.0; static constexpr double LO_MAX_POWER = 63.0; static constexpr double LO_POWER_STEP = 1.0; +static constexpr double RHODIUM_DEFAULT_BANDWIDTH = 250e6; // Hz + static const std::vector<std::string> RHODIUM_RX_ANTENNAS = { "TX/RX", "RX2", "CAL", "TERM" }; @@ -57,10 +59,15 @@ static const std::vector<std::string> RHODIUM_TX_ANTENNAS = { }; // These names are taken from radio_rhodium.xml -static constexpr char SPUR_DODGING_ARG_NAME[] = "spur_dodging"; -static constexpr char SPUR_DODGING_THRESHOLD_ARG_NAME[] = "spur_dodging_threshold"; -static constexpr char HIGHBAND_SPUR_REDUCTION_ARG_NAME[] = "highband_spur_reduction"; +static constexpr char SPUR_DODGING_PROP_NAME[] = "spur_dodging"; +static constexpr char SPUR_DODGING_THRESHOLD_PROP_NAME[] = "spur_dodging_threshold"; +static constexpr char HIGHBAND_SPUR_REDUCTION_PROP_NAME[] = "highband_spur_reduction"; +static constexpr char RHODIUM_DEFAULT_SPUR_DOGING_MODE[] = "disabled"; +static constexpr double RHODIUM_DEFAULT_SPUR_DOGING_THRESHOLD = 2e6; +static constexpr char RHODIUM_DEFAULT_HB_SPUR_REDUCTION_MODE[] = "disabled"; + +static constexpr char RHODIUM_FPGPIO_BANK[] = "FP0"; static constexpr uint32_t RHODIUM_GPIO_MASK = 0x1F; static constexpr uint32_t SW10_GPIO_MASK = 0x3; static constexpr uint32_t LED_GPIO_MASK = 0x1C; @@ -85,8 +92,9 @@ static constexpr char RHODIUM_LO_GAIN[] = "dsa"; //! LO output power static constexpr char RHODIUM_LO_POWER[] = "lo"; -static constexpr int NUM_LO_OUTPUT_PORT_NAMES = 4; +static constexpr char RHODIUM_FE_NAME[] = "Rhodium"; +static constexpr int NUM_LO_OUTPUT_PORT_NAMES = 4; static constexpr std::array<const char*, NUM_LO_OUTPUT_PORT_NAMES> LO_OUTPUT_PORT_NAMES = { "LO_OUT_0", "LO_OUT_1", @@ -96,4 +104,28 @@ static constexpr std::array<const char*, NUM_LO_OUTPUT_PORT_NAMES> LO_OUTPUT_POR static constexpr size_t RHODIUM_NUM_CHANS = 1; +namespace n320_regs { + +static constexpr uint32_t PERIPH_BASE = 0x80000; +static constexpr uint32_t PERIPH_REG_OFFSET = 8; + +// db_control registers +static constexpr uint32_t SR_MISC_OUTS = PERIPH_BASE + 160 * PERIPH_REG_OFFSET; +static constexpr uint32_t SR_SPI = PERIPH_BASE + 168 * PERIPH_REG_OFFSET; +static constexpr uint32_t SR_LEDS = PERIPH_BASE + 176 * PERIPH_REG_OFFSET; +static constexpr uint32_t SR_FP_GPIO = PERIPH_BASE + 184 * PERIPH_REG_OFFSET; +static constexpr uint32_t SR_DB_GPIO = PERIPH_BASE + 192 * PERIPH_REG_OFFSET; + +static constexpr uint32_t RB_MISC_IO = PERIPH_BASE + 16 * PERIPH_REG_OFFSET; +static constexpr uint32_t RB_SPI = PERIPH_BASE + 17 * PERIPH_REG_OFFSET; +static constexpr uint32_t RB_LEDS = PERIPH_BASE + 18 * PERIPH_REG_OFFSET; +static constexpr uint32_t RB_DB_GPIO = PERIPH_BASE + 19 * PERIPH_REG_OFFSET; +static constexpr uint32_t RB_FP_GPIO = PERIPH_BASE + 20 * PERIPH_REG_OFFSET; + +//! Delta between frontend offsets for channel 0 and 1 +constexpr uint32_t SR_TX_FE_BASE = PERIPH_BASE + 208 * PERIPH_REG_OFFSET; +constexpr uint32_t SR_RX_FE_BASE = PERIPH_BASE + 224 * PERIPH_REG_OFFSET; + +} // namespace n320_regs + #endif /* INCLUDED_LIBUHD_RHODIUM_CONSTANTS_HPP */ diff --git a/host/lib/usrp/dboard/rhodium/rhodium_radio_control.cpp b/host/lib/usrp/dboard/rhodium/rhodium_radio_control.cpp new file mode 100644 index 000000000..a3b072e74 --- /dev/null +++ b/host/lib/usrp/dboard/rhodium/rhodium_radio_control.cpp @@ -0,0 +1,723 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "rhodium_radio_control.hpp" +#include "rhodium_constants.hpp" +#include <uhd/exception.hpp> +#include <uhd/rfnoc/registry.hpp> +#include <uhd/transport/chdr.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/types/eeprom.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/math.hpp> +#include <uhdlib/usrp/common/apply_corrections.hpp> +#include <uhdlib/utils/narrow.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/format.hpp> +#include <boost/make_shared.hpp> +#include <cmath> +#include <cstdlib> +#include <sstream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; +using namespace uhd::math::fp_compare; + +namespace { + constexpr char RX_FE_CONNECTION_LOWBAND[] = "QI"; + constexpr char RX_FE_CONNECTION_HIGHBAND[] = "IQ"; + constexpr char TX_FE_CONNECTION_LOWBAND[] = "QI"; + constexpr char TX_FE_CONNECTION_HIGHBAND[] = "IQ"; + + constexpr double DEFAULT_IDENTIFY_DURATION = 5.0; // seconds + + constexpr uint64_t SET_RATE_RPC_TIMEOUT_MS = 10000; + +} + + +/****************************************************************************** + * Structors + *****************************************************************************/ +rhodium_radio_control_impl::rhodium_radio_control_impl(make_args_ptr make_args) + : radio_control_impl(std::move(make_args)) +{ + RFNOC_LOG_TRACE("Entering rhodium_radio_control_impl ctor..."); + UHD_ASSERT_THROW(get_block_id().get_block_count() < 2); + const char radio_slot_name[] = {'A', 'B'}; + _radio_slot = radio_slot_name[get_block_id().get_block_count()]; + _rpc_prefix = + (_radio_slot == "A") ? "db_0_" : "db_1_"; + RFNOC_LOG_TRACE("Radio slot: " << _radio_slot); + UHD_ASSERT_THROW(get_num_input_ports() == RHODIUM_NUM_CHANS); + UHD_ASSERT_THROW(get_num_output_ports() == RHODIUM_NUM_CHANS); + UHD_ASSERT_THROW(get_mb_controller()); + _n320_mb_control = std::dynamic_pointer_cast<mpmd_mb_controller>(get_mb_controller()); + UHD_ASSERT_THROW(_n320_mb_control); + _n3xx_timekeeper = std::dynamic_pointer_cast<mpmd_mb_controller::mpmd_timekeeper>( + _n320_mb_control->get_timekeeper(0)); + UHD_ASSERT_THROW(_n3xx_timekeeper); + _rpcc = _n320_mb_control->get_rpc_client(); + UHD_ASSERT_THROW(_rpcc); + + const auto all_dboard_info = + _rpcc->request<std::vector<std::map<std::string, std::string>>>( + "get_dboard_info"); + RFNOC_LOG_TRACE("Hardware detected " << all_dboard_info.size() << " daughterboards."); + + // If we two radio blocks, but there is only one dboard plugged in, we skip + // initialization. The board needs to be in slot A + if (all_dboard_info.size() > get_block_id().get_block_count()) { + _init_defaults(); + _init_mpm(); + _init_peripherals(); + _init_prop_tree(); + } + + // Properties + for (auto& samp_rate_prop : _samp_rate_in) { + samp_rate_prop.set(_master_clock_rate); + } + for (auto& samp_rate_prop : _samp_rate_out) { + samp_rate_prop.set(_master_clock_rate); + } +} + +rhodium_radio_control_impl::~rhodium_radio_control_impl() +{ + RFNOC_LOG_TRACE("rhodium_radio_control_impl::dtor() "); +} + + +/****************************************************************************** + * RF API Calls + *****************************************************************************/ +double rhodium_radio_control_impl::set_rate(double requested_rate) +{ + meta_range_t rates; + for (const double rate : RHODIUM_RADIO_RATES) { + rates.push_back(range_t(rate)); + } + + const double rate = rates.clip(requested_rate); + if (!math::frequencies_are_equal(requested_rate, rate)) { + RFNOC_LOG_WARNING("Coercing requested sample rate from " + << (requested_rate / 1e6) << " MHz to " << (rate / 1e6) + << " MHz, the closest possible rate."); + } + + const double current_rate = get_rate(); + if (math::frequencies_are_equal(current_rate, rate)) { + RFNOC_LOG_DEBUG( + "Rate is already at " << (rate / 1e6) << " MHz. Skipping set_rate()"); + return current_rate; + } + + RFNOC_LOG_TRACE("Updating master clock rate to " << rate); + _master_clock_rate = _rpcc->request_with_token<double>( + SET_RATE_RPC_TIMEOUT_MS, "db_0_set_master_clock_rate", rate); + _n3xx_timekeeper->update_tick_rate(_master_clock_rate); + radio_control_impl::set_rate(_master_clock_rate); + // The lowband LO frequency will change with the master clock rate, so + // update the tuning of the device. + set_tx_frequency(get_tx_frequency(0), 0); + set_rx_frequency(get_rx_frequency(0), 0); + + set_tick_rate(_master_clock_rate); + return _master_clock_rate; +} + +void rhodium_radio_control_impl::set_tx_antenna(const std::string& ant, const size_t chan) +{ + RFNOC_LOG_TRACE("set_tx_antenna(ant=" << ant << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + + if (!uhd::has(RHODIUM_TX_ANTENNAS, ant)) { + RFNOC_LOG_ERROR("Invalid TX antenna value: " << ant); + throw uhd::value_error("Requesting invalid TX antenna value!"); + } + + _update_tx_output_switches(ant); + // _update_atr will set the cached antenna value, so no need to do + // it here. See comments in _update_antenna for more info. + _update_atr(ant, TX_DIRECTION); +} + +void rhodium_radio_control_impl::set_rx_antenna(const std::string& ant, const size_t chan) +{ + RFNOC_LOG_TRACE("Setting RX antenna to " << ant); + UHD_ASSERT_THROW(chan == 0); + + if (!uhd::has(RHODIUM_RX_ANTENNAS, ant)) { + RFNOC_LOG_ERROR("Invalid RX antenna value: " << ant); + throw uhd::value_error("Requesting invalid RX antenna value!"); + } + + _update_rx_input_switches(ant); + // _update_atr will set the cached antenna value, so no need to do + // it here. See comments in _update_antenna for more info. + _update_atr(ant, RX_DIRECTION); +} + +void rhodium_radio_control_impl::_set_tx_fe_connection(const std::string& conn) +{ + RFNOC_LOG_TRACE("set_tx_fe_connection(conn=" << conn << ")"); + if (conn != _tx_fe_connection) { + _tx_fe_core->set_mux(conn); + _tx_fe_connection = conn; + } +} + +void rhodium_radio_control_impl::_set_rx_fe_connection(const std::string& conn) +{ + RFNOC_LOG_TRACE("set_rx_fe_connection(conn=" << conn << ")"); + if (conn != _rx_fe_connection) { + _rx_fe_core->set_fe_connection(conn); + _rx_fe_connection = conn; + } +} + +std::string rhodium_radio_control_impl::_get_tx_fe_connection() const +{ + return _tx_fe_connection; +} + +std::string rhodium_radio_control_impl::_get_rx_fe_connection() const +{ + return _rx_fe_connection; +} + +double rhodium_radio_control_impl::set_tx_frequency(const double freq, const size_t chan) +{ + RFNOC_LOG_TRACE("set_tx_frequency(f=" << freq << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + + const auto old_freq = get_tx_frequency(0); + double coerced_target_freq = uhd::clip(freq, RHODIUM_MIN_FREQ, RHODIUM_MAX_FREQ); + + if (freq != coerced_target_freq) { + RFNOC_LOG_DEBUG("Requested frequency is outside supported range. Coercing to " + << coerced_target_freq); + } + + const bool is_highband = !_is_tx_lowband(coerced_target_freq); + + const double target_lo_freq = is_highband ? + coerced_target_freq : _get_lowband_lo_freq() - coerced_target_freq; + const double actual_lo_freq = + set_tx_lo_freq(target_lo_freq, RHODIUM_LO1, chan); + const double coerced_freq = is_highband ? + actual_lo_freq : _get_lowband_lo_freq() - actual_lo_freq; + const auto conn = is_highband ? + TX_FE_CONNECTION_HIGHBAND : TX_FE_CONNECTION_LOWBAND; + + // update the cached frequency value now so calls to set gain and update + // switches will read the new frequency + radio_control_impl::set_tx_frequency(coerced_freq, chan); + + _set_tx_fe_connection(conn); + set_tx_gain(radio_control_impl::get_tx_gain(chan), 0); + + if (_get_highband_spur_reduction_enabled(TX_DIRECTION)) { + if (_get_timed_command_enabled() and _is_tx_lowband(old_freq) != not is_highband) { + RFNOC_LOG_WARNING( + "Timed tuning commands that transition between lowband and highband, 450 " + "MHz, do not function correctly when highband_spur_reduction is enabled! " + "Disable highband_spur_reduction or avoid using timed tuning commands."); + } + RFNOC_LOG_TRACE("TX Lowband LO is " << (is_highband ? "disabled" : "enabled")); + _rpcc->notify_with_token(_rpc_prefix + "enable_tx_lowband_lo", (!is_highband)); + } + _update_tx_freq_switches(coerced_freq); + const bool enable_corrections = is_highband + and (get_tx_lo_source(RHODIUM_LO1, 0) == "internal"); + _update_corrections(actual_lo_freq, TX_DIRECTION, enable_corrections); + // if TX lowband/highband changed and antenna is TX/RX, + // the ATR and SW1 need to be updated + _update_tx_output_switches(get_tx_antenna(0)); + _update_atr(get_tx_antenna(0), TX_DIRECTION); + + return coerced_freq; +} + +double rhodium_radio_control_impl::set_rx_frequency(const double freq, const size_t chan) +{ + RFNOC_LOG_TRACE("set_rx_frequency(f=" << freq << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + + const auto old_freq = get_rx_frequency(0); + double coerced_target_freq = uhd::clip(freq, RHODIUM_MIN_FREQ, RHODIUM_MAX_FREQ); + + if (freq != coerced_target_freq) { + RFNOC_LOG_DEBUG("Requested frequency is outside supported range. Coercing to " + << coerced_target_freq); + } + + const bool is_highband = !_is_rx_lowband(coerced_target_freq); + + const double target_lo_freq = is_highband ? + coerced_target_freq : _get_lowband_lo_freq() - coerced_target_freq; + const double actual_lo_freq = + set_rx_lo_freq(target_lo_freq, RHODIUM_LO1, chan); + const double coerced_freq = is_highband ? + actual_lo_freq : _get_lowband_lo_freq() - actual_lo_freq; + const auto conn = is_highband ? + RX_FE_CONNECTION_HIGHBAND : RX_FE_CONNECTION_LOWBAND; + + // update the cached frequency value now so calls to set gain and update + // switches will read the new frequency + radio_control_impl::set_rx_frequency(coerced_freq, chan); + + _set_rx_fe_connection(conn); + set_rx_gain(radio_control_impl::get_rx_gain(chan), 0); + + if (_get_highband_spur_reduction_enabled(RX_DIRECTION)) { + if (_get_timed_command_enabled() and _is_rx_lowband(old_freq) != not is_highband) { + RFNOC_LOG_WARNING( + "Timed tuning commands that transition between lowband and highband, 450 " + "MHz, do not function correctly when highband_spur_reduction is enabled! " + "Disable highband_spur_reduction or avoid using timed tuning commands."); + } + RFNOC_LOG_TRACE("RX Lowband LO is " << (is_highband ? "disabled" : "enabled")); + _rpcc->notify_with_token(_rpc_prefix + "enable_rx_lowband_lo", (!is_highband)); + } + _update_rx_freq_switches(coerced_freq); + const bool enable_corrections = is_highband + and (get_rx_lo_source(RHODIUM_LO1, 0) == "internal"); + _update_corrections(actual_lo_freq, RX_DIRECTION, enable_corrections); + + return coerced_freq; +} + +void rhodium_radio_control_impl::set_tx_tune_args( + const uhd::device_addr_t& args, const size_t chan) +{ + UHD_ASSERT_THROW(chan == 0); + _tune_args[uhd::TX_DIRECTION] = args; +} + +void rhodium_radio_control_impl::set_rx_tune_args( + const uhd::device_addr_t& args, const size_t chan) +{ + UHD_ASSERT_THROW(chan == 0); + _tune_args[uhd::RX_DIRECTION] = args; +} + +double rhodium_radio_control_impl::set_tx_gain(const double gain, const size_t chan) +{ + RFNOC_LOG_TRACE("set_tx_gain(gain=" << gain << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + + auto freq = this->get_tx_frequency(chan); + auto index = get_tx_gain_range(chan).clip(gain); + + auto old_band = _is_tx_lowband(_tx_frequency_at_last_gain_write) ? + rhodium_cpld_ctrl::gain_band_t::LOW : + rhodium_cpld_ctrl::gain_band_t::HIGH; + auto new_band = _is_tx_lowband(freq) ? + rhodium_cpld_ctrl::gain_band_t::LOW : + rhodium_cpld_ctrl::gain_band_t::HIGH; + + // The CPLD requires a rewrite of the gain control command on a change of lowband or highband + if (radio_control_impl::get_tx_gain(chan) != index or old_band != new_band) { + RFNOC_LOG_TRACE("Writing new TX gain index: " << index); + _cpld->set_gain_index(index, new_band, TX_DIRECTION); + _tx_frequency_at_last_gain_write = freq; + radio_control_impl::set_tx_gain(index, chan); + } else { + RFNOC_LOG_TRACE( + "No change in index or band, skipped writing TX gain index: " << index); + } + + return index; +} + +double rhodium_radio_control_impl::set_rx_gain(const double gain, const size_t chan) +{ + RFNOC_LOG_TRACE("set_rx_gain(gain=" << gain << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + + auto freq = this->get_rx_frequency(chan); + auto index = get_rx_gain_range(chan).clip(gain); + + auto old_band = _is_rx_lowband(_rx_frequency_at_last_gain_write) ? + rhodium_cpld_ctrl::gain_band_t::LOW : + rhodium_cpld_ctrl::gain_band_t::HIGH; + auto new_band = _is_rx_lowband(freq) ? + rhodium_cpld_ctrl::gain_band_t::LOW : + rhodium_cpld_ctrl::gain_band_t::HIGH; + + // The CPLD requires a rewrite of the gain control command on a change of lowband or highband + if (radio_control_impl::get_rx_gain(chan) != index or old_band != new_band) { + RFNOC_LOG_TRACE("Writing new RX gain index: " << index); + _cpld->set_gain_index(index, new_band, RX_DIRECTION); + _rx_frequency_at_last_gain_write = freq; + radio_control_impl::set_rx_gain(index, chan); + } else { + RFNOC_LOG_TRACE( + "No change in index or band, skipped writing RX gain index: " << index); + } + + return index; +} + +void rhodium_radio_control_impl::_identify_with_leds(double identify_duration) +{ + auto duration_ms = static_cast<uint64_t>(identify_duration * 1000); + auto end_time = + std::chrono::steady_clock::now() + std::chrono::milliseconds(duration_ms); + bool led_state = true; + { + std::lock_guard<std::mutex> lock(_ant_mutex); + while (std::chrono::steady_clock::now() < end_time) { + auto atr = led_state ? (LED_RX | LED_RX2 | LED_TX) : 0; + _gpio->set_atr_reg(gpio_atr::ATR_REG_IDLE, atr, RHODIUM_GPIO_MASK); + led_state = !led_state; + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + } + _update_atr(get_tx_antenna(0), TX_DIRECTION); + _update_atr(get_rx_antenna(0), RX_DIRECTION); +} + +void rhodium_radio_control_impl::_update_atr( + const std::string& ant, const direction_t dir) +{ + // This function updates sw10 based on the value of both antennas, so we + // use a mutex to prevent other calls in this class instance from running + // at the same time. + std::lock_guard<std::mutex> lock(_ant_mutex); + + RFNOC_LOG_TRACE( + "Updating ATRs for " << ((dir == RX_DIRECTION) ? "RX" : "TX") << " to " << ant); + + const auto rx_ant = (dir == RX_DIRECTION) ? ant : get_rx_antenna(0); + const auto tx_ant = (dir == TX_DIRECTION) ? ant : get_tx_antenna(0); + const auto sw10_tx = _is_tx_lowband(get_tx_frequency(0)) ? + SW10_FROMTXLOWBAND : SW10_FROMTXHIGHBAND; + + + const uint32_t atr_idle = SW10_ISOLATION; + + const uint32_t atr_rx = [rx_ant]{ + if (rx_ant == "TX/RX") { + return SW10_TORX | LED_RX; + } else if (rx_ant == "RX2") { + return SW10_ISOLATION | LED_RX2; + } else { + return SW10_ISOLATION; + } + }(); + + const uint32_t atr_tx = (tx_ant == "TX/RX") ? + (sw10_tx | LED_TX) : SW10_ISOLATION; + + const uint32_t atr_dx = [tx_ant, rx_ant, sw10_tx] { + uint32_t sw10_return; + if (tx_ant == "TX/RX") { + // if both channels are set to TX/RX, TX will override + sw10_return = sw10_tx | LED_TX; + } else if (rx_ant == "TX/RX") { + sw10_return = SW10_TORX | LED_RX; + } else { + sw10_return = SW10_ISOLATION; + } + sw10_return |= (rx_ant == "RX2") ? LED_RX2 : 0; + return sw10_return; + }(); + + _gpio->set_atr_reg(gpio_atr::ATR_REG_IDLE, atr_idle, RHODIUM_GPIO_MASK); + _gpio->set_atr_reg(gpio_atr::ATR_REG_RX_ONLY, atr_rx, RHODIUM_GPIO_MASK); + _gpio->set_atr_reg(gpio_atr::ATR_REG_TX_ONLY, atr_tx, RHODIUM_GPIO_MASK); + _gpio->set_atr_reg(gpio_atr::ATR_REG_FULL_DUPLEX, atr_dx, RHODIUM_GPIO_MASK); + + RFNOC_LOG_TRACE( + str(boost::format("Wrote ATR registers i:0x%02X, r:0x%02X, t:0x%02X, d:0x%02X") + % atr_idle % atr_rx % atr_tx % atr_dx)); + + if (dir == RX_DIRECTION) { + radio_control_impl::set_rx_antenna(ant, 0); + } else { + radio_control_impl::set_tx_antenna(ant, 0); + } +} + +void rhodium_radio_control_impl::_update_corrections( + const double freq, const direction_t dir, const bool enable) +{ + const std::string fe_path_part = dir == RX_DIRECTION ? "rx_fe_corrections" + : "tx_fe_corrections"; + const fs_path fe_corr_path = FE_PATH / fe_path_part / 0; + + if (enable) { + const std::vector<uint8_t> db_serial_u8 = get_db_eeprom().count("serial") + ? std::vector<uint8_t>() + : get_db_eeprom().at("serial"); + const std::string db_serial = + db_serial_u8.empty() ? "unknown" + : std::string(db_serial_u8.begin(), db_serial_u8.end()); + RFNOC_LOG_DEBUG("Loading any available frontend corrections for " + << ((dir == RX_DIRECTION) ? "RX" : "TX") << " at " << freq); + if (dir == RX_DIRECTION) { + apply_rx_fe_corrections(get_tree(), db_serial, fe_corr_path, freq); + } else { + apply_tx_fe_corrections(get_tree(), db_serial, fe_corr_path, freq); + } + } else { + RFNOC_LOG_DEBUG("Disabling frontend corrections for " + << ((dir == RX_DIRECTION) ? "RX" : "TX")); + if (dir == RX_DIRECTION) { + _rx_fe_core->set_iq_balance(rx_frontend_core_3000::DEFAULT_IQ_BALANCE_VALUE); + } else { + _tx_fe_core->set_dc_offset(tx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); + _tx_fe_core->set_iq_balance(tx_frontend_core_200::DEFAULT_IQ_BALANCE_VALUE); + } + } +} + +bool rhodium_radio_control_impl::_get_spur_dodging_enabled(uhd::direction_t dir) const +{ + // get the current tune_arg for spur_dodging + // if the tune_arg doesn't exist, use the radio block argument instead + const std::string spur_dodging_arg = _tune_args.at(dir).cast<std::string>( + SPUR_DODGING_PROP_NAME, _spur_dodging_mode.get()); + + RFNOC_LOG_TRACE("_get_spur_dodging_enabled returning " << spur_dodging_arg); + if (spur_dodging_arg == "enabled") { + return true; + } else if (spur_dodging_arg == "disabled") { + return false; + } else { + const std::string err_msg = str( + boost::format( + "Invalid spur_dodging argument: %s Valid options are [enabled, disabled]") + % spur_dodging_arg); + RFNOC_LOG_ERROR(err_msg); + throw uhd::value_error(err_msg); + } +} + +double rhodium_radio_control_impl::_get_spur_dodging_threshold(uhd::direction_t dir) const +{ + // get the current tune_arg for spur_dodging_threshold + // if the tune_arg doesn't exist, use the radio block argument instead + const double threshold = _tune_args.at(dir).cast<double>( + SPUR_DODGING_THRESHOLD_PROP_NAME, _spur_dodging_threshold.get()); + RFNOC_LOG_TRACE("_get_spur_dodging_threshold returning " << threshold); + return threshold; +} + +bool rhodium_radio_control_impl::_get_highband_spur_reduction_enabled( + uhd::direction_t dir) const +{ + const std::string highband_spur_reduction_arg = _tune_args.at(dir).cast<std::string>( + HIGHBAND_SPUR_REDUCTION_PROP_NAME, _highband_spur_reduction_mode.get()); + + RFNOC_LOG_TRACE(__func__ << " returning " << highband_spur_reduction_arg); + if (highband_spur_reduction_arg == "enabled") { + return true; + } else if (highband_spur_reduction_arg == "disabled") { + return false; + } else { + throw uhd::value_error( + str(boost::format("Invalid highband_spur_reduction argument: %s Valid " + "options are [enabled, disabled]") + % highband_spur_reduction_arg)); + } +} + +bool rhodium_radio_control_impl::_get_timed_command_enabled() const +{ + return get_command_time(0) != time_spec_t::ASAP; +} + +std::vector<std::string> rhodium_radio_control_impl::get_tx_antennas(const size_t) const +{ + return RHODIUM_RX_ANTENNAS; +} + +std::vector<std::string> rhodium_radio_control_impl::get_rx_antennas(const size_t) const +{ + return RHODIUM_TX_ANTENNAS; +} + +uhd::freq_range_t rhodium_radio_control_impl::get_tx_frequency_range(const size_t) const +{ + return meta_range_t(RHODIUM_MIN_FREQ, RHODIUM_MAX_FREQ, 1.0); +} + +uhd::freq_range_t rhodium_radio_control_impl::get_rx_frequency_range(const size_t) const +{ + return meta_range_t(RHODIUM_MIN_FREQ, RHODIUM_MAX_FREQ, 1.0); +} + +uhd::gain_range_t rhodium_radio_control_impl::get_tx_gain_range(const size_t) const +{ + return gain_range_t(TX_MIN_GAIN, TX_MAX_GAIN, TX_GAIN_STEP); +} + +uhd::gain_range_t rhodium_radio_control_impl::get_rx_gain_range(const size_t) const +{ + return gain_range_t(RX_MIN_GAIN, RX_MAX_GAIN, RX_GAIN_STEP); +} + +uhd::meta_range_t rhodium_radio_control_impl::get_tx_bandwidth_range(size_t) const +{ + return meta_range_t(RHODIUM_DEFAULT_BANDWIDTH, RHODIUM_DEFAULT_BANDWIDTH); +} + +uhd::meta_range_t rhodium_radio_control_impl::get_rx_bandwidth_range(size_t) const +{ + return meta_range_t(RHODIUM_DEFAULT_BANDWIDTH, RHODIUM_DEFAULT_BANDWIDTH); +} + + +/************************************************************************** + * Radio Identification API Calls + *************************************************************************/ +size_t rhodium_radio_control_impl::get_chan_from_dboard_fe( + const std::string& fe, const direction_t /* dir */ + ) const +{ + UHD_ASSERT_THROW(boost::lexical_cast<size_t>(fe) == 0); + return 0; +} + +std::string rhodium_radio_control_impl::get_dboard_fe_from_chan( + const size_t chan, const direction_t /* dir */ + ) const +{ + UHD_ASSERT_THROW(chan == 0); + return "0"; +} + +std::string rhodium_radio_control_impl::get_fe_name( + const size_t, const uhd::direction_t) const +{ + return RHODIUM_FE_NAME; +} + +/************************************************************************** + * GPIO Controls + *************************************************************************/ +std::vector<std::string> rhodium_radio_control_impl::get_gpio_banks() const +{ + return {RHODIUM_FPGPIO_BANK}; +} + +void rhodium_radio_control_impl::set_gpio_attr( + const std::string& bank, const std::string& attr, const uint32_t value) +{ + if (bank != RHODIUM_FPGPIO_BANK) { + RFNOC_LOG_ERROR("Invalid GPIO bank: " << bank); + throw uhd::key_error("Invalid GPIO bank!"); + } + if (!gpio_atr::gpio_attr_rev_map.count(attr)) { + RFNOC_LOG_ERROR("Invalid GPIO attr: " << attr); + throw uhd::key_error("Invalid GPIO attr!"); + } + + const gpio_atr::gpio_attr_t gpio_attr = gpio_atr::gpio_attr_rev_map.at(attr); + + if (gpio_attr == gpio_atr::GPIO_READBACK) { + RFNOC_LOG_WARNING("Cannot set READBACK attr."); + return; + } + + _fp_gpio->set_gpio_attr(gpio_attr, value); +} + +uint32_t rhodium_radio_control_impl::get_gpio_attr( + const std::string& bank, const std::string& attr) +{ + if (bank != RHODIUM_FPGPIO_BANK) { + RFNOC_LOG_ERROR("Invalid GPIO bank: " << bank); + throw uhd::key_error("Invalid GPIO bank!"); + } + + return _fp_gpio->get_attr_reg(usrp::gpio_atr::gpio_attr_rev_map.at(attr)); +} + +/****************************************************************************** + * EEPROM API + *****************************************************************************/ +void rhodium_radio_control_impl::set_db_eeprom(const eeprom_map_t& db_eeprom) +{ + const size_t db_idx = get_block_id().get_block_count(); + _rpcc->notify_with_token("set_db_eeprom", db_idx, db_eeprom); + _db_eeprom = this->_rpcc->request_with_token<eeprom_map_t>("get_db_eeprom", db_idx); +} + +eeprom_map_t rhodium_radio_control_impl::get_db_eeprom() +{ + return _db_eeprom; +} + +/************************************************************************** + * Sensor API + *************************************************************************/ +std::vector<std::string> rhodium_radio_control_impl::get_rx_sensor_names(size_t) const +{ + return _rx_sensor_names; +} + +sensor_value_t rhodium_radio_control_impl::get_rx_sensor( + const std::string& name, size_t chan) +{ + if (!uhd::has(_rx_sensor_names, name)) { + RFNOC_LOG_ERROR("Invalid RX sensor name: " << name); + throw uhd::key_error("Invalid RX sensor name!"); + } + if (name == "lo_locked") { + return sensor_value_t( + "all_los", this->get_lo_lock_status(RX_DIRECTION), "locked", "unlocked"); + } + return sensor_value_t(_rpcc->request_with_token<sensor_value_t::sensor_map_t>( + _rpc_prefix + "get_sensor", "RX", name, chan)); +} + +std::vector<std::string> rhodium_radio_control_impl::get_tx_sensor_names(size_t) const +{ + return _tx_sensor_names; +} + +sensor_value_t rhodium_radio_control_impl::get_tx_sensor( + const std::string& name, size_t chan) +{ + if (!uhd::has(_rx_sensor_names, name)) { + RFNOC_LOG_ERROR("Invalid RX sensor name: " << name); + throw uhd::key_error("Invalid RX sensor name!"); + } + if (name == "lo_locked") { + return sensor_value_t( + "all_los", this->get_lo_lock_status(TX_DIRECTION), "locked", "unlocked"); + } + return sensor_value_t(_rpcc->request_with_token<sensor_value_t::sensor_map_t>( + _rpc_prefix + "get_sensor", "TX", name, chan)); +} + +bool rhodium_radio_control_impl::get_lo_lock_status(const direction_t dir) const +{ + return (dir == RX_DIRECTION) ? _rx_lo->get_lock_status() : _tx_lo->get_lock_status(); +} + +/************************************************************************** + * node_t API Calls + *************************************************************************/ +void rhodium_radio_control_impl::set_command_time( + uhd::time_spec_t time, const size_t chan) +{ + UHD_ASSERT_THROW(chan == 0); + node_t::set_command_time(time, chan); + _wb_iface->set_time(time); +} + +// Register the block +UHD_RFNOC_BLOCK_REGISTER_FOR_DEVICE_DIRECT( + rhodium_radio_control, RADIO_BLOCK, N320, "Radio", true, "radio_clk", "bus_clk"); diff --git a/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_impl.hpp b/host/lib/usrp/dboard/rhodium/rhodium_radio_control.hpp index fad987b98..a70db79cc 100644 --- a/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_impl.hpp +++ b/host/lib/usrp/dboard/rhodium/rhodium_radio_control.hpp @@ -1,5 +1,6 @@ // // Copyright 2018 Ettus Research, a National Instruments Company +// Copyright 2019 Ettus Research, a National Instruments Brand // // SPDX-License-Identifier: GPL-3.0-or-later // @@ -7,28 +8,28 @@ #ifndef INCLUDED_LIBUHD_RFNOC_RHODIUM_RADIO_CTRL_IMPL_HPP #define INCLUDED_LIBUHD_RFNOC_RHODIUM_RADIO_CTRL_IMPL_HPP +#include "rhodium_constants.hpp" #include "rhodium_cpld_ctrl.hpp" #include "rhodium_cpld_regs.hpp" +#include <uhd/types/serial.hpp> +#include <uhd/usrp/gpio_defs.hpp> +#include <uhdlib/rfnoc/radio_control_impl.hpp> +#include <uhdlib/rfnoc/rpc_block_ctrl.hpp> #include <uhdlib/usrp/common/lmx2592.hpp> +#include <uhdlib/usrp/common/mpmd_mb_controller.hpp> #include <uhdlib/usrp/cores/gpio_atr_3000.hpp> -#include <uhdlib/rfnoc/rpc_block_ctrl.hpp> -#include <uhdlib/rfnoc/radio_ctrl_impl.hpp> #include <uhdlib/usrp/cores/rx_frontend_core_3000.hpp> #include <uhdlib/usrp/cores/tx_frontend_core_200.hpp> -#include <uhd/types/serial.hpp> -#include <uhd/usrp/dboard_manager.hpp> -#include <uhd/usrp/gpio_defs.hpp> #include <mutex> -namespace uhd { - namespace rfnoc { +namespace uhd { namespace rfnoc { /*! \brief Provide access to an Rhodium radio. */ -class rhodium_radio_ctrl_impl : public radio_ctrl_impl, public rpc_block_ctrl +class rhodium_radio_control_impl : public radio_control_impl { public: - typedef boost::shared_ptr<rhodium_radio_ctrl_impl> sptr; + typedef boost::shared_ptr<rhodium_radio_control_impl> sptr; //! Frequency bands for RX. Bands are a function of the analog filter banks enum class rx_band { @@ -59,54 +60,108 @@ public: /************************************************************************ * Structors ***********************************************************************/ - UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR_DECL(rhodium_radio_ctrl) - virtual ~rhodium_radio_ctrl_impl(); + rhodium_radio_control_impl(make_args_ptr make_args); + virtual ~rhodium_radio_control_impl(); /************************************************************************ - * API calls + * RF API calls ***********************************************************************/ // Note: We use the cached values in radio_ctrl_impl, so most getters are // not reimplemented here - double set_rate(const double rate); + double set_rate(double rate); + // Setters void set_tx_antenna(const std::string &ant, const size_t chan); void set_rx_antenna(const std::string &ant, const size_t chan); - double set_tx_frequency(const double freq, const size_t chan); double set_rx_frequency(const double freq, const size_t chan); - - double set_tx_bandwidth(const double bandwidth, const size_t chan); - double set_rx_bandwidth(const double bandwidth, const size_t chan); - + void set_tx_tune_args(const uhd::device_addr_t&, const size_t chan); + void set_rx_tune_args(const uhd::device_addr_t&, const size_t chan); double set_tx_gain(const double gain, const size_t chan); double set_rx_gain(const double gain, const size_t chan); - // LO Property Getters - std::vector<std::string> get_tx_lo_names(const size_t chan); - std::vector<std::string> get_rx_lo_names(const size_t chan); - std::vector<std::string> get_tx_lo_sources(const std::string& name, const size_t chan); - std::vector<std::string> get_rx_lo_sources(const std::string& name, const size_t chan); - freq_range_t get_tx_lo_freq_range(const std::string& name, const size_t chan); - freq_range_t get_rx_lo_freq_range(const std::string& name, const size_t chan); + // Getters + std::vector<std::string> get_tx_antennas(const size_t) const; + std::vector<std::string> get_rx_antennas(const size_t) const; + uhd::freq_range_t get_tx_frequency_range(const size_t) const; + uhd::freq_range_t get_rx_frequency_range(const size_t) const; + uhd::gain_range_t get_tx_gain_range(const size_t) const; + uhd::gain_range_t get_rx_gain_range(const size_t) const; + uhd::meta_range_t get_tx_bandwidth_range(size_t) const; + uhd::meta_range_t get_rx_bandwidth_range(size_t) const; - // LO Frequency Control - double set_tx_lo_freq(const double freq, const std::string& name, const size_t chan); - double set_rx_lo_freq(const double freq, const std::string& name, const size_t chan); - double get_tx_lo_freq(const std::string& name, const size_t chan); + /************************************************************************** + * LO Controls + *************************************************************************/ + std::vector<std::string> get_rx_lo_names(const size_t chan) const; + std::vector<std::string> get_rx_lo_sources( + const std::string& name, const size_t chan) const; + freq_range_t get_rx_lo_freq_range(const std::string& name, const size_t chan) const; + void set_rx_lo_source( + const std::string& src, const std::string& name, const size_t chan); + const std::string get_rx_lo_source(const std::string& name, const size_t chan); + double set_rx_lo_freq(double freq, const std::string& name, const size_t chan); double get_rx_lo_freq(const std::string& name, const size_t chan); - - // LO Source Control - void set_tx_lo_source(const std::string& src, const std::string& name, const size_t chan); - void set_rx_lo_source(const std::string& src, const std::string& name, const size_t chan); + std::vector<std::string> get_tx_lo_names(const size_t chan) const; + std::vector<std::string> get_tx_lo_sources( + const std::string& name, const size_t chan) const; + freq_range_t get_tx_lo_freq_range(const std::string& name, const size_t chan); + void set_tx_lo_source( + const std::string& src, const std::string& name, const size_t chan); const std::string get_tx_lo_source(const std::string& name, const size_t chan); - const std::string get_rx_lo_source(const std::string& name, const size_t chan); - + double set_tx_lo_freq(const double freq, const std::string& name, const size_t chan); + double get_tx_lo_freq(const std::string& name, const size_t chan); // LO Export Control - void set_tx_lo_export_enabled(const bool enabled, const std::string& name, const size_t chan); - void set_rx_lo_export_enabled(const bool enabled, const std::string& name, const size_t chan); + void set_tx_lo_export_enabled( + const bool enabled, const std::string& name, const size_t chan); + void set_rx_lo_export_enabled( + const bool enabled, const std::string& name, const size_t chan); bool get_tx_lo_export_enabled(const std::string& name, const size_t chan); bool get_rx_lo_export_enabled(const std::string& name, const size_t chan); + /************************************************************************** + * GPIO Controls + *************************************************************************/ + std::vector<std::string> get_gpio_banks() const; + void set_gpio_attr( + const std::string& bank, const std::string& attr, const uint32_t value); + uint32_t get_gpio_attr(const std::string& bank, const std::string& attr); + + /************************************************************************** + * EEPROM API + *************************************************************************/ + void set_db_eeprom(const uhd::eeprom_map_t& db_eeprom); + uhd::eeprom_map_t get_db_eeprom(); + + /************************************************************************** + * Sensor API + *************************************************************************/ + std::vector<std::string> get_rx_sensor_names(size_t chan) const; + uhd::sensor_value_t get_rx_sensor(const std::string& name, size_t chan); + std::vector<std::string> get_tx_sensor_names(size_t chan) const; + uhd::sensor_value_t get_tx_sensor(const std::string& name, size_t chan); + + /************************************************************************** + * Radio Identification API Calls + *************************************************************************/ + std::string get_slot_name() const + { + return _radio_slot; + } + size_t get_chan_from_dboard_fe( + const std::string& fe, const uhd::direction_t direction) const; + std::string get_dboard_fe_from_chan( + const size_t chan, const uhd::direction_t direction) const; + std::string get_fe_name(const size_t chan, const uhd::direction_t direction) const; + + /************************************************************************** + * node_t API Calls + *************************************************************************/ + void set_command_time(uhd::time_spec_t time, const size_t chan); + + /************************************************************************ + * ??? calls + ***********************************************************************/ // LO Distribution Control void set_tx_lo_output_enabled(const bool enabled, const std::string& port_name, const size_t chan); void set_rx_lo_output_enabled(const bool enabled, const std::string& port_name, const size_t chan); @@ -139,38 +194,49 @@ public: double get_tx_lo_power(const std::string &name, const size_t chan); double get_rx_lo_power(const std::string &name, const size_t chan); - size_t get_chan_from_dboard_fe(const std::string &fe, const direction_t dir); - std::string get_dboard_fe_from_chan(const size_t chan, const direction_t dir); - - void set_rpc_client( - uhd::rpc_client::sptr rpcc, - const uhd::device_addr_t &block_args - ); private: /************************************************************************** + * noc_block_base API + *************************************************************************/ + //! Safely shut down all peripherals + // + // Reminder: After this is called, no peeks and pokes are allowed! + void deinit() + { + RFNOC_LOG_TRACE("deinit()"); + // Remove access to all peripherals + _wb_iface.reset(); + _spi.reset(); + _tx_lo.reset(); + _rx_lo.reset(); + _cpld.reset(); + _gpio.reset(); + _fp_gpio.reset(); + _rx_fe_core.reset(); + _tx_fe_core.reset(); + } + + /************************************************************************** * Helpers *************************************************************************/ //! Initialize all the peripherals connected to this block void _init_peripherals(); + //! Sync up with MPM + void _init_mpm(); + //! Set state of this class to sensible defaults void _init_defaults(); //! Init a subtree for the RF frontends - void _init_frontend_subtree( - uhd::property_tree::sptr subtree, - const size_t chan_idx - ); + void _init_frontend_subtree(uhd::property_tree::sptr subtree); //! Initialize property tree void _init_prop_tree(); //! Discover and initialize any mpm sensors - void _init_mpm_sensors( - const direction_t dir, - const size_t chan_idx - ); + void _init_mpm_sensors(const direction_t dir, const size_t chan_idx); //! Get the frequency range for an LO freq_range_t _get_lo_freq_range(const std::string &name) const; @@ -235,8 +301,6 @@ private: // NOTE: Returns false if frequency is out of Rh's tx frequency range static bool _is_tx_lowband(const double freq); - //! Return the gain range for dir - static uhd::gain_range_t _get_gain_range(const direction_t dir); //! Return the gain range of the LMX LO static uhd::gain_range_t _get_lo_gain_range(); //! Return the power setting range of the LMX LO @@ -303,12 +367,18 @@ private: //! Daughterboard info from MPM std::map<std::string, std::string> _dboard_info; - //! Additional block args; gets set during set_rpc_client() - uhd::device_addr_t _block_args; + //! Reference to the MB controller + mpmd_mb_controller::sptr _n320_mb_control; + + //! Reference to the MB timekeeper + uhd::rfnoc::mpmd_mb_controller::mpmd_timekeeper::sptr _n3xx_timekeeper; //! Reference to the RPC client uhd::rpc_client::sptr _rpcc; + //! Reference to wb_iface compat adapter. This will call into this->regs() + uhd::timed_wb_iface::sptr _wb_iface; + //! Reference to the SPI core uhd::spi_iface::sptr _spi; @@ -343,7 +413,8 @@ private: std::string _rx_fe_connection; std::string _tx_fe_connection; //! Desired RF frequency - std::map<direction_t,double> _desired_rf_freq = { {RX_DIRECTION, 2.44e9}, {TX_DIRECTION, 2.44e9} }; + std::map<direction_t, double> _desired_rf_freq = { + {RX_DIRECTION, 2.44e9}, {TX_DIRECTION, 2.44e9}}; //! Frequency at which gain setting was last applied. The CPLD requires a new gain // control write when switching between lowband and highband frequencies, so save // the frequency when sending a gain control command. @@ -356,7 +427,8 @@ private: double _lo_rx_power = 0.0; double _lo_tx_power = 0.0; //! Gain profile - std::map<direction_t,std::string> _gain_profile = { {RX_DIRECTION, "default"}, {TX_DIRECTION, "default"} }; + std::map<direction_t, std::string> _gain_profile = { + {RX_DIRECTION, "default"}, {TX_DIRECTION, "default"}}; //! LO source std::string _rx_lo_source = "internal"; @@ -377,7 +449,30 @@ private: bool _lo_dist_rx_out_enabled[4] = { false, false, false, false }; bool _lo_dist_tx_out_enabled[4] = { false, false, false, false }; -}; /* class radio_ctrl_impl */ + std::unordered_map<uhd::direction_t, uhd::device_addr_t, std::hash<size_t>> + _tune_args{{uhd::RX_DIRECTION, uhd::device_addr_t()}, + {uhd::TX_DIRECTION, uhd::device_addr_t()}}; + + //! Cache the contents of the DB EEPROM + uhd::eeprom_map_t _db_eeprom; + + //! Cached list of RX sensor names + std::vector<std::string> _rx_sensor_names{"lo_locked"}; + //! Cached list of TX sensor names + std::vector<std::string> _tx_sensor_names{"lo_locked"}; + + property_t<std::string> _spur_dodging_mode{SPUR_DODGING_PROP_NAME, + RHODIUM_DEFAULT_SPUR_DOGING_MODE, + {res_source_info::USER}}; + property_t<double> _spur_dodging_threshold{SPUR_DODGING_THRESHOLD_PROP_NAME, + RHODIUM_DEFAULT_SPUR_DOGING_THRESHOLD, + {res_source_info::USER}}; + property_t<std::string> _highband_spur_reduction_mode{ + HIGHBAND_SPUR_REDUCTION_PROP_NAME, + RHODIUM_DEFAULT_HB_SPUR_REDUCTION_MODE, + {res_source_info::USER}}; + +}; /* class rhodium_radio_control_impl */ }} /* namespace uhd::rfnoc */ diff --git a/host/lib/usrp/dboard/rhodium/rhodium_radio_control_cpld.cpp b/host/lib/usrp/dboard/rhodium/rhodium_radio_control_cpld.cpp new file mode 100644 index 000000000..f4f17e8ab --- /dev/null +++ b/host/lib/usrp/dboard/rhodium/rhodium_radio_control_cpld.cpp @@ -0,0 +1,252 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "rhodium_constants.hpp" +#include "rhodium_cpld_ctrl.hpp" +#include "rhodium_radio_control.hpp" +#include <uhd/utils/log.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; + +namespace { + +const char* rx_band_to_log(rhodium_radio_control_impl::rx_band rx_band) +{ + switch (rx_band) { + case rhodium_radio_control_impl::rx_band::RX_BAND_0: + return "0"; + case rhodium_radio_control_impl::rx_band::RX_BAND_1: + return "1"; + case rhodium_radio_control_impl::rx_band::RX_BAND_2: + return "2"; + case rhodium_radio_control_impl::rx_band::RX_BAND_3: + return "3"; + case rhodium_radio_control_impl::rx_band::RX_BAND_4: + return "4"; + case rhodium_radio_control_impl::rx_band::RX_BAND_5: + return "5"; + case rhodium_radio_control_impl::rx_band::RX_BAND_6: + return "6"; + case rhodium_radio_control_impl::rx_band::RX_BAND_7: + return "7"; + case rhodium_radio_control_impl::rx_band::RX_BAND_INVALID: + return "INVALID"; + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +const char* tx_band_to_log(rhodium_radio_control_impl::tx_band tx_band) +{ + switch (tx_band) { + case rhodium_radio_control_impl::tx_band::TX_BAND_0: + return "0"; + case rhodium_radio_control_impl::tx_band::TX_BAND_1: + return "1"; + case rhodium_radio_control_impl::tx_band::TX_BAND_2: + return "2"; + case rhodium_radio_control_impl::tx_band::TX_BAND_3: + return "3"; + case rhodium_radio_control_impl::tx_band::TX_BAND_4: + return "4"; + case rhodium_radio_control_impl::tx_band::TX_BAND_5: + return "5"; + case rhodium_radio_control_impl::tx_band::TX_BAND_6: + return "6"; + case rhodium_radio_control_impl::tx_band::TX_BAND_7: + return "7"; + case rhodium_radio_control_impl::tx_band::TX_BAND_INVALID: + return "INVALID"; + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} +} // namespace + +void rhodium_radio_control_impl::_update_rx_freq_switches(const double freq) +{ + RFNOC_LOG_TRACE("Update all RX freq related switches. f=" << freq << " Hz"); + const auto band = _map_freq_to_rx_band(freq); + RFNOC_LOG_TRACE("Selected band " << rx_band_to_log(band)); + + // select values for lowband/highband switches + const bool is_lowband = (band == rx_band::RX_BAND_0); + auto rx_sw2_sw7 = is_lowband ? rhodium_cpld_ctrl::RX_SW2_SW7_LOWBANDFILTERBANK + : rhodium_cpld_ctrl::RX_SW2_SW7_HIGHBANDFILTERBANK; + auto rx_hb_lb_sel = is_lowband ? rhodium_cpld_ctrl::RX_HB_LB_SEL_LOWBAND + : rhodium_cpld_ctrl::RX_HB_LB_SEL_HIGHBAND; + + // select values for filter bank switches + rhodium_cpld_ctrl::rx_sw3_t rx_sw3; + rhodium_cpld_ctrl::rx_sw4_sw5_t rx_sw4_sw5; + rhodium_cpld_ctrl::rx_sw6_t rx_sw6; + switch (band) { + case rx_band::RX_BAND_0: + // Low band doesn't use the filter banks, use configuration for band 1 + case rx_band::RX_BAND_1: + rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOSWITCH4; + rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0450X0760MHZ; + rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMSWITCH5; + break; + case rx_band::RX_BAND_2: + rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOSWITCH4; + rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0760X1100MHZ; + rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMSWITCH5; + break; + case rx_band::RX_BAND_3: + rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOSWITCH4; + rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER1100X1410MHZ; + rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMSWITCH5; + break; + case rx_band::RX_BAND_4: + rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOSWITCH4; + rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER1410X2050MHZ; + rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMSWITCH5; + break; + case rx_band::RX_BAND_5: + rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOFILTER2050X3000MHZ; + rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0450X0760MHZ; + rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMFILTER2050X3000MHZ; + break; + case rx_band::RX_BAND_6: + rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOFILTER3000X4500MHZ; + rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0450X0760MHZ; + rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMFILTER3000X4500MHZ; + break; + case rx_band::RX_BAND_7: + rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOFILTER4500X6000MHZ; + rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0450X0760MHZ; + rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMFILTER4500X6000MHZ; + break; + case rx_band::RX_BAND_INVALID: + throw uhd::runtime_error( + str(boost::format("Cannot map RX frequency to band: %f") % freq)); + default: + UHD_THROW_INVALID_CODE_PATH(); + } + + // commit settings to cpld + _cpld->set_rx_switches(rx_sw2_sw7, rx_sw3, rx_sw4_sw5, rx_sw6, rx_hb_lb_sel); +} + +void rhodium_radio_control_impl::_update_tx_freq_switches(const double freq) +{ + RFNOC_LOG_TRACE("Update all TX freq related switches. f=" << freq << " Hz"); + const auto band = _map_freq_to_tx_band(freq); + RFNOC_LOG_TRACE("Selected band " << tx_band_to_log(band)); + + // select values for lowband/highband switches + const bool is_lowband = (band == tx_band::TX_BAND_0); + auto tx_hb_lb_sel = is_lowband ? rhodium_cpld_ctrl::TX_HB_LB_SEL_LOWBAND + : rhodium_cpld_ctrl::TX_HB_LB_SEL_HIGHBAND; + + // select values for filter bank switches + rhodium_cpld_ctrl::tx_sw2_t tx_sw2; + rhodium_cpld_ctrl::tx_sw3_sw4_t tx_sw3_sw4; + rhodium_cpld_ctrl::tx_sw5_t tx_sw5; + switch (band) { + case tx_band::TX_BAND_0: + // Low band doesn't use the filter banks, use configuration for band 1 + case tx_band::TX_BAND_1: + tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMSWITCH3; + tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP0650MHZ; + tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOSWITCH4; + break; + case tx_band::TX_BAND_2: + tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMSWITCH3; + tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP1000MHZ; + tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOSWITCH4; + break; + case tx_band::TX_BAND_3: + tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMSWITCH3; + tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP1350MHZ; + tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOSWITCH4; + break; + case tx_band::TX_BAND_4: + tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMSWITCH3; + tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP1900MHZ; + tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOSWITCH4; + break; + case tx_band::TX_BAND_5: + tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMTXFILTERLP3000MHZ; + tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP0650MHZ; + tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOTXFILTERLP3000MHZ; + break; + case tx_band::TX_BAND_6: + tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMTXFILTERLP4100MHZ; + tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP0650MHZ; + tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOTXFILTERLP4100MHZ; + break; + case tx_band::TX_BAND_7: + tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMTXFILTERLP6000MHZ; + tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP0650MHZ; + tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOTXFILTERLP6000MHZ; + break; + case tx_band::TX_BAND_INVALID: + throw uhd::runtime_error( + str(boost::format("Cannot map TX frequency to band: %f") % freq)); + default: + UHD_THROW_INVALID_CODE_PATH(); + } + + // commit settings to cpld + _cpld->set_tx_switches(tx_sw2, tx_sw3_sw4, tx_sw5, tx_hb_lb_sel); +} + +void rhodium_radio_control_impl::_update_rx_input_switches(const std::string& input) +{ + RFNOC_LOG_TRACE("Update all RX input related switches. input=" << input); + const rhodium_cpld_ctrl::cal_iso_sw_t cal_iso = + (input == "CAL") ? rhodium_cpld_ctrl::CAL_ISO_CALLOOPBACK + : rhodium_cpld_ctrl::CAL_ISO_ISOLATION; + const rhodium_cpld_ctrl::rx_sw1_t sw1 = [input] { + if (input == "TX/RX") { + return rhodium_cpld_ctrl::RX_SW1_FROMTXRXINPUT; + } else if (input == "RX2") { + return rhodium_cpld_ctrl::RX_SW1_FROMRX2INPUT; + } else if (input == "CAL") { + return rhodium_cpld_ctrl::RX_SW1_FROMCALLOOPBACK; + } else if (input == "TERM") { + return rhodium_cpld_ctrl::RX_SW1_ISOLATION; + } else { + throw uhd::runtime_error( + "Invalid antenna in _update_rx_input_switches: " + input); + } + }(); + + RFNOC_LOG_TRACE("Selected switch values:" + " sw1=" + << sw1 << " cal_iso=" << cal_iso); + _cpld->set_rx_input_switches(sw1, cal_iso); +} + +void rhodium_radio_control_impl::_update_tx_output_switches(const std::string& output) +{ + RFNOC_LOG_TRACE("Update all TX output related switches. output=" << output); + rhodium_cpld_ctrl::tx_sw1_t sw1; + + if (output == "TX/RX") { + // SW1 needs to select low/high band + if (_is_tx_lowband(get_tx_frequency(0))) { + sw1 = rhodium_cpld_ctrl::TX_SW1_TOLOWBAND; + } else { + sw1 = rhodium_cpld_ctrl::TX_SW1_TOSWITCH2; + } + } else if (output == "CAL") { + sw1 = rhodium_cpld_ctrl::TX_SW1_TOCALLOOPBACK; + } else if (output == "TERM") { + sw1 = rhodium_cpld_ctrl::TX_SW1_ISOLATION; + } else { + throw uhd::runtime_error( + "Invalid antenna in _update_tx_output_switches: " + output); + } + + RFNOC_LOG_TRACE("Selected switch values: sw1=" << sw1); + _cpld->set_tx_output_switches(sw1); +} diff --git a/host/lib/usrp/dboard/rhodium/rhodium_radio_control_init.cpp b/host/lib/usrp/dboard/rhodium/rhodium_radio_control_init.cpp new file mode 100644 index 000000000..d6b7afd09 --- /dev/null +++ b/host/lib/usrp/dboard/rhodium/rhodium_radio_control_init.cpp @@ -0,0 +1,611 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "rhodium_constants.hpp" +#include "rhodium_radio_control.hpp" +#include <uhd/transport/chdr.hpp> +#include <uhd/types/eeprom.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/log.hpp> +#include <uhdlib/rfnoc/reg_iface_adapter.hpp> +#include <uhdlib/usrp/common/mpmd_mb_controller.hpp> +#include <uhdlib/usrp/cores/spi_core_3000.hpp> +#include <string> +#include <vector> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; + +namespace { +enum slave_select_t { + SEN_CPLD = 8, + SEN_TX_LO = 1, + SEN_RX_LO = 2, + SEN_LO_DIST = 4 /* Unused */ +}; + +constexpr double RHODIUM_DEFAULT_FREQ = 2.5e9; // Hz +// An invalid default index ensures that set gain will apply settings +// the first time it is called +constexpr double RHODIUM_DEFAULT_INVALID_GAIN = -1; // gain index +constexpr double RHODIUM_DEFAULT_GAIN = 0; // gain index +constexpr double RHODIUM_DEFAULT_LO_GAIN = 30; // gain index +constexpr char RHODIUM_DEFAULT_RX_ANTENNA[] = "RX2"; +constexpr char RHODIUM_DEFAULT_TX_ANTENNA[] = "TX/RX"; +constexpr auto RHODIUM_DEFAULT_MASH_ORDER = lmx2592_iface::mash_order_t::THIRD; + +//! Returns the SPI config used by the CPLD +spi_config_t _get_cpld_spi_config() +{ + spi_config_t spi_config; + spi_config.use_custom_divider = true; + spi_config.divider = 10; + spi_config.mosi_edge = spi_config_t::EDGE_RISE; + spi_config.miso_edge = spi_config_t::EDGE_FALL; + + return spi_config; +} + +//! Returns the SPI config used by the TX LO +spi_config_t _get_tx_lo_spi_config() +{ + spi_config_t spi_config; + spi_config.use_custom_divider = true; + spi_config.divider = 10; + spi_config.mosi_edge = spi_config_t::EDGE_RISE; + spi_config.miso_edge = spi_config_t::EDGE_FALL; + + return spi_config; +} + +//! Returns the SPI config used by the RX LO +spi_config_t _get_rx_lo_spi_config() +{ + spi_config_t spi_config; + spi_config.use_custom_divider = true; + spi_config.divider = 10; + spi_config.mosi_edge = spi_config_t::EDGE_RISE; + spi_config.miso_edge = spi_config_t::EDGE_FALL; + + return spi_config; +} + +std::function<void(uint32_t)> _generate_write_spi( + uhd::spi_iface::sptr spi, slave_select_t slave, spi_config_t config) +{ + return [spi, slave, config](const uint32_t transaction) { + spi->write_spi(slave, config, transaction, 24); + }; +} + +std::function<uint32_t(uint32_t)> _generate_read_spi( + uhd::spi_iface::sptr spi, slave_select_t slave, spi_config_t config) +{ + return [spi, slave, config](const uint32_t transaction) { + return spi->read_spi(slave, config, transaction, 24); + }; +} +} // namespace + +void rhodium_radio_control_impl::_init_defaults() +{ + RFNOC_LOG_TRACE("Initializing defaults..."); + const size_t num_rx_chans = get_num_output_ports(); + const size_t num_tx_chans = get_num_input_ports(); + UHD_ASSERT_THROW(num_tx_chans == RHODIUM_NUM_CHANS); + UHD_ASSERT_THROW(num_rx_chans == RHODIUM_NUM_CHANS); + + for (size_t chan = 0; chan < num_rx_chans; chan++) { + radio_control_impl::set_rx_frequency(RHODIUM_DEFAULT_FREQ, chan); + radio_control_impl::set_rx_gain(RHODIUM_DEFAULT_INVALID_GAIN, chan); + radio_control_impl::set_rx_antenna(RHODIUM_DEFAULT_RX_ANTENNA, chan); + radio_control_impl::set_rx_bandwidth(RHODIUM_DEFAULT_BANDWIDTH, chan); + } + + for (size_t chan = 0; chan < num_tx_chans; chan++) { + radio_control_impl::set_tx_frequency(RHODIUM_DEFAULT_FREQ, chan); + radio_control_impl::set_tx_gain(RHODIUM_DEFAULT_INVALID_GAIN, chan); + radio_control_impl::set_tx_antenna(RHODIUM_DEFAULT_TX_ANTENNA, chan); + radio_control_impl::set_tx_bandwidth(RHODIUM_DEFAULT_BANDWIDTH, chan); + } + + register_property(&_spur_dodging_mode); + register_property(&_spur_dodging_threshold); + register_property(&_highband_spur_reduction_mode); + + // Update configurable block arguments from the device arguments provided + const auto block_args = get_block_args(); + if (block_args.has_key(SPUR_DODGING_PROP_NAME)) { + _spur_dodging_mode.set(block_args.get(SPUR_DODGING_PROP_NAME)); + } + if (block_args.has_key(SPUR_DODGING_THRESHOLD_PROP_NAME)) { + _spur_dodging_threshold.set(block_args.cast<double>( + SPUR_DODGING_THRESHOLD_PROP_NAME, RHODIUM_DEFAULT_SPUR_DOGING_THRESHOLD)); + } + if (block_args.has_key(HIGHBAND_SPUR_REDUCTION_PROP_NAME)) { + _highband_spur_reduction_mode.set( + block_args.get(HIGHBAND_SPUR_REDUCTION_PROP_NAME)); + } +} + +void rhodium_radio_control_impl::_init_peripherals() +{ + RFNOC_LOG_TRACE("Initializing SPI core..."); + _spi = spi_core_3000::make( + [this](uint32_t addr, uint32_t data) { + regs().poke32(addr, data, get_command_time(0)); + }, + [this](uint32_t addr) { return regs().peek32(addr, get_command_time(0)); }, + regmap::REG_SPI_W, + 8, + regmap::REG_SPI_R); + _wb_iface = RFNOC_MAKE_WB_IFACE(0, 0); + + RFNOC_LOG_TRACE("Initializing CPLD..."); + _cpld = std::make_shared<rhodium_cpld_ctrl>( + _generate_write_spi(this->_spi, SEN_CPLD, _get_cpld_spi_config()), + _generate_read_spi(this->_spi, SEN_CPLD, _get_cpld_spi_config())); + + RFNOC_LOG_TRACE("Initializing TX frontend DSP core...") + _tx_fe_core = tx_frontend_core_200::make(_wb_iface, n320_regs::SR_TX_FE_BASE); + _tx_fe_core->set_dc_offset(tx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); + _tx_fe_core->set_iq_balance(tx_frontend_core_200::DEFAULT_IQ_BALANCE_VALUE); + _tx_fe_core->populate_subtree(get_tree()->subtree(FE_PATH / "tx_fe_corrections" / 0)); + + RFNOC_LOG_TRACE("Initializing RX frontend DSP core...") + _rx_fe_core = rx_frontend_core_3000::make(_wb_iface, n320_regs::SR_TX_FE_BASE); + _rx_fe_core->set_adc_rate(_master_clock_rate); + _rx_fe_core->set_dc_offset(rx_frontend_core_3000::DEFAULT_DC_OFFSET_VALUE); + _rx_fe_core->set_dc_offset_auto(rx_frontend_core_3000::DEFAULT_DC_OFFSET_ENABLE); + _rx_fe_core->set_iq_balance(rx_frontend_core_3000::DEFAULT_IQ_BALANCE_VALUE); + _rx_fe_core->populate_subtree(get_tree()->subtree(FE_PATH / "rx_fe_corrections" / 0)); + + RFNOC_LOG_TRACE("Writing initial gain values..."); + set_tx_gain(RHODIUM_DEFAULT_GAIN, 0); + set_tx_lo_gain(RHODIUM_DEFAULT_LO_GAIN, RHODIUM_LO1, 0); + set_rx_gain(RHODIUM_DEFAULT_GAIN, 0); + set_rx_lo_gain(RHODIUM_DEFAULT_LO_GAIN, RHODIUM_LO1, 0); + + RFNOC_LOG_TRACE("Initializing TX LO..."); + _tx_lo = lmx2592_iface::make( + _generate_write_spi(this->_spi, SEN_TX_LO, _get_tx_lo_spi_config()), + _generate_read_spi(this->_spi, SEN_TX_LO, _get_tx_lo_spi_config())); + + RFNOC_LOG_TRACE("Writing initial TX LO state..."); + _tx_lo->set_reference_frequency(RHODIUM_LO1_REF_FREQ); + _tx_lo->set_mash_order(RHODIUM_DEFAULT_MASH_ORDER); + + RFNOC_LOG_TRACE("Initializing RX LO..."); + _rx_lo = lmx2592_iface::make( + _generate_write_spi(this->_spi, SEN_RX_LO, _get_rx_lo_spi_config()), + _generate_read_spi(this->_spi, SEN_RX_LO, _get_rx_lo_spi_config())); + + RFNOC_LOG_TRACE("Writing initial RX LO state..."); + _rx_lo->set_reference_frequency(RHODIUM_LO1_REF_FREQ); + _rx_lo->set_mash_order(RHODIUM_DEFAULT_MASH_ORDER); + + RFNOC_LOG_TRACE("Initializing GPIOs..."); + // DB GPIOs + _gpio = usrp::gpio_atr::gpio_atr_3000::make(_wb_iface, + n320_regs::SR_DB_GPIO, + n320_regs::RB_DB_GPIO, + n320_regs::PERIPH_REG_OFFSET); + _gpio->set_atr_mode(usrp::gpio_atr::MODE_ATR, // Enable ATR mode for Rhodium bits + RHODIUM_GPIO_MASK); + _gpio->set_atr_mode(usrp::gpio_atr::MODE_GPIO, // Disable ATR mode for unused bits + ~RHODIUM_GPIO_MASK); + _gpio->set_gpio_ddr(usrp::gpio_atr::DDR_OUTPUT, // Make all GPIOs outputs + usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL); + _fp_gpio = gpio_atr::gpio_atr_3000::make(_wb_iface, + n320_regs::SR_FP_GPIO, + n320_regs::RB_FP_GPIO, + n320_regs::PERIPH_REG_OFFSET); + + RFNOC_LOG_TRACE("Set initial ATR values..."); + _update_atr(RHODIUM_DEFAULT_TX_ANTENNA, TX_DIRECTION); + _update_atr(RHODIUM_DEFAULT_RX_ANTENNA, RX_DIRECTION); + + // Updating the TX frequency path may include an update to SW10, which is + // GPIO controlled, so this must follow CPLD and GPIO initialization + RFNOC_LOG_TRACE("Writing initial switch values..."); + _update_tx_freq_switches(RHODIUM_DEFAULT_FREQ); + _update_rx_freq_switches(RHODIUM_DEFAULT_FREQ); + + // Antenna setting requires both CPLD and GPIO control + RFNOC_LOG_TRACE("Setting initial antenna settings"); + _update_tx_output_switches(RHODIUM_DEFAULT_TX_ANTENNA); + _update_rx_input_switches(RHODIUM_DEFAULT_RX_ANTENNA); + + RFNOC_LOG_TRACE("Checking for existence of LO Distribution board"); + _lo_dist_present = + _rpcc->request_with_token<bool>(_rpc_prefix + "is_lo_dist_present"); + RFNOC_LOG_DEBUG( + "LO distribution board is" << (_lo_dist_present ? "" : " NOT") << " present"); + + RFNOC_LOG_TRACE("Reading EEPROM content..."); + const size_t db_idx = get_block_id().get_block_count(); + _db_eeprom = this->_rpcc->request_with_token<eeprom_map_t>("get_db_eeprom", db_idx); +} + +// Reminder: The property must not own any properties, it can only interact with +// the API of this block! +void rhodium_radio_control_impl::_init_frontend_subtree(uhd::property_tree::sptr subtree) +{ + 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 for channel 0" + << " to prop tree path " << tx_fe_path << " and " << rx_fe_path); + // TX Standard attributes + subtree->create<std::string>(tx_fe_path / "name").set(RHODIUM_FE_NAME); + subtree->create<std::string>(tx_fe_path / "connection") + .add_coerced_subscriber( + [this](const std::string& conn) { this->_set_tx_fe_connection(conn); }) + .set_publisher([this]() { return this->_get_tx_fe_connection(); }); + subtree->create<device_addr_t>(tx_fe_path / "tune_args") + .set(device_addr_t()) + .add_coerced_subscriber( + [this](const device_addr_t& args) { set_tx_tune_args(args, 0); }) + .set_publisher([this]() { return _tune_args.at(uhd::TX_DIRECTION); }); + // RX Standard attributes + subtree->create<std::string>(rx_fe_path / "name").set(RHODIUM_FE_NAME); + subtree->create<std::string>(rx_fe_path / "connection") + .add_coerced_subscriber( + [this](const std::string& conn) { this->_set_rx_fe_connection(conn); }) + .set_publisher([this]() { return this->_get_rx_fe_connection(); }); + subtree->create<device_addr_t>(rx_fe_path / "tune_args") + .set(device_addr_t()) + .add_coerced_subscriber( + [this](const device_addr_t& args) { set_rx_tune_args(args, 0); }) + .set_publisher([this]() { return _tune_args.at(uhd::RX_DIRECTION); }); + ; + // TX Antenna + subtree->create<std::string>(tx_fe_path / "antenna" / "value") + .add_coerced_subscriber( + [this](const std::string& ant) { this->set_tx_antenna(ant, 0); }) + .set_publisher([this]() { return this->get_tx_antenna(0); }); + subtree->create<std::vector<std::string>>(tx_fe_path / "antenna" / "options") + .add_coerced_subscriber([](const std::vector<std::string>&) { + throw uhd::runtime_error("Attempting to update antenna options!"); + }) + .set_publisher([this]() { return get_tx_antennas(0); }); + // RX Antenna + subtree->create<std::string>(rx_fe_path / "antenna" / "value") + .add_coerced_subscriber( + [this](const std::string& ant) { this->set_rx_antenna(ant, 0); }) + .set_publisher([this]() { return this->get_rx_antenna(0); }); + subtree->create<std::vector<std::string>>(rx_fe_path / "antenna" / "options") + .add_coerced_subscriber([](const std::vector<std::string>&) { + throw uhd::runtime_error("Attempting to update antenna options!"); + }) + .set_publisher([this]() { return get_rx_antennas(0); }); + // TX frequency + subtree->create<double>(tx_fe_path / "freq" / "value") + .set_coercer( + [this](const double freq) { return this->set_tx_frequency(freq, 0); }) + .set_publisher([this]() { return this->get_tx_frequency(0); }); + subtree->create<meta_range_t>(tx_fe_path / "freq" / "range") + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update freq range!"); + }) + .set_publisher([this]() { return get_tx_frequency_range(0); }); + // RX frequency + subtree->create<double>(rx_fe_path / "freq" / "value") + .set_coercer( + [this](const double freq) { return this->set_rx_frequency(freq, 0); }) + .set_publisher([this]() { return this->get_rx_frequency(0); }); + subtree->create<meta_range_t>(rx_fe_path / "freq" / "range") + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update freq range!"); + }) + .set_publisher([this]() { return get_rx_frequency_range(0); }); + // TX bandwidth + subtree->create<double>(tx_fe_path / "bandwidth" / "value") + .set_coercer([this](const double bw) { return this->set_tx_bandwidth(bw, 0); }) + .set_publisher([this]() { return this->get_tx_bandwidth(0); }); + subtree->create<meta_range_t>(tx_fe_path / "bandwidth" / "range") + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update bandwidth range!"); + }) + .set_publisher([this]() { return get_tx_bandwidth_range(0); }); + // RX bandwidth + subtree->create<double>(rx_fe_path / "bandwidth" / "value") + .set_coercer([this](const double bw) { return this->set_rx_bandwidth(bw, 0); }) + .set_publisher([this]() { return this->get_rx_bandwidth(0); }); + subtree->create<meta_range_t>(rx_fe_path / "bandwidth" / "range") + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update bandwidth range!"); + }) + .set_publisher([this]() { return get_rx_bandwidth_range(0); }); + // TX gains + subtree->create<double>(tx_fe_path / "gains" / "all" / "value") + .set_coercer([this](const double gain) { return this->set_tx_gain(gain, 0); }) + .set_publisher([this]() { return radio_control_impl::get_tx_gain(0); }); + subtree->create<meta_range_t>(tx_fe_path / "gains" / "all" / "range") + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update gain range!"); + }) + .set_publisher([this]() { return get_tx_gain_range(0); }); + // RX gains + subtree->create<double>(rx_fe_path / "gains" / "all" / "value") + .set_coercer([this](const double gain) { return this->set_rx_gain(gain, 0); }) + .set_publisher([this]() { return radio_control_impl::get_rx_gain(0); }); + subtree->create<meta_range_t>(rx_fe_path / "gains" / "all" / "range") + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update gain range!"); + }) + .set_publisher([this]() { return get_rx_gain_range(0); }); + + // LO Specific + // RX LO + // RX LO1 Frequency + subtree->create<double>(rx_fe_path / "los" / RHODIUM_LO1 / "freq/value") + .set_publisher([this]() { return this->get_rx_lo_freq(RHODIUM_LO1, 0); }) + .set_coercer([this](const double freq) { + return this->set_rx_lo_freq(freq, RHODIUM_LO1, 0); + }); + subtree->create<meta_range_t>(rx_fe_path / "los" / RHODIUM_LO1 / "freq/range") + .set_publisher([this]() { return this->get_rx_lo_freq_range(RHODIUM_LO1, 0); }); + // RX LO1 Source + subtree + ->create<std::vector<std::string>>( + rx_fe_path / "los" / RHODIUM_LO1 / "source/options") + .set_publisher([this]() { return this->get_rx_lo_sources(RHODIUM_LO1, 0); }); + subtree->create<std::string>(rx_fe_path / "los" / RHODIUM_LO1 / "source/value") + .add_coerced_subscriber([this](const std::string& src) { + this->set_rx_lo_source(src, RHODIUM_LO1, 0); + }) + .set_publisher([this]() { return this->get_rx_lo_source(RHODIUM_LO1, 0); }); + // RX LO1 Export + subtree->create<bool>(rx_fe_path / "los" / RHODIUM_LO1 / "export") + .add_coerced_subscriber([this](bool enabled) { + this->set_rx_lo_export_enabled(enabled, RHODIUM_LO1, 0); + }); + // RX LO1 Gain + subtree + ->create<double>( + rx_fe_path / "los" / RHODIUM_LO1 / "gains" / RHODIUM_LO_GAIN / "value") + .set_publisher([this]() { return this->get_rx_lo_gain(RHODIUM_LO1, 0); }) + .set_coercer([this](const double gain) { + return this->set_rx_lo_gain(gain, RHODIUM_LO1, 0); + }); + subtree + ->create<meta_range_t>( + rx_fe_path / "los" / RHODIUM_LO1 / "gains" / RHODIUM_LO_GAIN / "range") + .set_publisher([]() { return rhodium_radio_control_impl::_get_lo_gain_range(); }) + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update LO gain range!"); + }); + // RX LO1 Output Power + subtree + ->create<double>( + rx_fe_path / "los" / RHODIUM_LO1 / "gains" / RHODIUM_LO_POWER / "value") + .set_publisher([this]() { return this->get_rx_lo_power(RHODIUM_LO1, 0); }) + .set_coercer([this](const double gain) { + return this->set_rx_lo_power(gain, RHODIUM_LO1, 0); + }); + subtree + ->create<meta_range_t>( + rx_fe_path / "los" / RHODIUM_LO1 / "gains" / RHODIUM_LO_POWER / "range") + .set_publisher([]() { return rhodium_radio_control_impl::_get_lo_power_range(); }) + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update LO output power range!"); + }); + // RX LO2 Frequency + subtree->create<double>(rx_fe_path / "los" / RHODIUM_LO2 / "freq/value") + .set_publisher([this]() { return this->get_rx_lo_freq(RHODIUM_LO2, 0); }) + .set_coercer( + [this](double freq) { return this->set_rx_lo_freq(freq, RHODIUM_LO2, 0); }); + subtree->create<meta_range_t>(rx_fe_path / "los" / RHODIUM_LO2 / "freq/range") + .set_publisher([this]() { return this->get_rx_lo_freq_range(RHODIUM_LO2, 0); }); + // RX LO2 Source + subtree + ->create<std::vector<std::string>>( + rx_fe_path / "los" / RHODIUM_LO2 / "source/options") + .set_publisher([this]() { return this->get_rx_lo_sources(RHODIUM_LO2, 0); }); + subtree->create<std::string>(rx_fe_path / "los" / RHODIUM_LO2 / "source/value") + .add_coerced_subscriber( + [this](std::string src) { this->set_rx_lo_source(src, RHODIUM_LO2, 0); }) + .set_publisher([this]() { return this->get_rx_lo_source(RHODIUM_LO2, 0); }); + // RX LO2 Export + subtree->create<bool>(rx_fe_path / "los" / RHODIUM_LO2 / "export") + .add_coerced_subscriber([this](bool enabled) { + this->set_rx_lo_export_enabled(enabled, RHODIUM_LO2, 0); + }); + // RX ALL LOs + subtree->create<std::string>(rx_fe_path / "los" / ALL_LOS / "source/value") + .add_coerced_subscriber( + [this](std::string src) { this->set_rx_lo_source(src, ALL_LOS, 0); }) + .set_publisher([this]() { return this->get_rx_lo_source(ALL_LOS, 0); }); + subtree + ->create<std::vector<std::string>>( + rx_fe_path / "los" / ALL_LOS / "source/options") + .set_publisher([this]() { return this->get_rx_lo_sources(ALL_LOS, 0); }); + subtree->create<bool>(rx_fe_path / "los" / ALL_LOS / "export") + .add_coerced_subscriber( + [this](bool enabled) { this->set_rx_lo_export_enabled(enabled, ALL_LOS, 0); }) + .set_publisher([this]() { return this->get_rx_lo_export_enabled(ALL_LOS, 0); }); + // TX LO + // TX LO1 Frequency + subtree->create<double>(tx_fe_path / "los" / RHODIUM_LO1 / "freq/value ") + .set_publisher([this]() { return this->get_tx_lo_freq(RHODIUM_LO1, 0); }) + .set_coercer( + [this](double freq) { return this->set_tx_lo_freq(freq, RHODIUM_LO1, 0); }); + subtree->create<meta_range_t>(tx_fe_path / "los" / RHODIUM_LO1 / "freq/range") + .set_publisher([this]() { return this->get_rx_lo_freq_range(RHODIUM_LO1, 0); }); + // TX LO1 Source + subtree + ->create<std::vector<std::string>>( + tx_fe_path / "los" / RHODIUM_LO1 / "source/options") + .set_publisher([this]() { return this->get_tx_lo_sources(RHODIUM_LO1, 0); }); + subtree->create<std::string>(tx_fe_path / "los" / RHODIUM_LO1 / "source/value") + .add_coerced_subscriber( + [this](std::string src) { this->set_tx_lo_source(src, RHODIUM_LO1, 0); }) + .set_publisher([this]() { return this->get_tx_lo_source(RHODIUM_LO1, 0); }); + // TX LO1 Export + subtree->create<bool>(tx_fe_path / "los" / RHODIUM_LO1 / "export") + .add_coerced_subscriber([this](bool enabled) { + this->set_tx_lo_export_enabled(enabled, RHODIUM_LO1, 0); + }); + // TX LO1 Gain + subtree + ->create<double>( + tx_fe_path / "los" / RHODIUM_LO1 / "gains" / RHODIUM_LO_GAIN / "value") + .set_publisher([this]() { return this->get_tx_lo_gain(RHODIUM_LO1, 0); }) + .set_coercer([this](const double gain) { + return this->set_tx_lo_gain(gain, RHODIUM_LO1, 0); + }); + subtree + ->create<meta_range_t>( + tx_fe_path / "los" / RHODIUM_LO1 / "gains" / RHODIUM_LO_GAIN / "range") + .set_publisher([]() { return rhodium_radio_control_impl::_get_lo_gain_range(); }) + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update LO gain range!"); + }); + // TX LO1 Output Power + subtree + ->create<double>( + tx_fe_path / "los" / RHODIUM_LO1 / "gains" / RHODIUM_LO_POWER / "value") + .set_publisher([this]() { return this->get_tx_lo_power(RHODIUM_LO1, 0); }) + .set_coercer([this](const double gain) { + return this->set_tx_lo_power(gain, RHODIUM_LO1, 0); + }); + subtree + ->create<meta_range_t>( + tx_fe_path / "los" / RHODIUM_LO1 / "gains" / RHODIUM_LO_POWER / "range") + .set_publisher([]() { return rhodium_radio_control_impl::_get_lo_power_range(); }) + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update LO output power range!"); + }); + // TX LO2 Frequency + subtree->create<double>(tx_fe_path / "los" / RHODIUM_LO2 / "freq/value") + .set_publisher([this]() { return this->get_tx_lo_freq(RHODIUM_LO2, 0); }) + .set_coercer( + [this](double freq) { return this->set_tx_lo_freq(freq, RHODIUM_LO2, 0); }); + subtree->create<meta_range_t>(tx_fe_path / "los" / RHODIUM_LO2 / "freq/range") + .set_publisher([this]() { return this->get_tx_lo_freq_range(RHODIUM_LO2, 0); }); + // TX LO2 Source + subtree + ->create<std::vector<std::string>>( + tx_fe_path / "los" / RHODIUM_LO2 / "source/options") + .set_publisher([this]() { return this->get_tx_lo_sources(RHODIUM_LO2, 0); }); + subtree->create<std::string>(tx_fe_path / "los" / RHODIUM_LO2 / "source/value") + .add_coerced_subscriber( + [this](std::string src) { this->set_tx_lo_source(src, RHODIUM_LO2, 0); }) + .set_publisher([this]() { return this->get_tx_lo_source(RHODIUM_LO2, 0); }); + // TX LO2 Export + subtree->create<bool>(tx_fe_path / "los" / RHODIUM_LO2 / "export") + .add_coerced_subscriber([this](bool enabled) { + this->set_tx_lo_export_enabled(enabled, RHODIUM_LO2, 0); + }); + // TX ALL LOs + subtree->create<std::string>(tx_fe_path / "los" / ALL_LOS / "source/value") + .add_coerced_subscriber( + [this](std::string src) { this->set_tx_lo_source(src, ALL_LOS, 0); }) + .set_publisher([this]() { return this->get_tx_lo_source(ALL_LOS, 0); }); + subtree + ->create<std::vector<std::string>>( + tx_fe_path / "los" / ALL_LOS / "source/options") + .set_publisher([this]() { return this->get_tx_lo_sources(ALL_LOS, 0); }); + subtree->create<bool>(tx_fe_path / "los" / ALL_LOS / "export") + .add_coerced_subscriber( + [this](bool enabled) { this->set_tx_lo_export_enabled(enabled, ALL_LOS, 0); }) + .set_publisher([this]() { return this->get_tx_lo_export_enabled(ALL_LOS, 0); }); + + // LO Distribution Output Ports + if (_lo_dist_present) { + for (const auto& port : LO_OUTPUT_PORT_NAMES) { + subtree + ->create<bool>(tx_fe_path / "los" / RHODIUM_LO1 / "lo_distribution" / port + / "export") + .add_coerced_subscriber([this, port](bool enabled) { + this->set_tx_lo_output_enabled(enabled, port, 0); + }) + .set_publisher( + [this, port]() { return this->get_tx_lo_output_enabled(port, 0); }); + subtree + ->create<bool>(rx_fe_path / "los" / RHODIUM_LO1 / "lo_distribution" / port + / "export") + .add_coerced_subscriber([this, port](bool enabled) { + this->set_rx_lo_output_enabled(enabled, port, 0); + }) + .set_publisher( + [this, port]() { return this->get_rx_lo_output_enabled(port, 0); }); + } + } + + // Sensors + auto rx_sensor_names = get_rx_sensor_names(0); + for (const auto& sensor_name : rx_sensor_names) { + RFNOC_LOG_TRACE("Adding RX sensor " << sensor_name); + get_tree() + ->create<sensor_value_t>(rx_fe_path / "sensors" / sensor_name) + .add_coerced_subscriber([](const sensor_value_t&) { + throw uhd::runtime_error("Attempting to write to sensor!"); + }) + .set_publisher( + [this, sensor_name]() { return get_rx_sensor(sensor_name, 0); }); + } + auto tx_sensor_names = get_tx_sensor_names(0); + for (const auto& sensor_name : tx_sensor_names) { + RFNOC_LOG_TRACE("Adding TX sensor " << sensor_name); + get_tree() + ->create<sensor_value_t>(tx_fe_path / "sensors" / sensor_name) + .add_coerced_subscriber([](const sensor_value_t&) { + throw uhd::runtime_error("Attempting to write to sensor!"); + }) + .set_publisher( + [this, sensor_name]() { return get_tx_sensor(sensor_name, 0); }); + } +} + +void rhodium_radio_control_impl::_init_prop_tree() +{ + this->_init_frontend_subtree(get_tree()->subtree(DB_PATH)); + get_tree()->create<std::string>(fs_path("rx_codecs") / "name").set("ad9695-625"); + get_tree()->create<std::string>(fs_path("tx_codecs") / "name").set("dac37j82"); +} + +void rhodium_radio_control_impl::_init_mpm() +{ + auto block_args = get_block_args(); + if (block_args.has_key("identify")) { + const std::string identify_val = block_args.get("identify"); + int identify_duration = std::atoi(identify_val.c_str()); + if (identify_duration == 0) { + identify_duration = 5; + } + RFNOC_LOG_INFO("Running LED identification process for " << identify_duration + << " seconds."); + _identify_with_leds(identify_duration); + } + + // Note: MCR gets set during the init() call (prior to this), which takes + // in arguments from the device args. So if block_args contains a + // master_clock_rate key, then it should better be whatever the device is + // configured to do. + _master_clock_rate = + _rpcc->request_with_token<double>(_rpc_prefix + "get_master_clock_rate"); + if (block_args.cast<double>("master_clock_rate", _master_clock_rate) + != _master_clock_rate) { + throw uhd::runtime_error( + std::string("Master clock rate mismatch. Device returns ") + + std::to_string(_master_clock_rate) + + " MHz, " + "but should have been " + + std::to_string( + block_args.cast<double>("master_clock_rate", _master_clock_rate)) + + " MHz."); + } + RFNOC_LOG_DEBUG("Master Clock Rate is: " << (_master_clock_rate / 1e6) << " MHz."); + set_tick_rate(_master_clock_rate); + _n3xx_timekeeper->update_tick_rate(_master_clock_rate); + radio_control_impl::set_rate(_master_clock_rate); + + // Unlike N310, N320 does not have any MPM sensors. +} diff --git a/host/lib/usrp/dboard/rhodium/rhodium_radio_control_lo.cpp b/host/lib/usrp/dboard/rhodium/rhodium_radio_control_lo.cpp new file mode 100644 index 000000000..717a1c94f --- /dev/null +++ b/host/lib/usrp/dboard/rhodium/rhodium_radio_control_lo.cpp @@ -0,0 +1,713 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "rhodium_constants.hpp" +#include "rhodium_radio_control.hpp" +#include <uhd/exception.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/log.hpp> +#include <uhdlib/utils/narrow.hpp> +#include <boost/format.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; + +namespace { +constexpr int NUM_THRESHOLDS = 13; +constexpr std::array<double, NUM_THRESHOLDS> FREQ_THRESHOLDS = { + 0.45e9, 0.5e9, 1e9, 1.5e9, 2e9, 2.5e9, 3e9, 3.55e9, 4e9, 4.5e9, 5e9, 5.5e9, 6e9}; +constexpr std::array<int, NUM_THRESHOLDS> LMX_GAIN_VALUES = { + 18, 18, 17, 17, 17, 16, 12, 11, 11, 11, 10, 13, 18}; +const std::array<int, NUM_THRESHOLDS> DSA_RX_GAIN_VALUES = { + 19, 19, 21, 21, 20, 20, 22, 21, 20, 22, 22, 24, 26}; +const std::array<int, NUM_THRESHOLDS> DSA_TX_GAIN_VALUES = { + 19, 19, 21, 21, 20, 20, 22, 21, 22, 24, 24, 26, 28}; + +// Describes the lowband LO in terms of the master clock rate +const std::map<double, double> MCR_TO_LOWBAND_IF = { + {200e6, 1200e6}, + {245.76e6, 1228.8e6}, + {250e6, 1500e6}, +}; + +// validation helpers + +std::vector<std::string> _get_lo_names() +{ + return {RHODIUM_LO1, RHODIUM_LO2}; +} + +void _validate_lo_name(const std::string& name, const std::string& function_name) +{ + if (!uhd::has(_get_lo_names(), name) and name != radio_control::ALL_LOS) { + throw uhd::value_error( + str(boost::format("%s was called with an invalid LO name: %s") % function_name + % name)); + } +} + +// object agnostic helpers +std::vector<std::string> _get_lo_sources(const std::string& name) +{ + if (name == RHODIUM_LO1 or name == radio_control::ALL_LOS) { + return {"internal", "external"}; + } else { + return {"internal"}; + } +} +} // namespace + +/****************************************************************************** + * Property Getters + *****************************************************************************/ + +std::vector<std::string> rhodium_radio_control_impl::get_tx_lo_names( + const size_t chan) const +{ + RFNOC_LOG_TRACE("get_tx_lo_names(chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + return _get_lo_names(); +} + +std::vector<std::string> rhodium_radio_control_impl::get_rx_lo_names( + const size_t chan) const +{ + RFNOC_LOG_TRACE("get_rx_lo_names(chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + return _get_lo_names(); +} + +std::vector<std::string> rhodium_radio_control_impl::get_tx_lo_sources( + const std::string& name, const size_t chan) const +{ + RFNOC_LOG_TRACE("get_tx_lo_sources(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_tx_lo_sources"); + + return _get_lo_sources(name); +} + +std::vector<std::string> rhodium_radio_control_impl::get_rx_lo_sources( + const std::string& name, const size_t chan) const +{ + RFNOC_LOG_TRACE("get_rx_lo_sources(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_rx_lo_sources"); + + return _get_lo_sources(name); +} + +freq_range_t rhodium_radio_control_impl::_get_lo_freq_range(const std::string& name) const +{ + if (name == RHODIUM_LO1) { + return freq_range_t{RHODIUM_LO1_MIN_FREQ, RHODIUM_LO1_MAX_FREQ}; + } else if (name == RHODIUM_LO2) { + // The Lowband LO is a fixed frequency + return freq_range_t{_get_lowband_lo_freq(), _get_lowband_lo_freq()}; + } else { + throw uhd::runtime_error( + "LO frequency range must be retrieved for each stage individually"); + } +} + +freq_range_t rhodium_radio_control_impl::get_tx_lo_freq_range( + const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_tx_lo_freq_range(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_tx_lo_freq_range"); + + return _get_lo_freq_range(name); +} + +freq_range_t rhodium_radio_control_impl::get_rx_lo_freq_range( + const std::string& name, const size_t chan) const +{ + RFNOC_LOG_TRACE("get_rx_lo_freq_range(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_rx_lo_freq_range"); + + return _get_lo_freq_range(name); +} + +/****************************************************************************** + * Frequency Control + *****************************************************************************/ + +double rhodium_radio_control_impl::set_tx_lo_freq( + const double freq, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE( + "set_tx_lo_freq(freq=" << freq << ", name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "set_tx_lo_freq"); + + if (name == ALL_LOS) { + throw uhd::runtime_error("LO frequency must be set for each stage individually"); + } + if (name == RHODIUM_LO2) { + RFNOC_LOG_WARNING("The Lowband LO cannot be tuned"); + return _get_lowband_lo_freq(); + } + + const auto sd_enabled = _get_spur_dodging_enabled(TX_DIRECTION); + const auto sd_threshold = _get_spur_dodging_threshold(TX_DIRECTION); + + _tx_lo_freq = _tx_lo->set_frequency(freq, sd_enabled, sd_threshold); + set_tx_lo_gain(_get_lo_dsa_setting(_tx_lo_freq, TX_DIRECTION), RHODIUM_LO1, chan); + set_tx_lo_power(_get_lo_power_setting(_tx_lo_freq), RHODIUM_LO1, chan); + _cpld->set_tx_lo_path(_tx_lo_freq); + + return _tx_lo_freq; +} + +double rhodium_radio_control_impl::set_rx_lo_freq( + const double freq, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE( + "set_rx_lo_freq(freq=" << freq << ", name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "set_rx_lo_freq"); + + if (name == ALL_LOS) { + throw uhd::runtime_error("LO frequency must be set for each stage individually"); + } + if (name == RHODIUM_LO2) { + RFNOC_LOG_WARNING("The Lowband LO cannot be tuned"); + return _get_lowband_lo_freq(); + } + + const auto sd_enabled = _get_spur_dodging_enabled(RX_DIRECTION); + const auto sd_threshold = _get_spur_dodging_threshold(RX_DIRECTION); + + _rx_lo_freq = _rx_lo->set_frequency(freq, sd_enabled, sd_threshold); + set_rx_lo_gain(_get_lo_dsa_setting(_rx_lo_freq, RX_DIRECTION), RHODIUM_LO1, chan); + set_rx_lo_power(_get_lo_power_setting(_rx_lo_freq), RHODIUM_LO1, chan); + _cpld->set_rx_lo_path(_rx_lo_freq); + + return _rx_lo_freq; +} + +double rhodium_radio_control_impl::get_tx_lo_freq( + const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_tx_lo_freq(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_tx_lo_freq"); + + if (name == ALL_LOS) { + throw uhd::runtime_error( + "LO frequency must be retrieved for each stage individually"); + } + + return (name == RHODIUM_LO1) ? _tx_lo_freq : _get_lowband_lo_freq(); +} + +double rhodium_radio_control_impl::get_rx_lo_freq( + const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_rx_lo_freq(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_rx_lo_freq"); + + if (name == ALL_LOS) { + throw uhd::runtime_error( + "LO frequency must be retrieved for each stage individually"); + } + + return (name == RHODIUM_LO1) ? _rx_lo_freq : _get_lowband_lo_freq(); +} + +/****************************************************************************** + * Source Control + *****************************************************************************/ + +void rhodium_radio_control_impl::set_tx_lo_source( + const std::string& src, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE( + "set_tx_lo_source(src=" << src << ", name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "set_tx_lo_source"); + + if (name == RHODIUM_LO2) { + if (src != "internal") { + throw uhd::value_error("The Lowband LO can only be set to internal"); + } + return; + } + + if (src == "internal") { + _tx_lo->set_output_enable(lmx2592_iface::output_t::RF_OUTPUT_A, true); + _cpld->set_tx_lo_source( + rhodium_cpld_ctrl::tx_lo_input_sel_t::TX_LO_INPUT_SEL_INTERNAL); + } else if (src == "external") { + _tx_lo->set_output_enable(lmx2592_iface::output_t::RF_OUTPUT_A, false); + _cpld->set_tx_lo_source( + rhodium_cpld_ctrl::tx_lo_input_sel_t::TX_LO_INPUT_SEL_EXTERNAL); + } else { + throw uhd::value_error( + str(boost::format("set_tx_lo_source was called with an invalid LO source: %s " + "Valid sources are [internal, external]") + % src)); + } + + const bool enable_corrections = not _is_tx_lowband(get_tx_frequency(0)) + and src == "internal"; + _update_corrections(get_tx_frequency(0), TX_DIRECTION, enable_corrections); + + _tx_lo_source = src; +} + +void rhodium_radio_control_impl::set_rx_lo_source( + const std::string& src, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE( + "set_rx_lo_source(src=" << src << ", name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "set_tx_lo_source"); + + if (name == RHODIUM_LO2) { + if (src != "internal") { + throw uhd::value_error("The Lowband LO can only be set to internal"); + } + return; + } + + if (src == "internal") { + _rx_lo->set_output_enable(lmx2592_iface::output_t::RF_OUTPUT_A, true); + _cpld->set_rx_lo_source( + rhodium_cpld_ctrl::rx_lo_input_sel_t::RX_LO_INPUT_SEL_INTERNAL); + } else if (src == "external") { + _rx_lo->set_output_enable(lmx2592_iface::output_t::RF_OUTPUT_A, false); + _cpld->set_rx_lo_source( + rhodium_cpld_ctrl::rx_lo_input_sel_t::RX_LO_INPUT_SEL_EXTERNAL); + } else { + throw uhd::value_error( + str(boost::format("set_rx_lo_source was called with an invalid LO source: %s " + "Valid sources for LO1 are [internal, external]") + % src)); + } + + const bool enable_corrections = not _is_rx_lowband(get_rx_frequency(0)) + and src == "internal"; + _update_corrections(get_rx_frequency(0), RX_DIRECTION, enable_corrections); + + _rx_lo_source = src; +} + +const std::string rhodium_radio_control_impl::get_tx_lo_source( + const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_tx_lo_source(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_tx_lo_source"); + return (name == RHODIUM_LO1 or name == ALL_LOS) ? _tx_lo_source : "internal"; +} + +const std::string rhodium_radio_control_impl::get_rx_lo_source( + const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_rx_lo_source(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_rx_lo_source"); + return (name == RHODIUM_LO1 or name == ALL_LOS) ? _rx_lo_source : "internal"; +} + +/****************************************************************************** + * Export Control + *****************************************************************************/ + +void rhodium_radio_control_impl::_set_lo1_export_enabled( + const bool enabled, const direction_t dir) +{ + auto& _lo_ctrl = (dir == RX_DIRECTION) ? _rx_lo : _tx_lo; + _lo_ctrl->set_output_enable(lmx2592_iface::output_t::RF_OUTPUT_B, enabled); + if (_lo_dist_present) { + const auto direction = (dir == RX_DIRECTION) ? "RX" : "TX"; + _rpcc->notify_with_token(_rpc_prefix + "enable_lo_export", direction, enabled); + } +} + +void rhodium_radio_control_impl::set_tx_lo_export_enabled( + const bool enabled, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("set_tx_lo_export_enabled(enabled=" << enabled << ", name=" << name + << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "set_tx_lo_export_enabled"); + + if (name == RHODIUM_LO2) { + if (enabled) { + throw uhd::value_error("The lowband LO cannot be exported"); + } + return; + } + + _set_lo1_export_enabled(enabled, TX_DIRECTION); + _tx_lo_exported = enabled; +} + +void rhodium_radio_control_impl::set_rx_lo_export_enabled( + const bool enabled, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("set_rx_lo_export_enabled(enabled=" << enabled << ", name=" << name + << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "set_rx_lo_export_enabled"); + + if (name == RHODIUM_LO2) { + if (enabled) { + throw uhd::value_error("The lowband LO cannot be exported"); + } + return; + } + + _set_lo1_export_enabled(enabled, RX_DIRECTION); + _rx_lo_exported = enabled; +} + +bool rhodium_radio_control_impl::get_tx_lo_export_enabled( + const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_tx_lo_export_enabled(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_tx_lo_export_enabled"); + + return (name == RHODIUM_LO1 or name == ALL_LOS) ? _tx_lo_exported : false; +} + +bool rhodium_radio_control_impl::get_rx_lo_export_enabled( + const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_rx_lo_export_enabled(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_rx_lo_export_enabled"); + + return (name == RHODIUM_LO1 or name == ALL_LOS) ? _rx_lo_exported : false; +} + +/****************************************************************************** + * LO Distribution Control + *****************************************************************************/ + +void rhodium_radio_control_impl::_validate_output_port( + const std::string& port_name, const std::string& function_name) +{ + if (!_lo_dist_present) { + throw uhd::runtime_error( + str(boost::format( + "%s can only be called if the LO distribution board was detected") + % function_name)); + } + + if (!uhd::has(LO_OUTPUT_PORT_NAMES, port_name)) { + throw uhd::value_error( + str(boost::format("%s was called with an invalid LO output port: %s Valid " + "ports are [LO_OUT_0, LO_OUT_1, LO_OUT_2, LO_OUT_3]") + % function_name % port_name)); + } +} + +void rhodium_radio_control_impl::_set_lo_output_enabled( + const bool enabled, const std::string& port_name, const direction_t dir) +{ + auto direction = (dir == RX_DIRECTION) ? "RX" : "TX"; + auto name_iter = + std::find(LO_OUTPUT_PORT_NAMES.begin(), LO_OUTPUT_PORT_NAMES.end(), port_name); + auto index = std::distance(LO_OUTPUT_PORT_NAMES.begin(), name_iter); + + _rpcc->notify_with_token(_rpc_prefix + "enable_lo_output", direction, index, enabled); + auto out_enabled = (dir == RX_DIRECTION) ? _lo_dist_rx_out_enabled + : _lo_dist_tx_out_enabled; + out_enabled[index] = enabled; +} + +void rhodium_radio_control_impl::set_tx_lo_output_enabled( + const bool enabled, const std::string& port_name, const size_t chan) +{ + RFNOC_LOG_TRACE("set_tx_lo_output_enabled(enabled=" << enabled + << ", port_name=" << port_name + << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_output_port(port_name, "set_tx_lo_output_enabled"); + + _set_lo_output_enabled(enabled, port_name, TX_DIRECTION); +} + +void rhodium_radio_control_impl::set_rx_lo_output_enabled( + const bool enabled, const std::string& port_name, const size_t chan) +{ + RFNOC_LOG_TRACE("set_rx_lo_output_enabled(enabled=" << enabled + << ", port_name=" << port_name + << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_output_port(port_name, "set_rx_lo_output_enabled"); + + _set_lo_output_enabled(enabled, port_name, RX_DIRECTION); +} + +bool rhodium_radio_control_impl::_get_lo_output_enabled( + const std::string& port_name, const direction_t dir) +{ + auto name_iter = + std::find(LO_OUTPUT_PORT_NAMES.begin(), LO_OUTPUT_PORT_NAMES.end(), port_name); + auto index = std::distance(LO_OUTPUT_PORT_NAMES.begin(), name_iter); + + auto out_enabled = (dir == RX_DIRECTION) ? _lo_dist_rx_out_enabled + : _lo_dist_tx_out_enabled; + return out_enabled[index]; +} + +bool rhodium_radio_control_impl::get_tx_lo_output_enabled( + const std::string& port_name, const size_t chan) +{ + RFNOC_LOG_TRACE( + "get_tx_lo_output_enabled(port_name=" << port_name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_output_port(port_name, "get_tx_lo_output_enabled"); + + return _get_lo_output_enabled(port_name, TX_DIRECTION); +} + +bool rhodium_radio_control_impl::get_rx_lo_output_enabled( + const std::string& port_name, const size_t chan) +{ + RFNOC_LOG_TRACE( + "get_rx_lo_output_enabled(port_name=" << port_name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_output_port(port_name, "get_rx_lo_output_enabled"); + + return _get_lo_output_enabled(port_name, RX_DIRECTION); +} + +/****************************************************************************** + * Gain Control + *****************************************************************************/ + +double rhodium_radio_control_impl::set_tx_lo_gain( + double gain, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE( + "set_tx_lo_gain(gain=" << gain << ", name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "set_tx_lo_gain"); + + if (name == ALL_LOS) { + throw uhd::runtime_error("LO gain must be set for each stage individually"); + } + if (name == RHODIUM_LO2) { + RFNOC_LOG_WARNING("The Lowband LO does not have configurable gain"); + return 0.0; + } + + auto index = _get_lo_gain_range().clip(gain); + + _cpld->set_lo_gain(index, TX_DIRECTION); + _lo_tx_gain = index; + return _lo_tx_gain; +} + +double rhodium_radio_control_impl::set_rx_lo_gain( + double gain, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE( + "set_rx_lo_gain(gain=" << gain << ", name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "set_rx_lo_gain"); + + if (name == ALL_LOS) { + throw uhd::runtime_error("LO gain must be set for each stage individually"); + } + if (name == RHODIUM_LO2) { + RFNOC_LOG_WARNING("The Lowband LO does not have configurable gain"); + return 0.0; + } + + auto index = _get_lo_gain_range().clip(gain); + + _cpld->set_lo_gain(index, RX_DIRECTION); + _lo_rx_gain = index; + return _lo_rx_gain; +} + +double rhodium_radio_control_impl::get_tx_lo_gain( + const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_tx_lo_gain(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_tx_lo_gain"); + + if (name == ALL_LOS) { + throw uhd::runtime_error("LO gain must be retrieved for each stage individually"); + } + + return (name == RHODIUM_LO1) ? _lo_rx_gain : 0.0; +} + +double rhodium_radio_control_impl::get_rx_lo_gain( + const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_rx_lo_gain(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_rx_lo_gain"); + + if (name == ALL_LOS) { + throw uhd::runtime_error("LO gain must be retrieved for each stage individually"); + } + + return (name == RHODIUM_LO1) ? _lo_tx_gain : 0.0; +} + +/****************************************************************************** + * Output Power Control + *****************************************************************************/ + +double rhodium_radio_control_impl::_set_lo1_power( + const double power, const direction_t dir) +{ + auto& _lo_ctrl = (dir == RX_DIRECTION) ? _rx_lo : _tx_lo; + auto coerced_power = static_cast<uint32_t>(_get_lo_power_range().clip(power, true)); + + _lo_ctrl->set_output_power(lmx2592_iface::RF_OUTPUT_A, coerced_power); + return coerced_power; +} + +double rhodium_radio_control_impl::set_tx_lo_power( + const double power, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("set_tx_lo_power(power=" << power << ", name=" << name + << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "set_tx_lo_power"); + + if (name == ALL_LOS) { + throw uhd::runtime_error( + "LO output power must be set for each stage individually"); + } + if (name == RHODIUM_LO2) { + RFNOC_LOG_WARNING("The Lowband LO does not have configurable output power"); + return 0.0; + } + + _lo_tx_power = _set_lo1_power(power, TX_DIRECTION); + return _lo_tx_power; +} + +double rhodium_radio_control_impl::set_rx_lo_power( + const double power, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("set_rx_lo_power(power=" << power << ", name=" << name + << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "set_rx_lo_power"); + + if (name == ALL_LOS) { + throw uhd::runtime_error( + "LO output power must be set for each stage individually"); + } + if (name == RHODIUM_LO2) { + RFNOC_LOG_WARNING("The Lowband LO does not have configurable output power"); + return 0.0; + } + + _lo_rx_power = _set_lo1_power(power, RX_DIRECTION); + return _lo_rx_power; +} + +double rhodium_radio_control_impl::get_tx_lo_power( + const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_tx_lo_power(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_tx_lo_power"); + + if (name == ALL_LOS) { + throw uhd::runtime_error( + "LO output power must be retrieved for each stage individually"); + } + + return (name == RHODIUM_LO1) ? _lo_tx_power : 0.0; +} + +double rhodium_radio_control_impl::get_rx_lo_power( + const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_rx_lo_power(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_rx_lo_power"); + + if (name == ALL_LOS) { + throw uhd::runtime_error( + "LO output power must be retrieved for each stage individually"); + } + + return (name == RHODIUM_LO1) ? _lo_rx_power : 0.0; +} + +/****************************************************************************** + * Helper Functions + *****************************************************************************/ + +double rhodium_radio_control_impl::_get_lowband_lo_freq() const +{ + return MCR_TO_LOWBAND_IF.at(_master_clock_rate); +} + +uhd::gain_range_t rhodium_radio_control_impl::_get_lo_gain_range() +{ + return gain_range_t(LO_MIN_GAIN, LO_MAX_GAIN, LO_GAIN_STEP); +} + +uhd::gain_range_t rhodium_radio_control_impl::_get_lo_power_range() +{ + return gain_range_t(LO_MIN_POWER, LO_MAX_POWER, LO_POWER_STEP); +} + +int rhodium_radio_control_impl::_get_lo_dsa_setting( + const double freq, const direction_t dir) +{ + int index = 0; + while (freq > FREQ_THRESHOLDS[index + 1]) { + index++; + } + + const double freq_low = FREQ_THRESHOLDS[index]; + const double freq_high = FREQ_THRESHOLDS[index + 1]; + + const auto& gain_table = (dir == RX_DIRECTION) ? DSA_RX_GAIN_VALUES + : DSA_TX_GAIN_VALUES; + const double gain_low = gain_table[index]; + const double gain_high = gain_table[index + 1]; + + + const double slope = (gain_high - gain_low) / (freq_high - freq_low); + const double gain_at_freq = gain_low + (freq - freq_low) * slope; + + RFNOC_LOG_TRACE("Interpolated DSA Gain is " << gain_at_freq); + return static_cast<int>(std::round(gain_at_freq)); +} + +unsigned int rhodium_radio_control_impl::_get_lo_power_setting(double freq) +{ + int index = 0; + while (freq > FREQ_THRESHOLDS[index + 1]) { + index++; + } + + const double freq_low = FREQ_THRESHOLDS[index]; + const double freq_high = FREQ_THRESHOLDS[index + 1]; + const double power_low = LMX_GAIN_VALUES[index]; + const double power_high = LMX_GAIN_VALUES[index + 1]; + const double slope = (power_high - power_low) / (freq_high - freq_low); + const double power_at_freq = power_low + (freq - freq_low) * slope; + + RFNOC_LOG_TRACE("Interpolated LMX Power is " << power_at_freq); + return uhd::narrow_cast<unsigned int>(std::lround(power_at_freq)); +} diff --git a/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_cpld.cpp b/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_cpld.cpp deleted file mode 100644 index 846a4eac6..000000000 --- a/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_cpld.cpp +++ /dev/null @@ -1,298 +0,0 @@ -// -// Copyright 2018 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "rhodium_radio_ctrl_impl.hpp" -#include "rhodium_cpld_ctrl.hpp" -#include "rhodium_constants.hpp" -#include <uhd/utils/log.hpp> - -using namespace uhd; -using namespace uhd::usrp; -using namespace uhd::rfnoc; - -namespace { - const char* rx_band_to_log(rhodium_radio_ctrl_impl::rx_band rx_band) - { - switch (rx_band) - { - case rhodium_radio_ctrl_impl::rx_band::RX_BAND_0: - return "0"; - case rhodium_radio_ctrl_impl::rx_band::RX_BAND_1: - return "1"; - case rhodium_radio_ctrl_impl::rx_band::RX_BAND_2: - return "2"; - case rhodium_radio_ctrl_impl::rx_band::RX_BAND_3: - return "3"; - case rhodium_radio_ctrl_impl::rx_band::RX_BAND_4: - return "4"; - case rhodium_radio_ctrl_impl::rx_band::RX_BAND_5: - return "5"; - case rhodium_radio_ctrl_impl::rx_band::RX_BAND_6: - return "6"; - case rhodium_radio_ctrl_impl::rx_band::RX_BAND_7: - return "7"; - case rhodium_radio_ctrl_impl::rx_band::RX_BAND_INVALID: - return "INVALID"; - default: - UHD_THROW_INVALID_CODE_PATH(); - } - } - - const char* tx_band_to_log(rhodium_radio_ctrl_impl::tx_band tx_band) - { - switch (tx_band) - { - case rhodium_radio_ctrl_impl::tx_band::TX_BAND_0: - return "0"; - case rhodium_radio_ctrl_impl::tx_band::TX_BAND_1: - return "1"; - case rhodium_radio_ctrl_impl::tx_band::TX_BAND_2: - return "2"; - case rhodium_radio_ctrl_impl::tx_band::TX_BAND_3: - return "3"; - case rhodium_radio_ctrl_impl::tx_band::TX_BAND_4: - return "4"; - case rhodium_radio_ctrl_impl::tx_band::TX_BAND_5: - return "5"; - case rhodium_radio_ctrl_impl::tx_band::TX_BAND_6: - return "6"; - case rhodium_radio_ctrl_impl::tx_band::TX_BAND_7: - return "7"; - case rhodium_radio_ctrl_impl::tx_band::TX_BAND_INVALID: - return "INVALID"; - default: - UHD_THROW_INVALID_CODE_PATH(); - } - } -} - -void rhodium_radio_ctrl_impl::_update_rx_freq_switches( - const double freq -) { - UHD_LOG_TRACE(unique_id(), - "Update all RX freq related switches. f=" << freq << " Hz, " - ); - const auto band = _map_freq_to_rx_band(freq); - UHD_LOG_TRACE(unique_id(), - "Selected band " << rx_band_to_log(band)); - - // select values for lowband/highband switches - const bool is_lowband = (band == rx_band::RX_BAND_0); - auto rx_sw2_sw7 = is_lowband ? - rhodium_cpld_ctrl::RX_SW2_SW7_LOWBANDFILTERBANK : - rhodium_cpld_ctrl::RX_SW2_SW7_HIGHBANDFILTERBANK; - auto rx_hb_lb_sel = is_lowband ? - rhodium_cpld_ctrl::RX_HB_LB_SEL_LOWBAND : - rhodium_cpld_ctrl::RX_HB_LB_SEL_HIGHBAND; - - // select values for filter bank switches - rhodium_cpld_ctrl::rx_sw3_t rx_sw3; - rhodium_cpld_ctrl::rx_sw4_sw5_t rx_sw4_sw5; - rhodium_cpld_ctrl::rx_sw6_t rx_sw6; - switch (band) - { - case rx_band::RX_BAND_0: - // Low band doesn't use the filter banks, use configuration for band 1 - case rx_band::RX_BAND_1: - rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOSWITCH4; - rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0450X0760MHZ; - rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMSWITCH5; - break; - case rx_band::RX_BAND_2: - rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOSWITCH4; - rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0760X1100MHZ; - rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMSWITCH5; - break; - case rx_band::RX_BAND_3: - rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOSWITCH4; - rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER1100X1410MHZ; - rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMSWITCH5; - break; - case rx_band::RX_BAND_4: - rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOSWITCH4; - rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER1410X2050MHZ; - rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMSWITCH5; - break; - case rx_band::RX_BAND_5: - rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOFILTER2050X3000MHZ; - rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0450X0760MHZ; - rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMFILTER2050X3000MHZ; - break; - case rx_band::RX_BAND_6: - rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOFILTER3000X4500MHZ; - rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0450X0760MHZ; - rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMFILTER3000X4500MHZ; - break; - case rx_band::RX_BAND_7: - rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOFILTER4500X6000MHZ; - rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0450X0760MHZ; - rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMFILTER4500X6000MHZ; - break; - case rx_band::RX_BAND_INVALID: - throw uhd::runtime_error(str(boost::format( - "Cannot map RX frequency to band: %f") % freq)); - default: - UHD_THROW_INVALID_CODE_PATH(); - } - - // commit settings to cpld - _cpld->set_rx_switches( - rx_sw2_sw7, - rx_sw3, - rx_sw4_sw5, - rx_sw6, - rx_hb_lb_sel - ); -} - -void rhodium_radio_ctrl_impl::_update_tx_freq_switches( - const double freq -){ - UHD_LOG_TRACE(unique_id(), - "Update all TX freq related switches. f=" << freq << " Hz, " - ); - - const auto band = _map_freq_to_tx_band(freq); - - UHD_LOG_TRACE(unique_id(), - "Selected band " << tx_band_to_log(band)); - - // select values for lowband/highband switches - const bool is_lowband = (band == tx_band::TX_BAND_0); - auto tx_hb_lb_sel = is_lowband ? - rhodium_cpld_ctrl::TX_HB_LB_SEL_LOWBAND : - rhodium_cpld_ctrl::TX_HB_LB_SEL_HIGHBAND; - - // select values for filter bank switches - rhodium_cpld_ctrl::tx_sw2_t tx_sw2; - rhodium_cpld_ctrl::tx_sw3_sw4_t tx_sw3_sw4; - rhodium_cpld_ctrl::tx_sw5_t tx_sw5; - switch (band) - { - case tx_band::TX_BAND_0: - // Low band doesn't use the filter banks, use configuration for band 1 - case tx_band::TX_BAND_1: - tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMSWITCH3; - tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP0650MHZ; - tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOSWITCH4; - break; - case tx_band::TX_BAND_2: - tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMSWITCH3; - tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP1000MHZ; - tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOSWITCH4; - break; - case tx_band::TX_BAND_3: - tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMSWITCH3; - tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP1350MHZ; - tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOSWITCH4; - break; - case tx_band::TX_BAND_4: - tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMSWITCH3; - tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP1900MHZ; - tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOSWITCH4; - break; - case tx_band::TX_BAND_5: - tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMTXFILTERLP3000MHZ; - tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP0650MHZ; - tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOTXFILTERLP3000MHZ; - break; - case tx_band::TX_BAND_6: - tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMTXFILTERLP4100MHZ; - tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP0650MHZ; - tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOTXFILTERLP4100MHZ; - break; - case tx_band::TX_BAND_7: - tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMTXFILTERLP6000MHZ; - tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP0650MHZ; - tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOTXFILTERLP6000MHZ; - break; - case tx_band::TX_BAND_INVALID: - throw uhd::runtime_error(str(boost::format( - "Cannot map TX frequency to band: %f") % freq)); - default: - UHD_THROW_INVALID_CODE_PATH(); - } - - // commit settings to cpld - _cpld->set_tx_switches( - tx_sw2, - tx_sw3_sw4, - tx_sw5, - tx_hb_lb_sel - ); -} - -void rhodium_radio_ctrl_impl::_update_rx_input_switches( - const std::string &input -) { - UHD_LOG_TRACE(unique_id(), - "Update all RX input related switches. input=" << input - ); - const rhodium_cpld_ctrl::cal_iso_sw_t cal_iso = (input == "CAL") ? - rhodium_cpld_ctrl::CAL_ISO_CALLOOPBACK : - rhodium_cpld_ctrl::CAL_ISO_ISOLATION; - const rhodium_cpld_ctrl::rx_sw1_t sw1 = [input]{ - if (input == "TX/RX") - { - return rhodium_cpld_ctrl::RX_SW1_FROMTXRXINPUT; - } - else if (input == "RX2") { - return rhodium_cpld_ctrl::RX_SW1_FROMRX2INPUT; - } - else if (input == "CAL") { - return rhodium_cpld_ctrl::RX_SW1_FROMCALLOOPBACK; - } - else if (input == "TERM") { - return rhodium_cpld_ctrl::RX_SW1_ISOLATION; - } - else { - throw uhd::runtime_error("Invalid antenna in _update_rx_input_switches: " + input); - } - }(); - - UHD_LOG_TRACE(unique_id(), - "Selected switch values:" - " sw1=" << sw1 << - " cal_iso=" << cal_iso - ); - _cpld->set_rx_input_switches(sw1, cal_iso); -} - -void rhodium_radio_ctrl_impl::_update_tx_output_switches( - const std::string &output -) { - UHD_LOG_TRACE(unique_id(), - "Update all TX output related switches. output=" << output - ); - rhodium_cpld_ctrl::tx_sw1_t sw1; - - if (output == "TX/RX") - { - //SW1 needs to select low/high band - if (_is_tx_lowband(get_tx_frequency(0))) - { - sw1 = rhodium_cpld_ctrl::TX_SW1_TOLOWBAND; - } - else { - sw1 = rhodium_cpld_ctrl::TX_SW1_TOSWITCH2; - } - } - else if (output == "CAL") { - sw1 = rhodium_cpld_ctrl::TX_SW1_TOCALLOOPBACK; - } - else if (output == "TERM") { - sw1 = rhodium_cpld_ctrl::TX_SW1_ISOLATION; - } - else { - throw uhd::runtime_error("Invalid antenna in _update_tx_output_switches: " + output); - } - - UHD_LOG_TRACE(unique_id(), - "Selected switch values: sw1=" << sw1 - ); - - _cpld->set_tx_output_switches(sw1); -} diff --git a/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_impl.cpp b/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_impl.cpp deleted file mode 100644 index d6dbbc594..000000000 --- a/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_impl.cpp +++ /dev/null @@ -1,677 +0,0 @@ -// -// Copyright 2018 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "rhodium_radio_ctrl_impl.hpp" -#include "rhodium_constants.hpp" -#include <uhdlib/utils/narrow.hpp> -#include <uhdlib/usrp/common/apply_corrections.hpp> -#include <uhd/utils/log.hpp> -#include <uhd/rfnoc/node_ctrl_base.hpp> -#include <uhd/transport/chdr.hpp> -#include <uhd/utils/algorithm.hpp> -#include <uhd/utils/math.hpp> -#include <uhd/types/direction.hpp> -#include <uhd/types/eeprom.hpp> -#include <uhd/exception.hpp> -#include <boost/algorithm/string.hpp> -#include <boost/make_shared.hpp> -#include <boost/format.hpp> -#include <sstream> -#include <cmath> -#include <cstdlib> - -using namespace uhd; -using namespace uhd::usrp; -using namespace uhd::rfnoc; -using namespace uhd::math::fp_compare; - -namespace { - constexpr char RX_FE_CONNECTION_LOWBAND[] = "QI"; - constexpr char RX_FE_CONNECTION_HIGHBAND[] = "IQ"; - constexpr char TX_FE_CONNECTION_LOWBAND[] = "QI"; - constexpr char TX_FE_CONNECTION_HIGHBAND[] = "IQ"; - - constexpr double DEFAULT_IDENTIFY_DURATION = 5.0; // seconds - - constexpr uint64_t SET_RATE_RPC_TIMEOUT_MS = 10000; - - const fs_path TX_FE_PATH = fs_path("tx_frontends") / 0 / "tune_args"; - const fs_path RX_FE_PATH = fs_path("rx_frontends") / 0 / "tune_args"; - - device_addr_t _get_tune_args(uhd::property_tree::sptr tree, std::string _radio_slot, uhd::direction_t dir) - { - const auto subtree = tree->subtree(fs_path("dboards") / _radio_slot); - const auto tune_arg_path = (dir == RX_DIRECTION) ? RX_FE_PATH : TX_FE_PATH; - return subtree->access<device_addr_t>(tune_arg_path).get(); - } -} - - -/****************************************************************************** - * Structors - *****************************************************************************/ -UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR(rhodium_radio_ctrl) -{ - UHD_LOG_TRACE(unique_id(), "Entering rhodium_radio_ctrl_impl ctor..."); - const char radio_slot_name[] = {'A', 'B'}; - _radio_slot = radio_slot_name[get_block_id().get_block_count()]; - _rpc_prefix = - (_radio_slot == "A") ? "db_0_" : "db_1_"; - UHD_LOG_TRACE(unique_id(), "Radio slot: " << _radio_slot); -} - -rhodium_radio_ctrl_impl::~rhodium_radio_ctrl_impl() -{ - UHD_LOG_TRACE(unique_id(), "rhodium_radio_ctrl_impl::dtor() "); -} - - -/****************************************************************************** - * API Calls - *****************************************************************************/ -double rhodium_radio_ctrl_impl::set_rate(double requested_rate) -{ - meta_range_t rates; - for (const double rate : RHODIUM_RADIO_RATES) { - rates.push_back(range_t(rate)); - } - - const double rate = rates.clip(requested_rate); - if (!math::frequencies_are_equal(requested_rate, rate)) { - UHD_LOG_WARNING(unique_id(), - "Coercing requested sample rate from " << (requested_rate / 1e6) << " MHz to " << - (rate / 1e6) << " MHz, the closest possible rate."); - } - - const double current_rate = get_rate(); - if (math::frequencies_are_equal(current_rate, rate)) { - UHD_LOG_DEBUG( - unique_id(), "Rate is already at " << (rate / 1e6) << " MHz. Skipping set_rate()"); - return current_rate; - } - - // The master clock rate is always set by requesting db0's clock rate. - UHD_LOG_TRACE(unique_id(), "Updating master clock rate to " << rate); - auto new_rate = _rpcc->request_with_token<double>( - SET_RATE_RPC_TIMEOUT_MS, "db_0_set_master_clock_rate", rate); - // The lowband LO frequency will change with the master clock rate, so - // update the tuning of the device. - set_tx_frequency(get_tx_frequency(0), 0); - set_rx_frequency(get_rx_frequency(0), 0); - - radio_ctrl_impl::set_rate(new_rate); - return new_rate; -} - -void rhodium_radio_ctrl_impl::set_tx_antenna( - const std::string &ant, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_tx_antenna(ant=" << ant << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - - if (!uhd::has(RHODIUM_TX_ANTENNAS, ant)) { - throw uhd::value_error(str( - boost::format("[%s] Requesting invalid TX antenna value: %s") - % unique_id() - % ant - )); - } - - _update_tx_output_switches(ant); - // _update_atr will set the cached antenna value, so no need to do - // it here. See comments in _update_antenna for more info. - _update_atr(ant, TX_DIRECTION); -} - -void rhodium_radio_ctrl_impl::set_rx_antenna( - const std::string &ant, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "Setting RX antenna to " << ant); - UHD_ASSERT_THROW(chan == 0); - - if (!uhd::has(RHODIUM_RX_ANTENNAS, ant)) { - throw uhd::value_error(str( - boost::format("[%s] Requesting invalid RX antenna value: %s") - % unique_id() - % ant - )); - } - - _update_rx_input_switches(ant); - // _update_atr will set the cached antenna value, so no need to do - // it here. See comments in _update_antenna for more info. - _update_atr(ant, RX_DIRECTION); -} - -void rhodium_radio_ctrl_impl::_set_tx_fe_connection(const std::string &conn) -{ - UHD_LOG_TRACE(unique_id(), "set_tx_fe_connection(conn=" << conn << ")"); - - if (conn != _tx_fe_connection) - { - _tx_fe_core->set_mux(conn); - _tx_fe_connection = conn; - } -} - -void rhodium_radio_ctrl_impl::_set_rx_fe_connection(const std::string &conn) -{ - UHD_LOG_TRACE(unique_id(), "set_rx_fe_connection(conn=" << conn << ")"); - - if (conn != _rx_fe_connection) - { - _rx_fe_core->set_fe_connection(conn); - _rx_fe_connection = conn; - } -} - -std::string rhodium_radio_ctrl_impl::_get_tx_fe_connection() const -{ - UHD_LOG_TRACE(unique_id(), "get_tx_fe_connection()"); - - return _tx_fe_connection; -} - -std::string rhodium_radio_ctrl_impl::_get_rx_fe_connection() const -{ - UHD_LOG_TRACE(unique_id(), "get_rx_fe_connection()"); - - return _rx_fe_connection; -} - -double rhodium_radio_ctrl_impl::set_tx_frequency( - const double freq, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_tx_frequency(f=" << freq << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - - const auto old_freq = get_tx_frequency(0); - double coerced_target_freq = uhd::clip(freq, RHODIUM_MIN_FREQ, RHODIUM_MAX_FREQ); - - if (freq != coerced_target_freq) { - UHD_LOG_DEBUG(unique_id(), "Requested frequency is outside supported range. Coercing to " << coerced_target_freq); - } - - const bool is_highband = !_is_tx_lowband(coerced_target_freq); - - const double target_lo_freq = is_highband ? - coerced_target_freq : _get_lowband_lo_freq() - coerced_target_freq; - const double actual_lo_freq = - set_tx_lo_freq(target_lo_freq, RHODIUM_LO1, chan); - const double coerced_freq = is_highband ? - actual_lo_freq : _get_lowband_lo_freq() - actual_lo_freq; - const auto conn = is_highband ? - TX_FE_CONNECTION_HIGHBAND : TX_FE_CONNECTION_LOWBAND; - - // update the cached frequency value now so calls to set gain and update - // switches will read the new frequency - radio_ctrl_impl::set_tx_frequency(coerced_freq, chan); - - _set_tx_fe_connection(conn); - set_tx_gain(get_tx_gain(chan), 0); - - if (_get_highband_spur_reduction_enabled(TX_DIRECTION)) { - if (_get_timed_command_enabled() and _is_tx_lowband(old_freq) != not is_highband) { - UHD_LOG_WARNING(unique_id(), - "Timed tuning commands that transition between lowband and highband, 450 " - "MHz, do not function correctly when highband_spur_reduction is enabled! " - "Disable highband_spur_reduction or avoid using timed tuning commands."); - } - UHD_LOG_TRACE( - unique_id(), "TX Lowband LO is " << (is_highband ? "disabled" : "enabled")); - _rpcc->notify_with_token(_rpc_prefix + "enable_tx_lowband_lo", (!is_highband)); - } - _update_tx_freq_switches(coerced_freq); - const bool enable_corrections = is_highband - and (get_tx_lo_source(RHODIUM_LO1, 0) == "internal"); - _update_corrections(actual_lo_freq, TX_DIRECTION, enable_corrections); - // if TX lowband/highband changed and antenna is TX/RX, - // the ATR and SW1 need to be updated - _update_tx_output_switches(get_tx_antenna(0)); - _update_atr(get_tx_antenna(0), TX_DIRECTION); - - return coerced_freq; -} - -double rhodium_radio_ctrl_impl::set_rx_frequency( - const double freq, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_rx_frequency(f=" << freq << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - - const auto old_freq = get_rx_frequency(0); - double coerced_target_freq = uhd::clip(freq, RHODIUM_MIN_FREQ, RHODIUM_MAX_FREQ); - - if (freq != coerced_target_freq) { - UHD_LOG_DEBUG(unique_id(), "Requested frequency is outside supported range. Coercing to " << coerced_target_freq); - } - - const bool is_highband = !_is_rx_lowband(coerced_target_freq); - - const double target_lo_freq = is_highband ? - coerced_target_freq : _get_lowband_lo_freq() - coerced_target_freq; - const double actual_lo_freq = - set_rx_lo_freq(target_lo_freq, RHODIUM_LO1, chan); - const double coerced_freq = is_highband ? - actual_lo_freq : _get_lowband_lo_freq() - actual_lo_freq; - const auto conn = is_highband ? - RX_FE_CONNECTION_HIGHBAND : RX_FE_CONNECTION_LOWBAND; - - // update the cached frequency value now so calls to set gain and update - // switches will read the new frequency - radio_ctrl_impl::set_rx_frequency(coerced_freq, chan); - - _set_rx_fe_connection(conn); - set_rx_gain(get_rx_gain(chan), 0); - - if (_get_highband_spur_reduction_enabled(RX_DIRECTION)) { - if (_get_timed_command_enabled() and _is_rx_lowband(old_freq) != not is_highband) { - UHD_LOG_WARNING(unique_id(), - "Timed tuning commands that transition between lowband and highband, 450 " - "MHz, do not function correctly when highband_spur_reduction is enabled! " - "Disable highband_spur_reduction or avoid using timed tuning commands."); - } - UHD_LOG_TRACE( - unique_id(), "RX Lowband LO is " << (is_highband ? "disabled" : "enabled")); - _rpcc->notify_with_token(_rpc_prefix + "enable_rx_lowband_lo", (!is_highband)); - } - _update_rx_freq_switches(coerced_freq); - const bool enable_corrections = is_highband - and (get_rx_lo_source(RHODIUM_LO1, 0) == "internal"); - _update_corrections(actual_lo_freq, RX_DIRECTION, enable_corrections); - - return coerced_freq; -} - -double rhodium_radio_ctrl_impl::set_rx_bandwidth( - const double bandwidth, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_rx_bandwidth(bandwidth=" << bandwidth << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - - return get_rx_bandwidth(chan); -} - -double rhodium_radio_ctrl_impl::set_tx_bandwidth( - const double bandwidth, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_tx_bandwidth(bandwidth=" << bandwidth << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - - return get_tx_bandwidth(chan); -} - -double rhodium_radio_ctrl_impl::set_tx_gain( - const double gain, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_tx_gain(gain=" << gain << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - - auto freq = this->get_tx_frequency(chan); - auto index = _get_gain_range(TX_DIRECTION).clip(gain); - - auto old_band = _is_tx_lowband(_tx_frequency_at_last_gain_write) ? - rhodium_cpld_ctrl::gain_band_t::LOW : - rhodium_cpld_ctrl::gain_band_t::HIGH; - auto new_band = _is_tx_lowband(freq) ? - rhodium_cpld_ctrl::gain_band_t::LOW : - rhodium_cpld_ctrl::gain_band_t::HIGH; - - // The CPLD requires a rewrite of the gain control command on a change of lowband or highband - if (get_tx_gain(chan) != index or old_band != new_band) { - UHD_LOG_TRACE(unique_id(), "Writing new TX gain index: " << index); - _cpld->set_gain_index(index, new_band, TX_DIRECTION); - _tx_frequency_at_last_gain_write = freq; - radio_ctrl_impl::set_tx_gain(index, chan); - } else { - UHD_LOG_TRACE(unique_id(), "No change in index or band, skipped writing TX gain index: " << index); - } - - return index; -} - -double rhodium_radio_ctrl_impl::set_rx_gain( - const double gain, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_rx_gain(gain=" << gain << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - - auto freq = this->get_rx_frequency(chan); - auto index = _get_gain_range(RX_DIRECTION).clip(gain); - - auto old_band = _is_rx_lowband(_rx_frequency_at_last_gain_write) ? - rhodium_cpld_ctrl::gain_band_t::LOW : - rhodium_cpld_ctrl::gain_band_t::HIGH; - auto new_band = _is_rx_lowband(freq) ? - rhodium_cpld_ctrl::gain_band_t::LOW : - rhodium_cpld_ctrl::gain_band_t::HIGH; - - // The CPLD requires a rewrite of the gain control command on a change of lowband or highband - if (get_rx_gain(chan) != index or old_band != new_band) { - UHD_LOG_TRACE(unique_id(), "Writing new RX gain index: " << index); - _cpld->set_gain_index(index, new_band, RX_DIRECTION); - _rx_frequency_at_last_gain_write = freq; - radio_ctrl_impl::set_rx_gain(index, chan); - } else { - UHD_LOG_TRACE(unique_id(), "No change in index or band, skipped writing RX gain index: " << index); - } - - return index; -} - -void rhodium_radio_ctrl_impl::_identify_with_leds( - double identify_duration -) { - auto duration_ms = static_cast<uint64_t>(identify_duration * 1000); - auto end_time = - std::chrono::steady_clock::now() + std::chrono::milliseconds(duration_ms); - bool led_state = true; - { - std::lock_guard<std::mutex> lock(_ant_mutex); - while (std::chrono::steady_clock::now() < end_time) { - auto atr = led_state ? (LED_RX | LED_RX2 | LED_TX) : 0; - _gpio->set_atr_reg(gpio_atr::ATR_REG_IDLE, atr, RHODIUM_GPIO_MASK); - led_state = !led_state; - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - } - } - _update_atr(get_tx_antenna(0), TX_DIRECTION); - _update_atr(get_rx_antenna(0), RX_DIRECTION); -} - -void rhodium_radio_ctrl_impl::_update_atr( - const std::string& ant, - const direction_t dir -) { - // This function updates sw10 based on the value of both antennas, so we - // use a mutex to prevent other calls in this class instance from running - // at the same time. - std::lock_guard<std::mutex> lock(_ant_mutex); - - UHD_LOG_TRACE(unique_id(), - "Updating ATRs for " << ((dir == RX_DIRECTION) ? "RX" : "TX") << " to " << ant); - - const auto rx_ant = (dir == RX_DIRECTION) ? ant : get_rx_antenna(0); - const auto tx_ant = (dir == TX_DIRECTION) ? ant : get_tx_antenna(0); - const auto sw10_tx = _is_tx_lowband(get_tx_frequency(0)) ? - SW10_FROMTXLOWBAND : SW10_FROMTXHIGHBAND; - - - const uint32_t atr_idle = SW10_ISOLATION; - - const uint32_t atr_rx = [rx_ant]{ - if (rx_ant == "TX/RX") { - return SW10_TORX | LED_RX; - } else if (rx_ant == "RX2") { - return SW10_ISOLATION | LED_RX2; - } else { - return SW10_ISOLATION; - } - }(); - - const uint32_t atr_tx = (tx_ant == "TX/RX") ? - (sw10_tx | LED_TX) : SW10_ISOLATION; - - const uint32_t atr_dx = [tx_ant, rx_ant, sw10_tx] { - uint32_t sw10_return; - if (tx_ant == "TX/RX") { - // if both channels are set to TX/RX, TX will override - sw10_return = sw10_tx | LED_TX; - } else if (rx_ant == "TX/RX") { - sw10_return = SW10_TORX | LED_RX; - } else { - sw10_return = SW10_ISOLATION; - } - sw10_return |= (rx_ant == "RX2") ? LED_RX2 : 0; - return sw10_return; - }(); - - _gpio->set_atr_reg(gpio_atr::ATR_REG_IDLE, atr_idle, RHODIUM_GPIO_MASK); - _gpio->set_atr_reg(gpio_atr::ATR_REG_RX_ONLY, atr_rx, RHODIUM_GPIO_MASK); - _gpio->set_atr_reg(gpio_atr::ATR_REG_TX_ONLY, atr_tx, RHODIUM_GPIO_MASK); - _gpio->set_atr_reg(gpio_atr::ATR_REG_FULL_DUPLEX, atr_dx, RHODIUM_GPIO_MASK); - - UHD_LOG_TRACE(unique_id(), - str(boost::format("Wrote ATR registers i:0x%02X, r:0x%02X, t:0x%02X, d:0x%02X") - % atr_idle % atr_rx % atr_tx % atr_dx)); - - if (dir == RX_DIRECTION) { - radio_ctrl_impl::set_rx_antenna(ant, 0); - } else { - radio_ctrl_impl::set_tx_antenna(ant, 0); - } -} - -void rhodium_radio_ctrl_impl::_update_corrections( - const double freq, - const direction_t dir, - const bool enable) -{ - const std::string fe_path_part = dir == RX_DIRECTION ? "rx_fe_corrections" - : "tx_fe_corrections"; - const fs_path fe_corr_path = _root_path / fe_path_part / 0; - const fs_path dboard_path = fs_path("dboards") / _radio_slot; - - if (enable) - { - UHD_LOG_DEBUG(unique_id(), - "Loading any available frontend corrections for " - << ((dir == RX_DIRECTION) ? "RX" : "TX") << " at " << freq); - if (dir == RX_DIRECTION) { - apply_rx_fe_corrections(_tree, dboard_path, fe_corr_path, freq); - } else { - apply_tx_fe_corrections(_tree, dboard_path, fe_corr_path, freq); - } - } else { - UHD_LOG_DEBUG(unique_id(), - "Disabling frontend corrections for " - << ((dir == RX_DIRECTION) ? "RX" : "TX")); - if (dir == RX_DIRECTION) { - _rx_fe_core->set_iq_balance(rx_frontend_core_3000::DEFAULT_IQ_BALANCE_VALUE); - } else { - _tx_fe_core->set_dc_offset(tx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); - _tx_fe_core->set_iq_balance(tx_frontend_core_200::DEFAULT_IQ_BALANCE_VALUE); - } - } - -} - -uhd::gain_range_t rhodium_radio_ctrl_impl::_get_gain_range(direction_t dir) -{ - if (dir == RX_DIRECTION) { - return gain_range_t(RX_MIN_GAIN, RX_MAX_GAIN, RX_GAIN_STEP); - } else if (dir == TX_DIRECTION) { - return gain_range_t(TX_MIN_GAIN, TX_MAX_GAIN, TX_GAIN_STEP); - } else { - UHD_THROW_INVALID_CODE_PATH(); - } -} - -bool rhodium_radio_ctrl_impl::_get_spur_dodging_enabled(uhd::direction_t dir) const -{ - UHD_ASSERT_THROW(_tree->exists(get_arg_path(SPUR_DODGING_ARG_NAME) / "value")); - auto block_value = _tree->access<std::string>(get_arg_path(SPUR_DODGING_ARG_NAME) / "value").get(); - auto dict = _get_tune_args(_tree, _radio_slot, dir); - - // get the current tune_arg for spur_dodging - // if the tune_arg doesn't exist, use the radio block argument instead - std::string spur_dodging_arg = dict.cast<std::string>( - SPUR_DODGING_ARG_NAME, - block_value); - - if (spur_dodging_arg == "enabled") - { - UHD_LOG_TRACE(unique_id(), "_get_spur_dodging_enabled returning enabled"); - return true; - } - else if (spur_dodging_arg == "disabled") { - UHD_LOG_TRACE(unique_id(), "_get_spur_dodging_enabled returning disabled"); - return false; - } - else { - throw uhd::value_error( - str(boost::format("Invalid spur_dodging argument: %s Valid options are [enabled, disabled]") - % spur_dodging_arg)); - } -} - -double rhodium_radio_ctrl_impl::_get_spur_dodging_threshold(uhd::direction_t dir) const -{ - UHD_ASSERT_THROW(_tree->exists(get_arg_path(SPUR_DODGING_THRESHOLD_ARG_NAME) / "value")); - auto block_value = _tree->access<double>(get_arg_path(SPUR_DODGING_THRESHOLD_ARG_NAME) / "value").get(); - auto dict = _get_tune_args(_tree, _radio_slot, dir); - - // get the current tune_arg for spur_dodging_threshold - // if the tune_arg doesn't exist, use the radio block argument instead - auto threshold = dict.cast<double>(SPUR_DODGING_THRESHOLD_ARG_NAME, block_value); - - UHD_LOG_TRACE(unique_id(), "_get_spur_dodging_threshold returning " << threshold); - - return threshold; -} - -bool rhodium_radio_ctrl_impl::_get_highband_spur_reduction_enabled(uhd::direction_t dir) const -{ - UHD_ASSERT_THROW( - _tree->exists(get_arg_path(HIGHBAND_SPUR_REDUCTION_ARG_NAME) / "value")); - auto block_value = _tree - ->access<std::string>( - get_arg_path(HIGHBAND_SPUR_REDUCTION_ARG_NAME) / "value") - .get(); - auto dict = _get_tune_args(_tree, _radio_slot, dir); - - // get the current tune_arg for highband_spur_reduction - // if the tune_arg doesn't exist, use the radio block argument instead - std::string highband_spur_reduction_arg = - dict.cast<std::string>(HIGHBAND_SPUR_REDUCTION_ARG_NAME, block_value); - - if (highband_spur_reduction_arg == "enabled") { - UHD_LOG_TRACE(unique_id(), __func__ << " returning enabled"); - return true; - } else if (highband_spur_reduction_arg == "disabled") { - UHD_LOG_TRACE(unique_id(), __func__ << " returning disabled"); - return false; - } else { - throw uhd::value_error( - str(boost::format("Invalid highband_spur_reduction argument: %s Valid " - "options are [enabled, disabled]") - % highband_spur_reduction_arg)); - } -} - -bool rhodium_radio_ctrl_impl::_get_timed_command_enabled() const -{ - auto& prop = _tree->access<time_spec_t>(fs_path("time") / "cmd"); - // if timed commands are never set, the property will be empty - // if timed commands were set but cleared, time_spec will be set to 0.0 - return !prop.empty() and prop.get() != time_spec_t(0.0); -} - -size_t rhodium_radio_ctrl_impl::get_chan_from_dboard_fe( - const std::string &fe, const direction_t /* dir */ -) { - UHD_ASSERT_THROW(boost::lexical_cast<size_t>(fe) == 0); - return 0; -} - -std::string rhodium_radio_ctrl_impl::get_dboard_fe_from_chan( - const size_t chan, - const direction_t /* dir */ -) { - UHD_ASSERT_THROW(chan == 0); - return "0"; -} - -void rhodium_radio_ctrl_impl::set_rpc_client( - uhd::rpc_client::sptr rpcc, - const uhd::device_addr_t &block_args -) { - _rpcc = rpcc; - _block_args = block_args; - - // Get and verify the MCR before _init_peripherals, which will use this value - // Note: MCR gets set during the init() call (prior to this), which takes - // in arguments from the device args. So if block_args contains a - // master_clock_rate key, then it should better be whatever the device is - // configured to do. - _master_clock_rate = _rpcc->request_with_token<double>(_rpc_prefix + "get_master_clock_rate"); - if (block_args.cast<double>("master_clock_rate", _master_clock_rate) - != _master_clock_rate) { - throw uhd::runtime_error(str( - boost::format("Master clock rate mismatch. Device returns %f MHz, " - "but should have been %f MHz.") - % (_master_clock_rate / 1e6) - % (block_args.cast<double>( - "master_clock_rate", _master_clock_rate) / 1e6) - )); - } - UHD_LOG_DEBUG(unique_id(), - "Master Clock Rate is: " << (_master_clock_rate / 1e6) << " MHz."); - radio_ctrl_impl::set_rate(_master_clock_rate); - - UHD_LOG_TRACE(unique_id(), "Checking for existence of Rhodium DB in slot " << _radio_slot); - const auto all_dboard_info = _rpcc->request<std::vector<std::map<std::string, std::string>>>("get_dboard_info"); - - // There is a bug that if only one DB is plugged into slot B the vector - // will only have 1 element but not be correlated to slot B at all. - // For now, we assume a 1 element array means the DB is in slot A. - if (all_dboard_info.size() <= get_block_id().get_block_count()) - { - UHD_LOG_DEBUG(unique_id(), "No DB detected in slot " << _radio_slot); - // Name and master clock rate are needed for RFNoC init, so set the - // name now and let this function continue to set the MCR - _tree->subtree(fs_path("dboards") / _radio_slot / "tx_frontends" / "0") - ->create<std::string>("name").set("Unknown"); - _tree->subtree(fs_path("dboards") / _radio_slot / "rx_frontends" / "0") - ->create<std::string>("name").set("Unknown"); - } - else { - _dboard_info = all_dboard_info.at(get_block_id().get_block_count()); - UHD_LOG_DEBUG(unique_id(), - "Rhodium DB detected in slot " << _radio_slot << - ". Serial: " << _dboard_info.at("serial")); - _init_defaults(); - _init_peripherals(); - _init_prop_tree(); - - if (block_args.has_key("identify")) { - const std::string identify_val = block_args.get("identify"); - double identify_duration = 0.0; - try { - identify_duration = std::stod(identify_val); - if (!std::isnormal(identify_duration)) { - identify_duration = DEFAULT_IDENTIFY_DURATION; - } - } catch (std::invalid_argument) { - identify_duration = DEFAULT_IDENTIFY_DURATION; - } - - UHD_LOG_INFO(unique_id(), - "Running LED identification process for " << identify_duration - << " seconds."); - _identify_with_leds(identify_duration); - } - } -} - -bool rhodium_radio_ctrl_impl::get_lo_lock_status( - const direction_t dir -) const -{ - return - ((dir == RX_DIRECTION) or _tx_lo->get_lock_status()) and - ((dir == TX_DIRECTION) or _rx_lo->get_lock_status()); -} - -UHD_RFNOC_BLOCK_REGISTER(rhodium_radio_ctrl, "RhodiumRadio"); diff --git a/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_init.cpp b/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_init.cpp deleted file mode 100644 index 356932bc2..000000000 --- a/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_init.cpp +++ /dev/null @@ -1,843 +0,0 @@ -// -// Copyright 2018 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "rhodium_radio_ctrl_impl.hpp" -#include "rhodium_constants.hpp" -#include <uhdlib/usrp/cores/spi_core_3000.hpp> -#include <uhd/utils/log.hpp> -#include <uhd/utils/algorithm.hpp> -#include <uhd/types/eeprom.hpp> -#include <uhd/types/sensors.hpp> -#include <uhd/transport/chdr.hpp> -#include <vector> -#include <string> - -using namespace uhd; -using namespace uhd::usrp; -using namespace uhd::rfnoc; - -namespace { - enum slave_select_t { - SEN_CPLD = 8, - SEN_TX_LO = 1, - SEN_RX_LO = 2, - SEN_LO_DIST = 4 /* Unused */ - }; - - constexpr uint32_t TX_FE_BASE = 224; - constexpr uint32_t RX_FE_BASE = 232; - - constexpr double RHODIUM_DEFAULT_FREQ = 2.5e9; // Hz - // An invalid default index ensures that set gain will apply settings - // the first time it is called - constexpr double RHODIUM_DEFAULT_INVALID_GAIN = -1; // gain index - constexpr double RHODIUM_DEFAULT_GAIN = 0; // gain index - constexpr double RHODIUM_DEFAULT_LO_GAIN = 30; // gain index - constexpr char RHODIUM_DEFAULT_RX_ANTENNA[] = "RX2"; - constexpr char RHODIUM_DEFAULT_TX_ANTENNA[] = "TX/RX"; - constexpr double RHODIUM_DEFAULT_BANDWIDTH = 250e6; // Hz - constexpr auto RHODIUM_DEFAULT_MASH_ORDER = lmx2592_iface::mash_order_t::THIRD; - - //! Rhodium gain profile options - const std::vector<std::string> RHODIUM_GP_OPTIONS = { - "default" - }; - - //! Returns the SPI config used by the CPLD - spi_config_t _get_cpld_spi_config() { - spi_config_t spi_config; - spi_config.use_custom_divider = true; - spi_config.divider = 10; - spi_config.mosi_edge = spi_config_t::EDGE_RISE; - spi_config.miso_edge = spi_config_t::EDGE_FALL; - - return spi_config; - } - - //! Returns the SPI config used by the TX LO - spi_config_t _get_tx_lo_spi_config() { - spi_config_t spi_config; - spi_config.use_custom_divider = true; - spi_config.divider = 10; - spi_config.mosi_edge = spi_config_t::EDGE_RISE; - spi_config.miso_edge = spi_config_t::EDGE_FALL; - - return spi_config; - } - - //! Returns the SPI config used by the RX LO - spi_config_t _get_rx_lo_spi_config() { - spi_config_t spi_config; - spi_config.use_custom_divider = true; - spi_config.divider = 10; - spi_config.mosi_edge = spi_config_t::EDGE_RISE; - spi_config.miso_edge = spi_config_t::EDGE_FALL; - - return spi_config; - } - - std::function<void(uint32_t)> _generate_write_spi( - uhd::spi_iface::sptr spi, - slave_select_t slave, - spi_config_t config - ) { - return [spi, slave, config](const uint32_t transaction) { - spi->write_spi(slave, config, transaction, 24); - }; - } - - std::function<uint32_t(uint32_t)> _generate_read_spi( - uhd::spi_iface::sptr spi, - slave_select_t slave, - spi_config_t config - ) { - return [spi, slave, config](const uint32_t transaction) { - return spi->read_spi(slave, config, transaction, 24); - }; - } -} - -void rhodium_radio_ctrl_impl::_init_defaults() -{ - UHD_LOG_TRACE(unique_id(), "Initializing defaults..."); - const size_t num_rx_chans = get_output_ports().size(); - const size_t num_tx_chans = get_input_ports().size(); - - UHD_LOG_TRACE(unique_id(), - "Num TX chans: " << num_tx_chans - << " Num RX chans: " << num_rx_chans); - - for (size_t chan = 0; chan < num_rx_chans; chan++) { - radio_ctrl_impl::set_rx_frequency(RHODIUM_DEFAULT_FREQ, chan); - radio_ctrl_impl::set_rx_gain(RHODIUM_DEFAULT_INVALID_GAIN, chan); - radio_ctrl_impl::set_rx_antenna(RHODIUM_DEFAULT_RX_ANTENNA, chan); - radio_ctrl_impl::set_rx_bandwidth(RHODIUM_DEFAULT_BANDWIDTH, chan); - } - - for (size_t chan = 0; chan < num_tx_chans; chan++) { - radio_ctrl_impl::set_tx_frequency(RHODIUM_DEFAULT_FREQ, chan); - radio_ctrl_impl::set_tx_gain(RHODIUM_DEFAULT_INVALID_GAIN, chan); - radio_ctrl_impl::set_tx_antenna(RHODIUM_DEFAULT_TX_ANTENNA, chan); - radio_ctrl_impl::set_tx_bandwidth(RHODIUM_DEFAULT_BANDWIDTH, chan); - } - - /** Update default SPP (overwrites the default value from the XML file) **/ - const size_t max_bytes_header = - uhd::transport::vrt::chdr::max_if_hdr_words64 * sizeof(uint64_t); - const size_t default_spp = - (_tree->access<size_t>("mtu/recv").get() - max_bytes_header) - / (2 * sizeof(int16_t)); - UHD_LOG_DEBUG(unique_id(), - "Setting default spp to " << default_spp); - _tree->access<int>(get_arg_path("spp") / "value").set(default_spp); - - // Update configurable block arguments from the device arguments provided - if (_block_args.has_key(SPUR_DODGING_ARG_NAME)) { - _tree->access<std::string>(get_arg_path(SPUR_DODGING_ARG_NAME) / "value") - .set(_block_args.get(SPUR_DODGING_ARG_NAME)); - } - - if (_block_args.has_key(SPUR_DODGING_THRESHOLD_ARG_NAME)) { - _tree->access<double>(get_arg_path(SPUR_DODGING_THRESHOLD_ARG_NAME) / "value") - .set(boost::lexical_cast<double>(_block_args.get(SPUR_DODGING_THRESHOLD_ARG_NAME))); - } - - if (_block_args.has_key(HIGHBAND_SPUR_REDUCTION_ARG_NAME)) { - _tree - ->access<std::string>( - get_arg_path(HIGHBAND_SPUR_REDUCTION_ARG_NAME) / "value") - .set(_block_args.get(HIGHBAND_SPUR_REDUCTION_ARG_NAME)); - } -} - -void rhodium_radio_ctrl_impl::_init_peripherals() -{ - UHD_LOG_TRACE(unique_id(), "Initializing peripherals..."); - - UHD_LOG_TRACE(unique_id(), "Initializing SPI core..."); - _spi = spi_core_3000::make(_get_ctrl(0), - regs::sr_addr(regs::SPI), - regs::rb_addr(regs::RB_SPI) - ); - - UHD_LOG_TRACE(unique_id(), "Initializing CPLD..."); - _cpld = std::make_shared<rhodium_cpld_ctrl>( - _generate_write_spi(this->_spi, SEN_CPLD, _get_cpld_spi_config()), - _generate_read_spi(this->_spi, SEN_CPLD, _get_cpld_spi_config())); - - UHD_LOG_TRACE(unique_id(), "Initializing TX frontend DSP core...") - _tx_fe_core = tx_frontend_core_200::make(_get_ctrl(0), regs::sr_addr(TX_FE_BASE)); - _tx_fe_core->set_dc_offset(tx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); - _tx_fe_core->set_iq_balance(tx_frontend_core_200::DEFAULT_IQ_BALANCE_VALUE); - _tx_fe_core->populate_subtree(_tree->subtree(_root_path / "tx_fe_corrections" / 0)); - - UHD_LOG_TRACE(unique_id(), "Initializing RX frontend DSP core...") - _rx_fe_core = rx_frontend_core_3000::make(_get_ctrl(0), regs::sr_addr(RX_FE_BASE)); - _rx_fe_core->set_adc_rate(_master_clock_rate); - _rx_fe_core->set_dc_offset(rx_frontend_core_3000::DEFAULT_DC_OFFSET_VALUE); - _rx_fe_core->set_dc_offset_auto(rx_frontend_core_3000::DEFAULT_DC_OFFSET_ENABLE); - _rx_fe_core->set_iq_balance(rx_frontend_core_3000::DEFAULT_IQ_BALANCE_VALUE); - _rx_fe_core->populate_subtree(_tree->subtree(_root_path / "rx_fe_corrections" / 0)); - - UHD_LOG_TRACE(unique_id(), "Writing initial gain values..."); - set_tx_gain(RHODIUM_DEFAULT_GAIN, 0); - set_tx_lo_gain(RHODIUM_DEFAULT_LO_GAIN, RHODIUM_LO1, 0); - set_rx_gain(RHODIUM_DEFAULT_GAIN, 0); - set_rx_lo_gain(RHODIUM_DEFAULT_LO_GAIN, RHODIUM_LO1, 0); - - UHD_LOG_TRACE(unique_id(), "Initializing TX LO..."); - _tx_lo = lmx2592_iface::make( - _generate_write_spi(this->_spi, SEN_TX_LO, _get_tx_lo_spi_config()), - _generate_read_spi(this->_spi, SEN_TX_LO, _get_tx_lo_spi_config())); - - UHD_LOG_TRACE(unique_id(), "Writing initial TX LO state..."); - _tx_lo->set_reference_frequency(RHODIUM_LO1_REF_FREQ); - _tx_lo->set_mash_order(RHODIUM_DEFAULT_MASH_ORDER); - - UHD_LOG_TRACE(unique_id(), "Initializing RX LO..."); - _rx_lo = lmx2592_iface::make( - _generate_write_spi(this->_spi, SEN_RX_LO, _get_rx_lo_spi_config()), - _generate_read_spi(this->_spi, SEN_RX_LO, _get_rx_lo_spi_config())); - - UHD_LOG_TRACE(unique_id(), "Writing initial RX LO state..."); - _rx_lo->set_reference_frequency(RHODIUM_LO1_REF_FREQ); - _rx_lo->set_mash_order(RHODIUM_DEFAULT_MASH_ORDER); - - UHD_LOG_TRACE(unique_id(), "Initializing GPIOs..."); - _gpio = - usrp::gpio_atr::gpio_atr_3000::make( - _get_ctrl(0), - regs::sr_addr(regs::GPIO), - regs::rb_addr(regs::RB_DB_GPIO) - ); - _gpio->set_atr_mode( - usrp::gpio_atr::MODE_ATR, // Enable ATR mode for Rhodium bits - RHODIUM_GPIO_MASK - ); - _gpio->set_atr_mode( - usrp::gpio_atr::MODE_GPIO, // Disable ATR mode for unused bits - ~RHODIUM_GPIO_MASK - ); - _gpio->set_gpio_ddr( - usrp::gpio_atr::DDR_OUTPUT, // Make all GPIOs outputs - usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL - ); - - UHD_LOG_TRACE(unique_id(), "Set initial ATR values..."); - _update_atr(RHODIUM_DEFAULT_TX_ANTENNA, TX_DIRECTION); - _update_atr(RHODIUM_DEFAULT_RX_ANTENNA, RX_DIRECTION); - - // Updating the TX frequency path may include an update to SW10, which is - // GPIO controlled, so this must follow CPLD and GPIO initialization - UHD_LOG_TRACE(unique_id(), "Writing initial switch values..."); - _update_tx_freq_switches(RHODIUM_DEFAULT_FREQ); - _update_rx_freq_switches(RHODIUM_DEFAULT_FREQ); - - // Antenna setting requires both CPLD and GPIO control - UHD_LOG_TRACE(unique_id(), "Setting initial antenna settings"); - _update_tx_output_switches(RHODIUM_DEFAULT_TX_ANTENNA); - _update_rx_input_switches(RHODIUM_DEFAULT_RX_ANTENNA); - - UHD_LOG_TRACE(unique_id(), "Checking for existence of LO Distribution board"); - _lo_dist_present = _rpcc->request_with_token<bool>(_rpc_prefix + "is_lo_dist_present"); - UHD_LOG_DEBUG(unique_id(), str(boost::format("LO distribution board is%s present") % (_lo_dist_present ? "" : " NOT"))); -} - -void rhodium_radio_ctrl_impl::_init_frontend_subtree( - uhd::property_tree::sptr subtree, - const size_t chan_idx -) { - const fs_path tx_fe_path = fs_path("tx_frontends") / chan_idx; - const fs_path rx_fe_path = fs_path("rx_frontends") / chan_idx; - UHD_LOG_TRACE(unique_id(), - "Adding non-RFNoC block properties for channel " << chan_idx << - " to prop tree path " << tx_fe_path << " and " << rx_fe_path); - // TX Standard attributes - subtree->create<std::string>(tx_fe_path / "name") - .set(str(boost::format("Rhodium"))) - ; - subtree->create<std::string>(tx_fe_path / "connection") - .add_coerced_subscriber([this](const std::string& conn){ - this->_set_tx_fe_connection(conn); - }) - .set_publisher([this](){ - return this->_get_tx_fe_connection(); - }) - ; - subtree->create<device_addr_t>(tx_fe_path / "tune_args") - .set(device_addr_t()) - ; - // RX Standard attributes - subtree->create<std::string>(rx_fe_path / "name") - .set(str(boost::format("Rhodium"))) - ; - subtree->create<std::string>(rx_fe_path / "connection") - .add_coerced_subscriber([this](const std::string& conn){ - this->_set_rx_fe_connection(conn); - }) - .set_publisher([this](){ - return this->_get_rx_fe_connection(); - }) - ; - subtree->create<device_addr_t>(rx_fe_path / "tune_args") - .set(device_addr_t()) - ; - // TX Antenna - subtree->create<std::string>(tx_fe_path / "antenna" / "value") - .add_coerced_subscriber([this, chan_idx](const std::string &ant){ - this->set_tx_antenna(ant, chan_idx); - }) - .set_publisher([this, chan_idx](){ - return this->get_tx_antenna(chan_idx); - }) - ; - subtree->create<std::vector<std::string>>(tx_fe_path / "antenna" / "options") - .set(RHODIUM_TX_ANTENNAS) - .add_coerced_subscriber([](const std::vector<std::string> &){ - throw uhd::runtime_error( - "Attempting to update antenna options!"); - }) - ; - // RX Antenna - subtree->create<std::string>(rx_fe_path / "antenna" / "value") - .add_coerced_subscriber([this, chan_idx](const std::string &ant){ - this->set_rx_antenna(ant, chan_idx); - }) - .set_publisher([this, chan_idx](){ - return this->get_rx_antenna(chan_idx); - }) - ; - subtree->create<std::vector<std::string>>(rx_fe_path / "antenna" / "options") - .set(RHODIUM_RX_ANTENNAS) - .add_coerced_subscriber([](const std::vector<std::string> &){ - throw uhd::runtime_error( - "Attempting to update antenna options!"); - }) - ; - // TX frequency - subtree->create<double>(tx_fe_path / "freq" / "value") - .set_coercer([this, chan_idx](const double freq){ - return this->set_tx_frequency(freq, chan_idx); - }) - .set_publisher([this, chan_idx](){ - return this->get_tx_frequency(chan_idx); - }) - ; - subtree->create<meta_range_t>(tx_fe_path / "freq" / "range") - .set(meta_range_t(RHODIUM_MIN_FREQ, RHODIUM_MAX_FREQ, 1.0)) - .add_coerced_subscriber([](const meta_range_t &){ - throw uhd::runtime_error( - "Attempting to update freq range!"); - }) - ; - // RX frequency - subtree->create<double>(rx_fe_path / "freq" / "value") - .set_coercer([this, chan_idx](const double freq){ - return this->set_rx_frequency(freq, chan_idx); - }) - .set_publisher([this, chan_idx](){ - return this->get_rx_frequency(chan_idx); - }) - ; - subtree->create<meta_range_t>(rx_fe_path / "freq" / "range") - .set(meta_range_t(RHODIUM_MIN_FREQ, RHODIUM_MAX_FREQ, 1.0)) - .add_coerced_subscriber([](const meta_range_t &){ - throw uhd::runtime_error( - "Attempting to update freq range!"); - }) - ; - // TX bandwidth - subtree->create<double>(tx_fe_path / "bandwidth" / "value") - .set_coercer([this, chan_idx](const double bw){ - return this->set_tx_bandwidth(bw, chan_idx); - }) - .set_publisher([this, chan_idx](){ - return this->get_tx_bandwidth(chan_idx); - }) - ; - subtree->create<meta_range_t>(tx_fe_path / "bandwidth" / "range") - .set(meta_range_t(RHODIUM_DEFAULT_BANDWIDTH, RHODIUM_DEFAULT_BANDWIDTH)) - .add_coerced_subscriber([](const meta_range_t &){ - throw uhd::runtime_error( - "Attempting to update bandwidth range!"); - }) - ; - // RX bandwidth - subtree->create<double>(rx_fe_path / "bandwidth" / "value") - .set_coercer([this, chan_idx](const double bw){ - return this->set_rx_bandwidth(bw, chan_idx); - }) - .set_publisher([this, chan_idx](){ - return this->get_rx_bandwidth(chan_idx); - }) - ; - subtree->create<meta_range_t>(rx_fe_path / "bandwidth" / "range") - .set(meta_range_t(RHODIUM_DEFAULT_BANDWIDTH, RHODIUM_DEFAULT_BANDWIDTH)) - .add_coerced_subscriber([](const meta_range_t &){ - throw uhd::runtime_error( - "Attempting to update bandwidth range!"); - }) - ; - // TX gains - subtree->create<double>(tx_fe_path / "gains" / "all" / "value") - .set_coercer([this, chan_idx](const double gain){ - return this->set_tx_gain(gain, chan_idx); - }) - .set_publisher([this, chan_idx](){ - return radio_ctrl_impl::get_tx_gain(chan_idx); - }) - ; - subtree->create<meta_range_t>(tx_fe_path / "gains" / "all" / "range") - .add_coerced_subscriber([](const meta_range_t &){ - throw uhd::runtime_error( - "Attempting to update gain range!"); - }) - .set_publisher([](){ - return rhodium_radio_ctrl_impl::_get_gain_range(TX_DIRECTION); - }) - ; - - subtree->create<std::vector<std::string>>(tx_fe_path / "gains/all/profile/options") - .set(RHODIUM_GP_OPTIONS); - - subtree->create<std::string>(tx_fe_path / "gains/all/profile/value") - .set_coercer([this](const std::string& profile){ - std::string return_profile = profile; - if (!uhd::has(RHODIUM_GP_OPTIONS, profile)) - { - return_profile = "default"; - } - _gain_profile[TX_DIRECTION] = return_profile; - return return_profile; - }) - .set_publisher([this](){ - return _gain_profile[TX_DIRECTION]; - }) - ; - - // RX gains - subtree->create<double>(rx_fe_path / "gains" / "all" / "value") - .set_coercer([this, chan_idx](const double gain){ - return this->set_rx_gain(gain, chan_idx); - }) - .set_publisher([this, chan_idx](){ - return radio_ctrl_impl::get_rx_gain(chan_idx); - }) - ; - - subtree->create<meta_range_t>(rx_fe_path / "gains" / "all" / "range") - .add_coerced_subscriber([](const meta_range_t &){ - throw uhd::runtime_error( - "Attempting to update gain range!"); - }) - .set_publisher([](){ - return rhodium_radio_ctrl_impl::_get_gain_range(RX_DIRECTION); - }) - ; - - subtree->create<std::vector<std::string> >(rx_fe_path / "gains/all/profile/options") - .set(RHODIUM_GP_OPTIONS); - - subtree->create<std::string>(rx_fe_path / "gains/all/profile/value") - .set_coercer([this](const std::string& profile){ - std::string return_profile = profile; - if (!uhd::has(RHODIUM_GP_OPTIONS, profile)) - { - return_profile = "default"; - } - _gain_profile[RX_DIRECTION] = return_profile; - return return_profile; - }) - .set_publisher([this](){ - return _gain_profile[RX_DIRECTION]; - }) - ; - - // TX LO lock sensor - subtree->create<sensor_value_t>(tx_fe_path / "sensors" / "lo_locked") - .set(sensor_value_t("all_los", false, "locked", "unlocked")) - .add_coerced_subscriber([](const sensor_value_t &){ - throw uhd::runtime_error( - "Attempting to write to sensor!"); - }) - .set_publisher([this](){ - return sensor_value_t( - "all_los", - this->get_lo_lock_status(TX_DIRECTION), - "locked", "unlocked" - ); - }) - ; - // RX LO lock sensor - subtree->create<sensor_value_t>(rx_fe_path / "sensors" / "lo_locked") - .set(sensor_value_t("all_los", false, "locked", "unlocked")) - .add_coerced_subscriber([](const sensor_value_t &){ - throw uhd::runtime_error( - "Attempting to write to sensor!"); - }) - .set_publisher([this](){ - return sensor_value_t( - "all_los", - this->get_lo_lock_status(RX_DIRECTION), - "locked", "unlocked" - ); - }) - ; - //LO Specific - //RX LO - //RX LO1 Frequency - subtree->create<double>(rx_fe_path / "los"/RHODIUM_LO1/"freq/value") - .set_publisher([this,chan_idx](){ - return this->get_rx_lo_freq(RHODIUM_LO1, chan_idx); - }) - .set_coercer([this,chan_idx](const double freq){ - return this->set_rx_lo_freq(freq, RHODIUM_LO1, chan_idx); - }) - ; - subtree->create<meta_range_t>(rx_fe_path / "los"/RHODIUM_LO1/"freq/range") - .set_publisher([this,chan_idx](){ - return this->get_rx_lo_freq_range(RHODIUM_LO1, chan_idx); - }) - ; - //RX LO1 Source - subtree->create<std::vector<std::string>>(rx_fe_path / "los"/RHODIUM_LO1/"source/options") - .set_publisher([this,chan_idx](){ - return this->get_rx_lo_sources(RHODIUM_LO1, chan_idx); - }) - ; - subtree->create<std::string>(rx_fe_path / "los"/RHODIUM_LO1/"source/value") - .add_coerced_subscriber([this,chan_idx](std::string src){ - this->set_rx_lo_source(src, RHODIUM_LO1,chan_idx); - }) - .set_publisher([this,chan_idx](){ - return this->get_rx_lo_source(RHODIUM_LO1, chan_idx); - }) - ; - //RX LO1 Export - subtree->create<bool>(rx_fe_path / "los"/RHODIUM_LO1/"export") - .add_coerced_subscriber([this,chan_idx](bool enabled){ - this->set_rx_lo_export_enabled(enabled, RHODIUM_LO1, chan_idx); - }) - ; - //RX LO1 Gain - subtree->create<double>(rx_fe_path / "los" /RHODIUM_LO1/ "gains" / RHODIUM_LO_GAIN / "value") - .set_publisher([this,chan_idx](){ - return this->get_rx_lo_gain(RHODIUM_LO1, chan_idx); - }) - .set_coercer([this,chan_idx](const double gain){ - return this->set_rx_lo_gain(gain, RHODIUM_LO1, chan_idx); - }) - ; - subtree->create<meta_range_t>(rx_fe_path / "los" /RHODIUM_LO1/ "gains" / RHODIUM_LO_GAIN / "range") - .set_publisher([](){ - return rhodium_radio_ctrl_impl::_get_lo_gain_range(); - }) - .add_coerced_subscriber([](const meta_range_t &){ - throw uhd::runtime_error("Attempting to update LO gain range!"); - }) - ; - //RX LO1 Output Power - subtree->create<double>(rx_fe_path / "los" /RHODIUM_LO1/ "gains" / RHODIUM_LO_POWER / "value") - .set_publisher([this,chan_idx](){ - return this->get_rx_lo_power(RHODIUM_LO1, chan_idx); - }) - .set_coercer([this,chan_idx](const double gain){ - return this->set_rx_lo_power(gain, RHODIUM_LO1, chan_idx); - }) - ; - subtree->create<meta_range_t>(rx_fe_path / "los" /RHODIUM_LO1/ "gains" / RHODIUM_LO_POWER / "range") - .set_publisher([](){ - return rhodium_radio_ctrl_impl::_get_lo_power_range(); - }) - .add_coerced_subscriber([](const meta_range_t &){ - throw uhd::runtime_error("Attempting to update LO output power range!"); - }) - ; - //RX LO2 Frequency - subtree->create<double>(rx_fe_path / "los"/RHODIUM_LO2/"freq/value") - .set_publisher([this,chan_idx](){ - return this->get_rx_lo_freq(RHODIUM_LO2, chan_idx); - }) - .set_coercer([this,chan_idx](double freq){ - return this->set_rx_lo_freq(freq, RHODIUM_LO2, chan_idx); - }) - ; - subtree->create<meta_range_t>(rx_fe_path / "los"/RHODIUM_LO2/"freq/range") - .set_publisher([this,chan_idx](){ - return this->get_rx_lo_freq_range(RHODIUM_LO2, chan_idx); - }) - ; - //RX LO2 Source - subtree->create<std::vector<std::string>>(rx_fe_path / "los"/RHODIUM_LO2/"source/options") - .set_publisher([this,chan_idx](){ - return this->get_rx_lo_sources(RHODIUM_LO2, chan_idx); - }) - ; - subtree->create<std::string>(rx_fe_path / "los"/RHODIUM_LO2/"source/value") - .add_coerced_subscriber([this,chan_idx](std::string src){ - this->set_rx_lo_source(src, RHODIUM_LO2, chan_idx); - }) - .set_publisher([this,chan_idx](){ - return this->get_rx_lo_source(RHODIUM_LO2, chan_idx); - }) - ; - //RX LO2 Export - subtree->create<bool>(rx_fe_path / "los"/RHODIUM_LO2/"export") - .add_coerced_subscriber([this,chan_idx](bool enabled){ - this->set_rx_lo_export_enabled(enabled, RHODIUM_LO2, chan_idx); - }); - //RX ALL LOs - subtree->create<std::string>(rx_fe_path / "los" / ALL_LOS / "source/value") - .add_coerced_subscriber([this,chan_idx](std::string src) { - this->set_rx_lo_source(src, ALL_LOS, chan_idx); - }) - .set_publisher([this,chan_idx]() { - return this->get_rx_lo_source(ALL_LOS, chan_idx); - }) - ; - subtree->create<std::vector<std::string>>(rx_fe_path / "los" / ALL_LOS / "source/options") - .set_publisher([this, chan_idx]() { - return this->get_rx_lo_sources(ALL_LOS, chan_idx); - }) - ; - subtree->create<bool>(rx_fe_path / "los" / ALL_LOS / "export") - .add_coerced_subscriber([this,chan_idx](bool enabled){ - this->set_rx_lo_export_enabled(enabled, ALL_LOS, chan_idx); - }) - .set_publisher([this,chan_idx](){ - return this->get_rx_lo_export_enabled(ALL_LOS, chan_idx); - }) - ; - //TX LO - //TX LO1 Frequency - subtree->create<double>(tx_fe_path / "los"/RHODIUM_LO1/"freq/value ") - .set_publisher([this,chan_idx](){ - return this->get_tx_lo_freq(RHODIUM_LO1, chan_idx); - }) - .set_coercer([this,chan_idx](double freq){ - return this->set_tx_lo_freq(freq, RHODIUM_LO1, chan_idx); - }) - ; - subtree->create<meta_range_t>(tx_fe_path / "los"/RHODIUM_LO1/"freq/range") - .set_publisher([this,chan_idx](){ - return this->get_rx_lo_freq_range(RHODIUM_LO1, chan_idx); - }) - ; - //TX LO1 Source - subtree->create<std::vector<std::string>>(tx_fe_path / "los"/RHODIUM_LO1/"source/options") - .set_publisher([this,chan_idx](){ - return this->get_tx_lo_sources(RHODIUM_LO1, chan_idx); - }) - ; - subtree->create<std::string>(tx_fe_path / "los"/RHODIUM_LO1/"source/value") - .add_coerced_subscriber([this,chan_idx](std::string src){ - this->set_tx_lo_source(src, RHODIUM_LO1, chan_idx); - }) - .set_publisher([this,chan_idx](){ - return this->get_tx_lo_source(RHODIUM_LO1, chan_idx); - }) - ; - //TX LO1 Export - subtree->create<bool>(tx_fe_path / "los"/RHODIUM_LO1/"export") - .add_coerced_subscriber([this,chan_idx](bool enabled){ - this->set_tx_lo_export_enabled(enabled, RHODIUM_LO1, chan_idx); - }) - ; - //TX LO1 Gain - subtree->create<double>(tx_fe_path / "los" /RHODIUM_LO1/ "gains" / RHODIUM_LO_GAIN / "value") - .set_publisher([this,chan_idx](){ - return this->get_tx_lo_gain(RHODIUM_LO1, chan_idx); - }) - .set_coercer([this,chan_idx](const double gain){ - return this->set_tx_lo_gain(gain, RHODIUM_LO1, chan_idx); - }) - ; - subtree->create<meta_range_t>(tx_fe_path / "los" /RHODIUM_LO1/ "gains" / RHODIUM_LO_GAIN / "range") - .set_publisher([](){ - return rhodium_radio_ctrl_impl::_get_lo_gain_range(); - }) - .add_coerced_subscriber([](const meta_range_t &){ - throw uhd::runtime_error("Attempting to update LO gain range!"); - }) - ; - //TX LO1 Output Power - subtree->create<double>(tx_fe_path / "los" /RHODIUM_LO1/ "gains" / RHODIUM_LO_POWER / "value") - .set_publisher([this,chan_idx](){ - return this->get_tx_lo_power(RHODIUM_LO1, chan_idx); - }) - .set_coercer([this,chan_idx](const double gain){ - return this->set_tx_lo_power(gain, RHODIUM_LO1, chan_idx); - }) - ; - subtree->create<meta_range_t>(tx_fe_path / "los" /RHODIUM_LO1/ "gains" / RHODIUM_LO_POWER / "range") - .set_publisher([](){ - return rhodium_radio_ctrl_impl::_get_lo_power_range(); - }) - .add_coerced_subscriber([](const meta_range_t &){ - throw uhd::runtime_error("Attempting to update LO output power range!"); - }) - ; - //TX LO2 Frequency - subtree->create<double>(tx_fe_path / "los"/RHODIUM_LO2/"freq/value") - .set_publisher([this,chan_idx](){ - return this->get_tx_lo_freq(RHODIUM_LO2, chan_idx); - }) - .set_coercer([this,chan_idx](double freq){ - return this->set_tx_lo_freq(freq, RHODIUM_LO2, chan_idx); - }) - ; - subtree->create<meta_range_t>(tx_fe_path / "los"/RHODIUM_LO2/"freq/range") - .set_publisher([this,chan_idx](){ - return this->get_tx_lo_freq_range(RHODIUM_LO2,chan_idx); - }) - ; - //TX LO2 Source - subtree->create<std::vector<std::string>>(tx_fe_path / "los"/RHODIUM_LO2/"source/options") - .set_publisher([this,chan_idx](){ - return this->get_tx_lo_sources(RHODIUM_LO2, chan_idx); - }) - ; - subtree->create<std::string>(tx_fe_path / "los"/RHODIUM_LO2/"source/value") - .add_coerced_subscriber([this,chan_idx](std::string src){ - this->set_tx_lo_source(src, RHODIUM_LO2, chan_idx); - }) - .set_publisher([this,chan_idx](){ - return this->get_tx_lo_source(RHODIUM_LO2, chan_idx); - }) - ; - //TX LO2 Export - subtree->create<bool>(tx_fe_path / "los"/RHODIUM_LO2/"export") - .add_coerced_subscriber([this,chan_idx](bool enabled){ - this->set_tx_lo_export_enabled(enabled, RHODIUM_LO2, chan_idx); - }) - ; - //TX ALL LOs - subtree->create<std::string>(tx_fe_path / "los" / ALL_LOS / "source/value") - .add_coerced_subscriber([this,chan_idx](std::string src) { - this->set_tx_lo_source(src, ALL_LOS, chan_idx); - }) - .set_publisher([this,chan_idx]() { - return this->get_tx_lo_source(ALL_LOS, chan_idx); - }) - ; - subtree->create<std::vector<std::string>>(tx_fe_path / "los" / ALL_LOS / "source/options") - .set_publisher([this, chan_idx]() { - return this->get_tx_lo_sources(ALL_LOS, chan_idx); - }) - ; - subtree->create<bool>(tx_fe_path / "los" / ALL_LOS / "export") - .add_coerced_subscriber([this,chan_idx](bool enabled){ - this->set_tx_lo_export_enabled(enabled, ALL_LOS, chan_idx); - }) - .set_publisher([this,chan_idx](){ - return this->get_tx_lo_export_enabled(ALL_LOS, chan_idx); - }) - ; - - //LO Distribution Output Ports - if (_lo_dist_present) { - for (const auto& port : LO_OUTPUT_PORT_NAMES) { - subtree->create<bool>(tx_fe_path / "los" / RHODIUM_LO1 / "lo_distribution" / port / "export") - .add_coerced_subscriber([this, chan_idx, port](bool enabled) { - this->set_tx_lo_output_enabled(enabled, port, chan_idx); - }) - .set_publisher([this, chan_idx, port]() { - return this->get_tx_lo_output_enabled(port, chan_idx); - }) - ; - subtree->create<bool>(rx_fe_path / "los" / RHODIUM_LO1 / "lo_distribution" / port / "export") - .add_coerced_subscriber([this, chan_idx, port](bool enabled) { - this->set_rx_lo_output_enabled(enabled, port, chan_idx); - }) - .set_publisher([this, chan_idx, port]() { - return this->get_rx_lo_output_enabled(port, chan_idx); - }) - ; - } - } -} - -void rhodium_radio_ctrl_impl::_init_prop_tree() -{ - const fs_path fe_base = fs_path("dboards") / _radio_slot; - this->_init_frontend_subtree(_tree->subtree(fe_base), 0); - - // legacy EEPROM paths - auto eeprom_get = [this]() { - auto eeprom = dboard_eeprom_t(); - eeprom.id = boost::lexical_cast<uint16_t>(_dboard_info.at("pid")); - eeprom.revision = _dboard_info.at("rev"); - eeprom.serial = _dboard_info.at("serial"); - return eeprom; - }; - - auto eeprom_set = [](dboard_eeprom_t) { - throw uhd::not_implemented_error("Setting DB EEPROM from this interface not implemented"); - }; - - _tree->create<dboard_eeprom_t>(fe_base / "rx_eeprom") - .set_publisher(eeprom_get) - .add_coerced_subscriber(eeprom_set); - - _tree->create<dboard_eeprom_t>(fe_base / "tx_eeprom") - .set_publisher(eeprom_get) - .add_coerced_subscriber(eeprom_set); - - // EEPROM paths subject to change FIXME - _tree->create<eeprom_map_t>(_root_path / "eeprom") - .set(eeprom_map_t()); - - _tree->create<int>("rx_codecs" / _radio_slot / "gains"); - _tree->create<int>("tx_codecs" / _radio_slot / "gains"); - _tree->create<std::string>("rx_codecs" / _radio_slot / "name").set("ad9695-625"); - _tree->create<std::string>("tx_codecs" / _radio_slot / "name").set("dac37j82"); - - // The tick_rate is equivalent to the master clock rate of the DB in slot A - if (_radio_slot == "A") - { - UHD_ASSERT_THROW(!_tree->exists("tick_rate")); - // set_rate sets the clock rate of the entire device, not just this DB, - // so only add DB A's set and get functions to the tree. - _tree->create<double>("tick_rate") - .set_publisher([this](){ return this->get_rate(); }) - .add_coerced_subscriber([this](double rate) { return this->set_rate(rate); }) - ; - } -} - -void rhodium_radio_ctrl_impl::_init_mpm_sensors( - const direction_t dir, - const size_t chan_idx -) { - const std::string trx = (dir == RX_DIRECTION) ? "RX" : "TX"; - const fs_path fe_path = - fs_path("dboards") / _radio_slot / - (dir == RX_DIRECTION ? "rx_frontends" : "tx_frontends") / chan_idx; - auto sensor_list = - _rpcc->request_with_token<std::vector<std::string>>( - this->_rpc_prefix + "get_sensors", trx); - UHD_LOG_TRACE(unique_id(), - "Chan " << chan_idx << ": Found " - << sensor_list.size() << " " << trx << " sensors."); - for (const auto &sensor_name : sensor_list) { - UHD_LOG_TRACE(unique_id(), - "Adding " << trx << " sensor " << sensor_name); - _tree->create<sensor_value_t>(fe_path / "sensors" / sensor_name) - .add_coerced_subscriber([](const sensor_value_t &){ - throw uhd::runtime_error( - "Attempting to write to sensor!"); - }) - .set_publisher([this, trx, sensor_name, chan_idx](){ - return sensor_value_t( - this->_rpcc->request_with_token<sensor_value_t::sensor_map_t>( - this->_rpc_prefix + "get_sensor", - trx, sensor_name, chan_idx) - ); - }) - ; - } -} - diff --git a/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_lo.cpp b/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_lo.cpp deleted file mode 100644 index 405862485..000000000 --- a/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_lo.cpp +++ /dev/null @@ -1,726 +0,0 @@ -// -// Copyright 2018 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "rhodium_radio_ctrl_impl.hpp" -#include "rhodium_constants.hpp" -#include <uhdlib/utils/narrow.hpp> -#include <uhd/utils/log.hpp> -#include <uhd/utils/algorithm.hpp> -#include <uhd/types/direction.hpp> -#include <uhd/exception.hpp> -#include <boost/format.hpp> - -using namespace uhd; -using namespace uhd::usrp; -using namespace uhd::rfnoc; - -namespace { - constexpr int NUM_THRESHOLDS = 13; - constexpr std::array<double, NUM_THRESHOLDS> FREQ_THRESHOLDS = - {0.45e9, 0.5e9, 1e9, 1.5e9, 2e9, 2.5e9, 3e9, 3.55e9, 4e9, 4.5e9, 5e9, 5.5e9, 6e9}; - constexpr std::array<int, NUM_THRESHOLDS> LMX_GAIN_VALUES = - {18, 18, 17, 17, 17, 16, 12, 11, 11, 11, 10, 13, 18}; - const std::array<int, NUM_THRESHOLDS> DSA_RX_GAIN_VALUES = - {19, 19, 21, 21, 20, 20, 22, 21, 20, 22, 22, 24, 26}; - const std::array<int, NUM_THRESHOLDS> DSA_TX_GAIN_VALUES = - {19, 19, 21, 21, 20, 20, 22, 21, 22, 24, 24, 26, 28}; - - // Describes the lowband LO in terms of the master clock rate - const std::map<double, double> MCR_TO_LOWBAND_IF = { - {200e6, 1200e6}, - {245.76e6, 1228.8e6}, - {250e6, 1500e6}, - }; - - // validation helpers - - std::vector<std::string> _get_lo_names() - { - return { RHODIUM_LO1, RHODIUM_LO2 }; - } - - void _validate_lo_name(const std::string& name, const std::string& function_name) - { - if (!uhd::has(_get_lo_names(), name) and name != radio_ctrl::ALL_LOS) { - throw uhd::value_error(str(boost::format( - "%s was called with an invalid LO name: %s") - % function_name - % name)); - } - } - - // object agnostic helpers - std::vector<std::string> _get_lo_sources(const std::string& name) - { - if (name == RHODIUM_LO1 or name == radio_ctrl::ALL_LOS) { - return { "internal", "external" }; - } else { - return { "internal" }; - } - } -} - -/****************************************************************************** - * Property Getters - *****************************************************************************/ - -std::vector<std::string> rhodium_radio_ctrl_impl::get_tx_lo_names( - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_tx_lo_names(chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - - return _get_lo_names(); -} - -std::vector<std::string> rhodium_radio_ctrl_impl::get_rx_lo_names( - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_rx_lo_names(chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - - return _get_lo_names(); -} - -std::vector<std::string> rhodium_radio_ctrl_impl::get_tx_lo_sources( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_tx_lo_sources(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_tx_lo_sources"); - - return _get_lo_sources(name); -} - -std::vector<std::string> rhodium_radio_ctrl_impl::get_rx_lo_sources( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_rx_lo_sources(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_rx_lo_sources"); - - return _get_lo_sources(name); -} - -freq_range_t rhodium_radio_ctrl_impl::_get_lo_freq_range(const std::string &name) const -{ - if (name == RHODIUM_LO1) { - return freq_range_t{ RHODIUM_LO1_MIN_FREQ, RHODIUM_LO1_MAX_FREQ }; - } else if (name == RHODIUM_LO2) { - // The Lowband LO is a fixed frequency - return freq_range_t{ _get_lowband_lo_freq(), _get_lowband_lo_freq() }; - } else { - throw uhd::runtime_error( - "LO frequency range must be retrieved for each stage individually"); - } -} - -freq_range_t rhodium_radio_ctrl_impl::get_tx_lo_freq_range( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_tx_lo_freq_range(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_tx_lo_freq_range"); - - return _get_lo_freq_range(name); -} - -freq_range_t rhodium_radio_ctrl_impl::get_rx_lo_freq_range( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_rx_lo_freq_range(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_rx_lo_freq_range"); - - return _get_lo_freq_range(name); -} - -/****************************************************************************** - * Frequency Control - *****************************************************************************/ - -double rhodium_radio_ctrl_impl::set_tx_lo_freq( - const double freq, - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_tx_lo_freq(freq=" << freq << ", name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "set_tx_lo_freq"); - - if (name == ALL_LOS) { - throw uhd::runtime_error("LO frequency must be set for each stage individually"); - } - if (name == RHODIUM_LO2) { - UHD_LOG_WARNING(unique_id(), "The Lowband LO cannot be tuned"); - return _get_lowband_lo_freq(); - } - - const auto sd_enabled = _get_spur_dodging_enabled(TX_DIRECTION); - const auto sd_threshold = _get_spur_dodging_threshold(TX_DIRECTION); - - _tx_lo_freq = _tx_lo->set_frequency(freq, sd_enabled, sd_threshold); - set_tx_lo_gain(_get_lo_dsa_setting(_tx_lo_freq, TX_DIRECTION), RHODIUM_LO1, chan); - set_tx_lo_power(_get_lo_power_setting(_tx_lo_freq), RHODIUM_LO1, chan); - _cpld->set_tx_lo_path(_tx_lo_freq); - - return _tx_lo_freq; -} - -double rhodium_radio_ctrl_impl::set_rx_lo_freq( - const double freq, - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_rx_lo_freq(freq=" << freq << ", name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "set_rx_lo_freq"); - - if (name == ALL_LOS) { - throw uhd::runtime_error("LO frequency must be set for each stage individually"); - } - if (name == RHODIUM_LO2) { - UHD_LOG_WARNING(unique_id(), "The Lowband LO cannot be tuned"); - return _get_lowband_lo_freq(); - } - - const auto sd_enabled = _get_spur_dodging_enabled(RX_DIRECTION); - const auto sd_threshold = _get_spur_dodging_threshold(RX_DIRECTION); - - _rx_lo_freq = _rx_lo->set_frequency(freq, sd_enabled, sd_threshold); - set_rx_lo_gain(_get_lo_dsa_setting(_rx_lo_freq, RX_DIRECTION), RHODIUM_LO1, chan); - set_rx_lo_power(_get_lo_power_setting(_rx_lo_freq), RHODIUM_LO1, chan); - _cpld->set_rx_lo_path(_rx_lo_freq); - - return _rx_lo_freq; -} - -double rhodium_radio_ctrl_impl::get_tx_lo_freq( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_tx_lo_freq(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_tx_lo_freq"); - - if (name == ALL_LOS) { - throw uhd::runtime_error( - "LO frequency must be retrieved for each stage individually"); - } - - return (name == RHODIUM_LO1) ? _tx_lo_freq : _get_lowband_lo_freq(); -} - -double rhodium_radio_ctrl_impl::get_rx_lo_freq( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_rx_lo_freq(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_rx_lo_freq"); - - if (name == ALL_LOS) { - throw uhd::runtime_error( - "LO frequency must be retrieved for each stage individually"); - } - - return (name == RHODIUM_LO1) ? _rx_lo_freq : _get_lowband_lo_freq(); -} - -/****************************************************************************** - * Source Control - *****************************************************************************/ - -void rhodium_radio_ctrl_impl::set_tx_lo_source( - const std::string& src, - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_tx_lo_source(src=" << src << ", name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "set_tx_lo_source"); - - if (name == RHODIUM_LO2) { - if (src != "internal") { - throw uhd::value_error("The Lowband LO can only be set to internal"); - } - return; - } - - if (src == "internal") { - _tx_lo->set_output_enable(lmx2592_iface::output_t::RF_OUTPUT_A, true); - _cpld->set_tx_lo_source(rhodium_cpld_ctrl::tx_lo_input_sel_t::TX_LO_INPUT_SEL_INTERNAL); - } else if (src == "external") { - _tx_lo->set_output_enable(lmx2592_iface::output_t::RF_OUTPUT_A, false); - _cpld->set_tx_lo_source(rhodium_cpld_ctrl::tx_lo_input_sel_t::TX_LO_INPUT_SEL_EXTERNAL); - } else { - throw uhd::value_error(str(boost::format("set_tx_lo_source was called with an invalid LO source: %s Valid sources are [internal, external]") % src)); - } - - const bool enable_corrections = not _is_tx_lowband(get_tx_frequency(0)) - and src == "internal"; - _update_corrections(get_tx_frequency(0), TX_DIRECTION, enable_corrections); - - _tx_lo_source = src; -} - -void rhodium_radio_ctrl_impl::set_rx_lo_source( - const std::string& src, - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_rx_lo_source(src=" << src << ", name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "set_tx_lo_source"); - - if (name == RHODIUM_LO2) { - if (src != "internal") { - throw uhd::value_error("The Lowband LO can only be set to internal"); - } - return; - } - - if (src == "internal") { - _rx_lo->set_output_enable(lmx2592_iface::output_t::RF_OUTPUT_A, true); - _cpld->set_rx_lo_source(rhodium_cpld_ctrl::rx_lo_input_sel_t::RX_LO_INPUT_SEL_INTERNAL); - } else if (src == "external") { - _rx_lo->set_output_enable(lmx2592_iface::output_t::RF_OUTPUT_A, false); - _cpld->set_rx_lo_source(rhodium_cpld_ctrl::rx_lo_input_sel_t::RX_LO_INPUT_SEL_EXTERNAL); - } else { - throw uhd::value_error(str(boost::format("set_rx_lo_source was called with an invalid LO source: %s Valid sources for LO1 are [internal, external]") % src)); - } - - const bool enable_corrections = not _is_rx_lowband(get_rx_frequency(0)) - and src == "internal"; - _update_corrections(get_rx_frequency(0), RX_DIRECTION, enable_corrections); - - _rx_lo_source = src; -} - -const std::string rhodium_radio_ctrl_impl::get_tx_lo_source( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_tx_lo_source(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_tx_lo_source"); - - return (name == RHODIUM_LO1 or name == ALL_LOS) ? _tx_lo_source : "internal"; -} - -const std::string rhodium_radio_ctrl_impl::get_rx_lo_source( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_rx_lo_source(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_rx_lo_source"); - - return (name == RHODIUM_LO1 or name == ALL_LOS) ? _rx_lo_source : "internal"; -} - -/****************************************************************************** - * Export Control - *****************************************************************************/ - -void rhodium_radio_ctrl_impl::_set_lo1_export_enabled( - const bool enabled, - const direction_t dir -) { - auto& _lo_ctrl = (dir == RX_DIRECTION) ? _rx_lo : _tx_lo; - _lo_ctrl->set_output_enable(lmx2592_iface::output_t::RF_OUTPUT_B, enabled); - if (_lo_dist_present) { - const auto direction = (dir == RX_DIRECTION) ? "RX" : "TX"; - _rpcc->notify_with_token(_rpc_prefix + "enable_lo_export", direction, enabled); - } -} - -void rhodium_radio_ctrl_impl::set_tx_lo_export_enabled( - const bool enabled, - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_tx_lo_export_enabled(enabled=" << enabled << ", name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "set_tx_lo_export_enabled"); - - if (name == RHODIUM_LO2) { - if (enabled) { - throw uhd::value_error("The lowband LO cannot be exported"); - } - return; - } - - _set_lo1_export_enabled(enabled, TX_DIRECTION); - _tx_lo_exported = enabled; -} - -void rhodium_radio_ctrl_impl::set_rx_lo_export_enabled( - const bool enabled, - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_rx_lo_export_enabled(enabled=" << enabled << ", name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "set_rx_lo_export_enabled"); - - if (name == RHODIUM_LO2) { - if (enabled) { - throw uhd::value_error("The lowband LO cannot be exported"); - } - return; - } - - _set_lo1_export_enabled(enabled, RX_DIRECTION); - _rx_lo_exported = enabled; -} - -bool rhodium_radio_ctrl_impl::get_tx_lo_export_enabled( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_tx_lo_export_enabled(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_tx_lo_export_enabled"); - - return (name == RHODIUM_LO1 or name == ALL_LOS) ? _tx_lo_exported : false; -} - -bool rhodium_radio_ctrl_impl::get_rx_lo_export_enabled( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_rx_lo_export_enabled(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_rx_lo_export_enabled"); - - return (name == RHODIUM_LO1 or name == ALL_LOS) ? _rx_lo_exported : false; -} - -/****************************************************************************** - * LO Distribution Control - *****************************************************************************/ - -void rhodium_radio_ctrl_impl::_validate_output_port(const std::string& port_name, const std::string& function_name) -{ - if (!_lo_dist_present) { - throw uhd::runtime_error(str(boost::format( - "%s can only be called if the LO distribution board was detected") % function_name)); - } - - if (!uhd::has(LO_OUTPUT_PORT_NAMES, port_name)) { - throw uhd::value_error(str(boost::format( - "%s was called with an invalid LO output port: %s Valid ports are [LO_OUT_0, LO_OUT_1, LO_OUT_2, LO_OUT_3]") - % function_name % port_name)); - } -} - -void rhodium_radio_ctrl_impl::_set_lo_output_enabled( - const bool enabled, - const std::string& port_name, - const direction_t dir -) { - auto direction = (dir == RX_DIRECTION) ? "RX" : "TX"; - auto name_iter = std::find(LO_OUTPUT_PORT_NAMES.begin(), LO_OUTPUT_PORT_NAMES.end(), port_name); - auto index = std::distance(LO_OUTPUT_PORT_NAMES.begin(), name_iter); - - _rpcc->notify_with_token(_rpc_prefix + "enable_lo_output", direction, index, enabled); - auto out_enabled = (dir == RX_DIRECTION) ? _lo_dist_rx_out_enabled : _lo_dist_tx_out_enabled; - out_enabled[index] = enabled; -} - -void rhodium_radio_ctrl_impl::set_tx_lo_output_enabled( - const bool enabled, - const std::string& port_name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_tx_lo_output_enabled(enabled=" << enabled << ", port_name=" << port_name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_output_port(port_name, "set_tx_lo_output_enabled"); - - _set_lo_output_enabled(enabled, port_name, TX_DIRECTION); -} - -void rhodium_radio_ctrl_impl::set_rx_lo_output_enabled( - const bool enabled, - const std::string& port_name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_rx_lo_output_enabled(enabled=" << enabled << ", port_name=" << port_name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_output_port(port_name, "set_rx_lo_output_enabled"); - - _set_lo_output_enabled(enabled, port_name, RX_DIRECTION); -} - -bool rhodium_radio_ctrl_impl::_get_lo_output_enabled( - const std::string& port_name, - const direction_t dir -) { - auto name_iter = std::find(LO_OUTPUT_PORT_NAMES.begin(), LO_OUTPUT_PORT_NAMES.end(), port_name); - auto index = std::distance(LO_OUTPUT_PORT_NAMES.begin(), name_iter); - - auto out_enabled = (dir == RX_DIRECTION) ? _lo_dist_rx_out_enabled : _lo_dist_tx_out_enabled; - return out_enabled[index]; -} - -bool rhodium_radio_ctrl_impl::get_tx_lo_output_enabled( - const std::string& port_name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_tx_lo_output_enabled(port_name=" << port_name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_output_port(port_name, "get_tx_lo_output_enabled"); - - return _get_lo_output_enabled(port_name, TX_DIRECTION); -} - -bool rhodium_radio_ctrl_impl::get_rx_lo_output_enabled( - const std::string& port_name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_rx_lo_output_enabled(port_name=" << port_name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_output_port(port_name, "get_rx_lo_output_enabled"); - - return _get_lo_output_enabled(port_name, RX_DIRECTION); -} - -/****************************************************************************** - * Gain Control - *****************************************************************************/ - -double rhodium_radio_ctrl_impl::set_tx_lo_gain( - double gain, - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_tx_lo_gain(gain=" << gain << ", name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "set_tx_lo_gain"); - - if (name == ALL_LOS) { - throw uhd::runtime_error("LO gain must be set for each stage individually"); - } - if (name == RHODIUM_LO2) { - UHD_LOG_WARNING(unique_id(), "The Lowband LO does not have configurable gain"); - return 0.0; - } - - auto index = _get_lo_gain_range().clip(gain); - - _cpld->set_lo_gain(index, TX_DIRECTION); - _lo_tx_gain = index; - return _lo_tx_gain; -} - -double rhodium_radio_ctrl_impl::set_rx_lo_gain( - double gain, - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_rx_lo_gain(gain=" << gain << ", name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "set_rx_lo_gain"); - - if (name == ALL_LOS) { - throw uhd::runtime_error("LO gain must be set for each stage individually"); - } - if (name == RHODIUM_LO2) { - UHD_LOG_WARNING(unique_id(), "The Lowband LO does not have configurable gain"); - return 0.0; - } - - auto index = _get_lo_gain_range().clip(gain); - - _cpld->set_lo_gain(index, RX_DIRECTION); - _lo_rx_gain = index; - return _lo_rx_gain; -} - -double rhodium_radio_ctrl_impl::get_tx_lo_gain( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_tx_lo_gain(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_tx_lo_gain"); - - if (name == ALL_LOS) { - throw uhd::runtime_error("LO gain must be retrieved for each stage individually"); - } - - return (name == RHODIUM_LO1) ? _lo_rx_gain : 0.0; -} - -double rhodium_radio_ctrl_impl::get_rx_lo_gain( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_rx_lo_gain(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_rx_lo_gain"); - - if (name == ALL_LOS) { - throw uhd::runtime_error("LO gain must be retrieved for each stage individually"); - } - - return (name == RHODIUM_LO1) ? _lo_tx_gain : 0.0; -} - -/****************************************************************************** - * Output Power Control - *****************************************************************************/ - -double rhodium_radio_ctrl_impl::_set_lo1_power( - const double power, - const direction_t dir -) { - auto& _lo_ctrl = (dir == RX_DIRECTION) ? _rx_lo : _tx_lo; - auto coerced_power = static_cast<uint32_t>(_get_lo_power_range().clip(power, true)); - - _lo_ctrl->set_output_power(lmx2592_iface::RF_OUTPUT_A, coerced_power); - return coerced_power; -} - -double rhodium_radio_ctrl_impl::set_tx_lo_power( - const double power, - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_tx_lo_power(power=" << power << ", name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "set_tx_lo_power"); - - if (name == ALL_LOS) { - throw uhd::runtime_error( - "LO output power must be set for each stage individually"); - } - if (name == RHODIUM_LO2) { - UHD_LOG_WARNING(unique_id(), "The Lowband LO does not have configurable output power"); - return 0.0; - } - - _lo_tx_power = _set_lo1_power(power, TX_DIRECTION); - return _lo_tx_power; -} - -double rhodium_radio_ctrl_impl::set_rx_lo_power( - const double power, - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_rx_lo_power(power=" << power << ", name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "set_rx_lo_power"); - - if (name == ALL_LOS) { - throw uhd::runtime_error( - "LO output power must be set for each stage individually"); - } - if (name == RHODIUM_LO2) { - UHD_LOG_WARNING(unique_id(), "The Lowband LO does not have configurable output power"); - return 0.0; - } - - _lo_rx_power = _set_lo1_power(power, RX_DIRECTION); - return _lo_rx_power; -} - -double rhodium_radio_ctrl_impl::get_tx_lo_power( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_tx_lo_power(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_tx_lo_power"); - - if (name == ALL_LOS) { - throw uhd::runtime_error( - "LO output power must be retrieved for each stage individually"); - } - - return (name == RHODIUM_LO1) ? _lo_tx_power : 0.0; -} - -double rhodium_radio_ctrl_impl::get_rx_lo_power( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_rx_lo_power(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_rx_lo_power"); - - if (name == ALL_LOS) { - throw uhd::runtime_error( - "LO output power must be retrieved for each stage individually"); - } - - return (name == RHODIUM_LO1) ? _lo_rx_power : 0.0; -} - -/****************************************************************************** - * Helper Functions - *****************************************************************************/ - -double rhodium_radio_ctrl_impl::_get_lowband_lo_freq() const -{ - return MCR_TO_LOWBAND_IF.at(_master_clock_rate); -} - -uhd::gain_range_t rhodium_radio_ctrl_impl::_get_lo_gain_range() -{ - return gain_range_t(LO_MIN_GAIN, LO_MAX_GAIN, LO_GAIN_STEP); -} - -uhd::gain_range_t rhodium_radio_ctrl_impl::_get_lo_power_range() -{ - return gain_range_t(LO_MIN_POWER, LO_MAX_POWER, LO_POWER_STEP); -} - -int rhodium_radio_ctrl_impl::_get_lo_dsa_setting(const double freq, const direction_t dir) { - int index = 0; - while (freq > FREQ_THRESHOLDS[index+1]) { - index++; - } - - const double freq_low = FREQ_THRESHOLDS[index]; - const double freq_high = FREQ_THRESHOLDS[index+1]; - - const auto& gain_table = (dir == RX_DIRECTION) ? DSA_RX_GAIN_VALUES : DSA_TX_GAIN_VALUES; - const double gain_low = gain_table[index]; - const double gain_high = gain_table[index+1]; - - - const double slope = (gain_high - gain_low) / (freq_high - freq_low); - const double gain_at_freq = gain_low + (freq - freq_low) * slope; - - UHD_LOG_TRACE(unique_id(), "Interpolated DSA Gain is " << gain_at_freq); - return static_cast<int>(std::round(gain_at_freq)); -} - -unsigned int rhodium_radio_ctrl_impl::_get_lo_power_setting(double freq) { - int index = 0; - while (freq > FREQ_THRESHOLDS[index+1]) { - index++; - } - - const double freq_low = FREQ_THRESHOLDS[index]; - const double freq_high = FREQ_THRESHOLDS[index+1]; - const double power_low = LMX_GAIN_VALUES[index]; - const double power_high = LMX_GAIN_VALUES[index+1]; - - - const double slope = (power_high - power_low) / (freq_high - freq_low); - const double power_at_freq = power_low + (freq - freq_low) * slope; - - UHD_LOG_TRACE(unique_id(), "Interpolated LMX Power is " << power_at_freq); - return uhd::narrow_cast<unsigned int>(std::lround(power_at_freq)); -} diff --git a/host/lib/usrp/mpmd/CMakeLists.txt b/host/lib/usrp/mpmd/CMakeLists.txt index 21511e6da..18c9c6cbd 100644 --- a/host/lib/usrp/mpmd/CMakeLists.txt +++ b/host/lib/usrp/mpmd/CMakeLists.txt @@ -21,21 +21,21 @@ if(ENABLE_MPMD) ${CMAKE_CURRENT_SOURCE_DIR}/mpmd_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/mpmd_mboard_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/mpmd_mb_controller.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mpmd_mb_iface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/mpmd_prop_tree.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/mpmd_xport.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/mpmd_xport_mgr.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/mpmd_xport_ctrl_udp.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mpmd_link_if_mgr.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mpmd_link_if_ctrl_udp.cpp ) if(ENABLE_LIBERIO) LIBUHD_APPEND_SOURCES( - ${CMAKE_CURRENT_SOURCE_DIR}/mpmd_xport_ctrl_liberio.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mpmd_link_if_ctrl_liberio.cpp ) endif(ENABLE_LIBERIO) if(ENABLE_DPDK) LIBUHD_APPEND_SOURCES( - ${CMAKE_CURRENT_SOURCE_DIR}/mpmd_xport_ctrl_dpdk_udp.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mpmd_link_if_ctrl_dpdk_udp.cpp ) endif(ENABLE_DPDK) diff --git a/host/lib/usrp/mpmd/mpmd_find.cpp b/host/lib/usrp/mpmd/mpmd_find.cpp index 2b8e1350d..3e8bcc72f 100644 --- a/host/lib/usrp/mpmd/mpmd_find.cpp +++ b/host/lib/usrp/mpmd/mpmd_find.cpp @@ -8,6 +8,7 @@ #include "mpmd_devices.hpp" #include "mpmd_impl.hpp" +#include "mpmd_link_if_mgr.hpp" #include <uhdlib/transport/dpdk_common.hpp> #include <uhd/transport/if_addrs.hpp> #include <uhd/transport/udp_simple.hpp> @@ -89,7 +90,7 @@ device_addrs_t mpmd_find_with_addr( // Create result to return device_addr_t new_addr; - new_addr[xport::MGMT_ADDR_KEY] = recv_addr; + new_addr[MGMT_ADDR_KEY] = recv_addr; new_addr["type"] = "mpmd"; // hwd will overwrite this // remove ident string and put other informations into device_args dict result.erase(result.begin()); @@ -101,7 +102,7 @@ device_addrs_t mpmd_find_with_addr( el, [](const char& in) { return in == '='; }, boost::token_compress_on); - if (value[0] != xport::MGMT_ADDR_KEY) { + if (value[0] != MGMT_ADDR_KEY) { new_addr[value[0]] = value[1]; } } @@ -132,12 +133,12 @@ device_addrs_t mpmd_find_with_addrs(const device_addrs_t& hints) found_devices.reserve(hints.size()); for (const auto& hint : hints) { if (not(hint.has_key(xport::FIRST_ADDR_KEY) - or hint.has_key(xport::MGMT_ADDR_KEY))) { + or hint.has_key(MGMT_ADDR_KEY))) { UHD_LOG_DEBUG("MPMD FIND", "No address given in hint " << hint.to_string()); continue; } const std::string mgmt_addr = - hint.get(xport::MGMT_ADDR_KEY, hint.get(xport::FIRST_ADDR_KEY, "")); + hint.get(MGMT_ADDR_KEY, hint.get(xport::FIRST_ADDR_KEY, "")); device_addrs_t reply_addrs = mpmd_find_with_addr(mgmt_addr, hint); if (reply_addrs.size() > 1) { UHD_LOG_ERROR("MPMD", @@ -217,7 +218,7 @@ device_addrs_t mpmd_find(const device_addr_t& hint_) // Scenario 1): User gave us at least one address if (not hints.empty() and (hints[0].has_key(xport::FIRST_ADDR_KEY) - or hints[0].has_key(xport::MGMT_ADDR_KEY))) { + or hints[0].has_key(MGMT_ADDR_KEY))) { // Note: We don't try and connect to the devices in this mode, because // we only get here if the user specified addresses, and we assume she // knows what she's doing. diff --git a/host/lib/usrp/mpmd/mpmd_image_loader.cpp b/host/lib/usrp/mpmd/mpmd_image_loader.cpp index d5c7d3da9..3a285da3e 100644 --- a/host/lib/usrp/mpmd/mpmd_image_loader.cpp +++ b/host/lib/usrp/mpmd/mpmd_image_loader.cpp @@ -13,6 +13,7 @@ #include <uhd/types/component_file.hpp> #include <uhd/types/eeprom.hpp> #include <uhd/utils/paths.hpp> +#include <uhd/utils/static.hpp> #include <boost/algorithm/string.hpp> #include <boost/filesystem/convenience.hpp> #include <fstream> @@ -216,6 +217,7 @@ static bool mpmd_image_loader(const image_loader::image_loader_args_t& image_loa return true; } + }} // namespace uhd:: UHD_STATIC_BLOCK(register_mpm_image_loader) diff --git a/host/lib/usrp/mpmd/mpmd_impl.cpp b/host/lib/usrp/mpmd/mpmd_impl.cpp index 05d847060..30a3c5804 100644 --- a/host/lib/usrp/mpmd/mpmd_impl.cpp +++ b/host/lib/usrp/mpmd/mpmd_impl.cpp @@ -14,11 +14,11 @@ #include <uhd/utils/tasks.hpp> #include <uhdlib/rfnoc/radio_ctrl_impl.hpp> #include <uhdlib/rfnoc/rpc_block_ctrl.hpp> -#include <../device3/device3_impl.hpp> #include <boost/algorithm/string.hpp> #include <boost/asio.hpp> #include <boost/make_shared.hpp> #include <boost/thread.hpp> +#include <chrono> #include <future> #include <memory> #include <mutex> @@ -34,11 +34,10 @@ namespace { /************************************************************************* * Local constants ************************************************************************/ -const size_t MPMD_CROSSBAR_MAX_LADDR = 255; //! Most pessimistic time for a CHDR query to go to device and back const double MPMD_CHDR_MAX_RTT = 0.02; -//! MPM Compatibility number -const std::vector<size_t> MPM_COMPAT_NUM = {1, 2}; +//! MPM Compatibility number {MAJOR, MINOR} +const std::vector<size_t> MPM_COMPAT_NUM = {2, 0}; /************************************************************************* * Helper functions @@ -155,7 +154,7 @@ const std::string mpmd_impl::MPM_ECHO_CMD = "MPM-ECHO"; * Structors ****************************************************************************/ mpmd_impl::mpmd_impl(const device_addr_t& device_args) - : usrp::device3_impl(), _device_args(device_args) + : rfnoc_device(), _device_args(device_args) { const device_addrs_t mb_args = separate_device_addr(device_args); const size_t num_mboards = mb_args.size(); @@ -175,20 +174,12 @@ mpmd_impl::mpmd_impl(const device_addr_t& device_args) _mb.push_back(claim_and_make(mb_args[mb_i])); } - // Next figure out the number of base xport addresses. This way, we - // can run _mb[*]->init() in parallel on all the _mb. - // This can *not* be parallelized. - std::vector<size_t> base_xport_addr(num_mboards, 2); // Starts at 2 [sic] - for (size_t mb_i = 0; mb_i < num_mboards - 1; ++mb_i) { - base_xport_addr[mb_i + 1] = base_xport_addr[mb_i] + _mb[mb_i]->num_xbars; - } - if (not skip_init) { // Run the actual device initialization. This can run in parallel. for (size_t mb_i = 0; mb_i < num_mboards; ++mb_i) { // Note: This is the only place we do compat number checks. They're // effectively disabled for skip_init=1 - setup_mb(_mb[mb_i].get(), mb_i, base_xport_addr[mb_i]); + setup_mb(_mb[mb_i].get(), mb_i); } } else { UHD_LOG_DEBUG("MPMD", "Claimed device, but skipped init."); @@ -205,12 +196,6 @@ mpmd_impl::mpmd_impl(const device_addr_t& device_args) } if (not skip_init) { - // This can be parallelized, because the blocks of individual mboards - // live on different subtrees. - for (size_t mb_i = 0; mb_i < mb_args.size(); ++mb_i) { - setup_rfnoc_blocks(_mb[mb_i].get(), mb_i, mb_args[mb_i]); - } - // FIXME this section only makes sense for when the time source is external. // So, check for that, or something similar. // This section of code assumes that the prop tree is set and we have access @@ -218,12 +203,6 @@ mpmd_impl::mpmd_impl(const device_addr_t& device_args) if (device_args.has_key("sync_time")) { reset_time_synchronized(_tree); } - - auto filtered_block_args = device_args; // TODO actually filter - // Blocks will finalize their own setup in this function. They have - // (and might need) full access to the prop tree, the timekeepers, etc. - // This is already internally parallelized. - setup_rpc_blocks(filtered_block_args, serialize_init); } else { UHD_LOG_INFO("MPMD", "Claimed device without full initialization."); } @@ -231,7 +210,6 @@ mpmd_impl::mpmd_impl(const device_addr_t& device_args) mpmd_impl::~mpmd_impl() { - _rfnoc_block_ctrl.clear(); _tree.reset(); _mb.clear(); } @@ -241,7 +219,7 @@ mpmd_impl::~mpmd_impl() ****************************************************************************/ mpmd_mboard_impl::uptr mpmd_impl::claim_and_make(const uhd::device_addr_t& device_args) { - const std::string rpc_addr = device_args.get(xport::MGMT_ADDR_KEY); + const std::string rpc_addr = device_args.get(MGMT_ADDR_KEY); UHD_LOGGER_DEBUG("MPMD") << "Device args: `" << device_args.to_string() << "'. RPC address: " << rpc_addr; @@ -254,8 +232,7 @@ mpmd_mboard_impl::uptr mpmd_impl::claim_and_make(const uhd::device_addr_t& devic return mpmd_mboard_impl::make(device_args, rpc_addr); } -void mpmd_impl::setup_mb( - mpmd_mboard_impl* mb, const size_t mb_index, const size_t base_xport_addr) +void mpmd_impl::setup_mb(mpmd_mboard_impl* mb, const size_t mb_index) { assert_compat_number_throw("MPM", MPM_COMPAT_NUM, @@ -264,94 +241,8 @@ void mpmd_impl::setup_mb( UHD_LOG_DEBUG("MPMD", "Initializing mboard " << mb_index); mb->init(); - for (size_t xbar_index = 0; xbar_index < mb->num_xbars; xbar_index++) { - mb->set_xbar_local_addr(xbar_index, base_xport_addr + xbar_index); - } -} - -void mpmd_impl::setup_rfnoc_blocks(mpmd_mboard_impl* mb, - const size_t mb_index, - const uhd::device_addr_t& ctrl_xport_args) -{ - UHD_LOG_TRACE( - "MPMD", "Mboard " << mb_index << " reports " << mb->num_xbars << " crossbar(s)."); - // TODO: The args apply to all xbars, which may or may not be true - for (size_t xbar_index = 0; xbar_index < mb->num_xbars; xbar_index++) { - // Pull the number of blocks and base port from the args, if available. - // Otherwise, get the values from MPM. - const size_t num_blocks = - ctrl_xport_args.has_key("rfnoc_num_blocks") - ? ctrl_xport_args.cast<size_t>("rfnoc_num_blocks", 0) - : mb->rpc->request<size_t>("get_num_blocks", xbar_index); - const size_t base_port = - ctrl_xport_args.has_key("rfnoc_base_port") - ? ctrl_xport_args.cast<size_t>("rfnoc_base_port", 0) - : mb->rpc->request<size_t>("get_base_port", xbar_index); - const size_t local_addr = mb->get_xbar_local_addr(xbar_index); - UHD_LOGGER_TRACE("MPMD") - << "Enumerating RFNoC blocks for xbar " << xbar_index - << ". Total blocks: " << num_blocks << " Base port: " << base_port - << " Local address: " << local_addr; - if (ctrl_xport_args.has_key("rfnoc_num_blocks") - or ctrl_xport_args.has_key("rfnoc_base_port")) { - // TODO: Remove this warning once we're confident this is - // (relatively) safe and useful. Also add documentation to - // usrp_n3xx.dox - UHD_LOGGER_WARNING("MPMD") - << "Overriding default RFNoC configuration. You are using an " - << "experimental development feature, which may go away in " - << "future versions."; - } - - try { - enumerate_rfnoc_blocks(mb_index, - num_blocks, - base_port, - uhd::sid_t(0, 0, local_addr, 0), - ctrl_xport_args); - } catch (const std::exception& ex) { - UHD_LOGGER_ERROR("MPMD") << "Failure during block enumeration: " << ex.what(); - throw uhd::runtime_error("Failed to run enumerate_rfnoc_blocks()"); - } - } -} - -void mpmd_impl::setup_rpc_blocks( - const device_addr_t& block_args, const bool serialize_init) -{ - std::vector<std::future<void>> task_list; - // If we don't force async, most compilers, at least now, will default to - // deferred. - const auto launch_policy = serialize_init ? std::launch::deferred - : std::launch::async; - - // Preload all the tasks (they might start running on emplace_back) - for (const auto& block_ctrl : _rfnoc_block_ctrl) { - auto rpc_block_id = block_ctrl->get_block_id(); - if (has_block<uhd::rfnoc::rpc_block_ctrl>(rpc_block_id)) { - const size_t mboard_idx = rpc_block_id.get_device_no(); - auto rpc_block_ctrl = - get_block_ctrl<uhd::rfnoc::rpc_block_ctrl>(rpc_block_id); - auto rpc_sptr = _mb[mboard_idx]->rpc; - task_list.emplace_back(std::async( - launch_policy, [rpc_block_id, rpc_block_ctrl, &block_args, rpc_sptr]() { - UHD_LOGGER_DEBUG("MPMD") - << "Adding RPC access to block: " << rpc_block_id - << " Block args: " << block_args.to_string(); - rpc_block_ctrl->set_rpc_client(rpc_sptr, block_args); - })); - } - } - - // Execute all the calls to set_rpc_client(), either concurrently, or - // serially - for (auto& task : task_list) { - task.get(); - } -} - -size_t mpmd_impl::get_mtu(const size_t mb_index, const uhd::direction_t dir) { - return _mb[mb_index]->get_mtu(dir); + UHD_ASSERT_THROW(mb->mb_ctrl); + register_mb_controller(mb_index, mb->mb_ctrl); } /***************************************************************************** diff --git a/host/lib/usrp/mpmd/mpmd_impl.hpp b/host/lib/usrp/mpmd/mpmd_impl.hpp index bdb6bd691..e1dde49b5 100644 --- a/host/lib/usrp/mpmd/mpmd_impl.hpp +++ b/host/lib/usrp/mpmd/mpmd_impl.hpp @@ -7,14 +7,14 @@ #ifndef INCLUDED_MPMD_IMPL_HPP #define INCLUDED_MPMD_IMPL_HPP -#include "../device3/device3_impl.hpp" -#include "mpmd_xport_mgr.hpp" #include <uhd/property_tree.hpp> #include <uhd/stream.hpp> -#include <uhd/transport/muxed_zero_copy_if.hpp> #include <uhd/types/device_addr.hpp> #include <uhd/types/dict.hpp> #include <uhd/utils/tasks.hpp> +#include <uhdlib/rfnoc/rfnoc_device.hpp> +#include <uhdlib/rfnoc/clock_iface.hpp> +#include <uhdlib/usrp/common/mpmd_mb_controller.hpp> #include <uhdlib/utils/rpc.hpp> #include <boost/optional.hpp> #include <map> @@ -34,6 +34,8 @@ static constexpr size_t MPMD_DEFAULT_RPC_TIMEOUT = 2000; static constexpr size_t MPMD_SHORT_RPC_TIMEOUT = 2000; //! Claimer loop timeout value for RPC calls (ms). static constexpr size_t MPMD_CLAIMER_RPC_TIMEOUT = 10000; +//! Ethernet address for management and RPC communication +static const std::string MGMT_ADDR_KEY = "mgmt_addr"; namespace uhd { namespace mpmd { @@ -46,6 +48,11 @@ public: using uptr = std::unique_ptr<mpmd_mboard_impl>; using dev_info = std::map<std::string, std::string>; + //! MPMD-specific implementation of the mb_iface + // + // This handles the transport management + class mpmd_mb_iface; + /*** Static helper *******************************************************/ /*! Will run some checks to determine if this device can be reached from * the current UHD session @@ -73,9 +80,11 @@ public: */ static uptr make(const uhd::device_addr_t& mb_args, const std::string& addr); - /*** Init ****************************************************************/ + /*** API *****************************************************************/ void init(); + uhd::rfnoc::mb_iface& get_mb_iface(); + /*** Public attributes ***************************************************/ //! These are the args given by the user, with some filtering/preprocessing uhd::device_addr_t mb_args; @@ -88,6 +97,12 @@ public: // to be populated at all. std::vector<uhd::device_addr_t> dboard_info; + //! Reference to this motherboards mb_iface + std::unique_ptr<mpmd_mb_iface> mb_iface; + + //! Reference to this motherboards mb_controller + uhd::rfnoc::mpmd_mb_controller::sptr mb_ctrl; + /*! Reference to the RPC client for this motherboard * * We store a shared ptr, because we might share it with some of the RFNoC @@ -95,41 +110,9 @@ public: */ uhd::rpc_client::sptr rpc; - //! Number of RFNoC crossbars on this device - const size_t num_xbars; - /************************************************************************* * API ************************************************************************/ - //! Configure a crossbar to have a certain local address - void set_xbar_local_addr(const size_t xbar_index, const size_t local_addr); - - //! Return the local address of a given crossbar - size_t get_xbar_local_addr(const size_t xbar_index) const - { - return xbar_local_addrs.at(xbar_index); - } - - //! Device-specific make_transport implementation - // - // A major difference to the mpmd_impl::make_transport() is the meaning of - // the first argument (\p sid). mpmd_impl::make_transport() will add a - // source part to the SID which needs to be taken into account in this - // function. - // - // \param sid The full SID of this transport (UHD to device) - // \param xport_type Transport type (CTRL, RX_DATA, ...) - // \param args Any kind of args passed in via get_?x_stream() - uhd::both_xports_t make_transport(const sid_t& sid, - usrp::device3_impl::xport_type_t xport_type, - const uhd::device_addr_t& args); - - size_t get_mtu(const uhd::direction_t dir) const; - - - uhd::device_addr_t get_rx_hints() const; - uhd::device_addr_t get_tx_hints() const; - /*! Setting this flag will enable a mode where a reclaim failure is * acceptable. * @@ -177,18 +160,10 @@ private: /************************************************************************* * Private attributes ************************************************************************/ - //! Stores a list of local addresses of the crossbars. The local address is - // what we use when addressing a crossbar in a CHDR header. - std::vector<size_t> xbar_local_addrs; - /*! Continuously reclaims the device. */ uhd::task::sptr _claimer_task; - uhd::mpmd::xport::mpmd_xport_mgr::uptr _xport_mgr; - uhd::device_addr_t send_args; - uhd::device_addr_t recv_args; - /*! This flag is only used within the claim() function. Go look there if you * really need to know what it does. */ @@ -208,7 +183,7 @@ private: * are taken care of by MPM itself, it is not necessary to write a specific * derived class for every single type of MPM device. */ -class mpmd_impl : public uhd::usrp::device3_impl +class mpmd_impl : public uhd::rfnoc::detail::rfnoc_device { public: //! Device arg key which will allow finding all devices, even those not @@ -242,17 +217,17 @@ public: /************************************************************************** * API ************************************************************************/ - uhd::both_xports_t make_transport(const uhd::sid_t&, - uhd::usrp::device3_impl::xport_type_t, - const uhd::device_addr_t&); - - //! get mtu - size_t get_mtu(const size_t, const uhd::direction_t); + uhd::rfnoc::mb_iface& get_mb_iface(const size_t mb_idx) + { + if (mb_idx >= _mb.size()) { + throw uhd::index_error( + std::string("Cannot get mb_iface, invalid motherboard index: ") + + std::to_string(mb_idx)); + } + return _mb.at(mb_idx)->get_mb_iface(); + } private: - uhd::device_addr_t get_rx_hints(size_t mb_index); - uhd::device_addr_t get_tx_hints(size_t mb_index); - /************************************************************************* * Private methods/helpers ************************************************************************/ @@ -272,21 +247,7 @@ private: * \param device_args Device args * */ - void setup_mb( - mpmd_mboard_impl* mb, const size_t mb_index, const size_t base_xport_addr); - - //! Setup all RFNoC blocks running on mboard \p mb_i - void setup_rfnoc_blocks( - mpmd_mboard_impl* mb, const size_t mb_i, const uhd::device_addr_t& block_args); - - //! Configure all blocks that require access to an RPC client - void setup_rpc_blocks( - const uhd::device_addr_t& block_args, const bool serialize_init); - - /*! Return the index of the motherboard given the local address of a - * crossbar - */ - size_t identify_mboard_by_xbar_addr(const size_t xbar_addr) const; + void setup_mb(mpmd_mboard_impl* mb, const size_t mb_index); /*! Initialize property tree for a single device. * @@ -297,7 +258,6 @@ private: static void init_property_tree( uhd::property_tree::sptr tree, fs_path mb_path, mpmd_mboard_impl* mb); - /************************************************************************* * Private attributes ************************************************************************/ diff --git a/host/lib/usrp/mpmd/mpmd_link_if_ctrl_base.hpp b/host/lib/usrp/mpmd/mpmd_link_if_ctrl_base.hpp new file mode 100644 index 000000000..a9cb456e5 --- /dev/null +++ b/host/lib/usrp/mpmd/mpmd_link_if_ctrl_base.hpp @@ -0,0 +1,56 @@ +// +// Copyright 2017 Ettus Research, a National Instruments Company +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_MPMD_XPORT_CTRL_BASE_HPP +#define INCLUDED_MPMD_XPORT_CTRL_BASE_HPP + +#include "mpmd_link_if_mgr.hpp" +#include <uhd/types/device_addr.hpp> +#include <uhdlib/rfnoc/chdr_packet.hpp> +#include <uhdlib/transport/links.hpp> +#include <memory> + +namespace uhd { namespace mpmd { namespace xport { + +/*! Transport manager implementation base + */ +class mpmd_link_if_ctrl_base +{ +public: + using uptr = std::unique_ptr<mpmd_link_if_ctrl_base>; + virtual ~mpmd_link_if_ctrl_base() {} + + virtual size_t get_num_links() const = 0; + + /*! Return links object + * + * \param link_idx The number of the link to use. link_idx < get_num_links() + * must hold true. link_idx is often 0. Example: When + * the underlying transport is Ethernet, and the user + * specified both addr and second_addr, then get_num_links() + * equals 2 and link_idx can also be 1. + * \param link_type CTRL, RX_DATA, or TX_DATA (for configuring the link) + * \param link_args Link-specific additional information that the underlying + * mpmd_link_if_ctrl instantiation can use + */ + virtual uhd::transport::both_links_t get_link(const size_t link_idx, + const uhd::transport::link_type_t link_type, + const uhd::device_addr_t& link_args) = 0; + + //! Return the underlying link's MTU in bytes + virtual size_t get_mtu(const uhd::direction_t dir) const = 0; + + //! Return the rate of the underlying link in bytes/sec + virtual double get_link_rate(const size_t link_idx) const = 0; + + //! Get the packet factory associated with this link + virtual const uhd::rfnoc::chdr::chdr_packet_factory& get_packet_factory() const = 0; +}; + +}}} /* namespace uhd::mpmd::xport */ + +#endif /* INCLUDED_MPMD_XPORT_CTRL_BASE_HPP */ diff --git a/host/lib/usrp/mpmd/mpmd_xport_ctrl_dpdk_udp.cpp b/host/lib/usrp/mpmd/mpmd_link_if_ctrl_dpdk_udp.cpp index 38d295728..baf0dde3e 100644 --- a/host/lib/usrp/mpmd/mpmd_xport_ctrl_dpdk_udp.cpp +++ b/host/lib/usrp/mpmd/mpmd_link_if_ctrl_dpdk_udp.cpp @@ -5,8 +5,8 @@ // #include "mpmd_impl.hpp" -#include "mpmd_xport_mgr.hpp" -#include "mpmd_xport_ctrl_dpdk_udp.hpp" +#include "mpmd_link_if_mgr.hpp" +#include "mpmd_link_if_ctrl_dpdk_udp.hpp" #include <uhd/transport/udp_simple.hpp> #include <uhd/transport/udp_constants.hpp> #include <uhdlib/transport/dpdk_zero_copy.hpp> @@ -161,7 +161,7 @@ size_t discover_mtu( } -mpmd_xport_ctrl_dpdk_udp::mpmd_xport_ctrl_dpdk_udp( +mpmd_link_if_ctrl_dpdk_udp::mpmd_link_if_ctrl_dpdk_udp( const uhd::device_addr_t& mb_args ) : _mb_args(mb_args) , _ctx(uhd::transport::uhd_dpdk_ctx::get()) @@ -193,8 +193,8 @@ mpmd_xport_ctrl_dpdk_udp::mpmd_xport_ctrl_dpdk_udp( } uhd::both_xports_t -mpmd_xport_ctrl_dpdk_udp::make_transport( - mpmd_xport_mgr::xport_info_t &xport_info, +mpmd_link_if_ctrl_dpdk_udp::make_transport( + mpmd_link_if_mgr::xport_info_t &xport_info, const usrp::device3_impl::xport_type_t xport_type, const uhd::device_addr_t& xport_args ) { @@ -264,14 +264,14 @@ mpmd_xport_ctrl_dpdk_udp::make_transport( return xports; } -bool mpmd_xport_ctrl_dpdk_udp::is_valid( - const mpmd_xport_mgr::xport_info_t& xport_info +bool mpmd_link_if_ctrl_dpdk_udp::is_valid( + const mpmd_link_if_mgr::xport_info_t& xport_info ) const { int dpdk_port_id = _ctx.get_route(xport_info.at("ipv4")); return (dpdk_port_id >= 0); } -size_t mpmd_xport_ctrl_dpdk_udp::get_mtu(const uhd::direction_t /*dir*/) const +size_t mpmd_link_if_ctrl_dpdk_udp::get_mtu(const uhd::direction_t /*dir*/) const { return _mtu; } diff --git a/host/lib/usrp/mpmd/mpmd_xport_ctrl_dpdk_udp.hpp b/host/lib/usrp/mpmd/mpmd_link_if_ctrl_dpdk_udp.hpp index 943cd44d6..4423b4340 100644 --- a/host/lib/usrp/mpmd/mpmd_xport_ctrl_dpdk_udp.hpp +++ b/host/lib/usrp/mpmd/mpmd_link_if_ctrl_dpdk_udp.hpp @@ -7,7 +7,7 @@ #ifndef INCLUDED_MPMD_XPORT_CTRL_DPDK_UDP_HPP #define INCLUDED_MPMD_XPORT_CTRL_DPDK_UDP_HPP -#include "mpmd_xport_ctrl_base.hpp" +#include "mpmd_link_if_ctrl_base.hpp" #include <uhd/types/device_addr.hpp> #include <uhdlib/transport/dpdk_zero_copy.hpp> #include "../device3/device3_impl.hpp" @@ -18,21 +18,21 @@ namespace uhd { namespace mpmd { namespace xport { * * Opens UDP sockets */ -class mpmd_xport_ctrl_dpdk_udp : public mpmd_xport_ctrl_base +class mpmd_link_if_ctrl_dpdk_udp : public mpmd_link_if_ctrl_base { public: - mpmd_xport_ctrl_dpdk_udp( + mpmd_link_if_ctrl_dpdk_udp( const uhd::device_addr_t& mb_args ); both_xports_t make_transport( - mpmd_xport_mgr::xport_info_t& xport_info, + mpmd_link_if_mgr::xport_info_t& xport_info, const usrp::device3_impl::xport_type_t xport_type, const uhd::device_addr_t& xport_args ); bool is_valid( - const mpmd_xport_mgr::xport_info_t& xport_info + const mpmd_link_if_mgr::xport_info_t& xport_info ) const; size_t get_mtu( diff --git a/host/lib/usrp/mpmd/mpmd_link_if_ctrl_liberio.cpp b/host/lib/usrp/mpmd/mpmd_link_if_ctrl_liberio.cpp new file mode 100644 index 000000000..ed8f4395e --- /dev/null +++ b/host/lib/usrp/mpmd/mpmd_link_if_ctrl_liberio.cpp @@ -0,0 +1,135 @@ +// +// Copyright 2017 Ettus Research, National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "mpmd_link_if_ctrl_liberio.hpp" +#include <uhd/rfnoc/constants.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/log.hpp> +#include <uhdlib/transport/inline_io_service.hpp> +#include <uhdlib/transport/liberio_link.hpp> + +using namespace uhd; +using namespace uhd::transport; +using namespace uhd::mpmd::xport; + +const uhd::rfnoc::chdr::chdr_packet_factory mpmd_link_if_ctrl_liberio::_pkt_factory( + uhd::rfnoc::CHDR_W_64, ENDIANNESS_LITTLE); + +namespace { + +//! The default MTU will be this number times the page size +const size_t LIBERIO_PAGES_PER_BUF = 2; +//! The default MTU +const size_t LIBERIO_DEFAULT_MTU = LIBERIO_PAGES_PER_BUF * getpagesize(); +//! The default link_rate (8 Bytes * 200 MHz) +const double LIBERIO_DEFAULT_LINK_RATE = 200e6 * 8; +//! Number of descriptors that liberio allocates (receive) +const size_t LIBERIO_NUM_RECV_FRAMES = 128; +//! Number of descriptors that liberio allocates (send) +const size_t LIBERIO_NUM_SEND_FRAMES = 128; +//! MTU for largest non-data packet accepted (arbitrarily-determined...) +// Note: Management frames must fit here, and it determines the padded RX size +const size_t LIBERIO_MAX_NONDATA_PACKET_SIZE = 128; + +std::vector<mpmd_link_if_ctrl_liberio::liberio_link_info_t> +get_liberio_info_from_xport_info( + const mpmd_link_if_mgr::xport_info_list_t& link_info_list) +{ + std::vector<mpmd_link_if_ctrl_liberio::liberio_link_info_t> result; + for (const auto& link_info : link_info_list) { + if (!link_info.count("tx_dev")) { + UHD_LOG_ERROR("MPMD::XPORT::LIBERIO", + "Invalid response from get_chdr_link_options()! No `tx_dev' key!"); + throw uhd::runtime_error( + "Invalid response from get_chdr_link_options()! No `tx_dev' key!"); + } + if (!link_info.count("rx_dev")) { + UHD_LOG_ERROR("MPMD::XPORT::LIBERIO", + "Invalid response from get_chdr_link_options()! No `rx_dev' key!"); + throw uhd::runtime_error( + "Invalid response from get_chdr_link_options()! No `rx_dev' key!"); + } + const std::string tx_dev = link_info.at("tx_dev"); + const std::string rx_dev = link_info.at("rx_dev"); + result.emplace_back( + mpmd_link_if_ctrl_liberio::liberio_link_info_t{tx_dev, rx_dev}); + } + + return result; +} + +} // namespace + + +/****************************************************************************** + * Structors + *****************************************************************************/ +mpmd_link_if_ctrl_liberio::mpmd_link_if_ctrl_liberio(const uhd::device_addr_t& mb_args, + const mpmd_link_if_mgr::xport_info_list_t& xport_info) + : _mb_args(mb_args) + , _recv_args(filter_args(mb_args, "recv")) + , _send_args(filter_args(mb_args, "send")) + , _dma_channels(get_liberio_info_from_xport_info(xport_info)) + , _link_rate(LIBERIO_DEFAULT_LINK_RATE) // FIXME +{ + // nop +} + +/****************************************************************************** + * API + *****************************************************************************/ +uhd::transport::both_links_t mpmd_link_if_ctrl_liberio::get_link(const size_t link_idx, + const uhd::transport::link_type_t link_type, + const uhd::device_addr_t& link_args) +{ + UHD_ASSERT_THROW(link_idx == 0); + if (_next_channel >= _dma_channels.size()) { + UHD_LOG_ERROR( + "MPMD::XPORT::LIBERIO", "Cannot create liberio link: DMA channels exhausted"); + throw uhd::runtime_error("Cannot create liberio link: DMA channels exhausted"); + } + auto link_info = _dma_channels.at(_next_channel++); + + /* FIXME: Should have common infrastructure for creating I/O services */ + auto io_srv = uhd::transport::inline_io_service::make(); + link_params_t link_params; + if (link_type == link_type_t::RX_DATA) { + link_params.recv_frame_size = get_mtu(uhd::RX_DIRECTION); // FIXME + link_params.send_frame_size = LIBERIO_MAX_NONDATA_PACKET_SIZE; // FIXME + } else if (link_type == link_type_t::TX_DATA) { + link_params.recv_frame_size = LIBERIO_MAX_NONDATA_PACKET_SIZE; // FIXME + link_params.send_frame_size = get_mtu(uhd::TX_DIRECTION); // FIXME + } else { + link_params.recv_frame_size = LIBERIO_MAX_NONDATA_PACKET_SIZE; // FIXME + link_params.send_frame_size = LIBERIO_MAX_NONDATA_PACKET_SIZE; // FIXME + } + link_params.num_recv_frames = LIBERIO_NUM_RECV_FRAMES; // FIXME + link_params.num_send_frames = LIBERIO_NUM_SEND_FRAMES; // FIXME + + // Liberio doesn't need in-band flow control, so pretend have very large buffers + link_params.recv_buff_size = std::numeric_limits<size_t>::max(); + link_params.send_buff_size = std::numeric_limits<size_t>::max(); + auto link = uhd::transport::liberio_link::make( + link_info.first, link_info.second, link_params); + io_srv->attach_send_link(link); + io_srv->attach_recv_link(link); + return std::tuple<io_service::sptr, + send_link_if::sptr, + size_t, + recv_link_if::sptr, + size_t, + bool>(io_srv, + link, + link_params.send_buff_size, + link, + link_params.recv_buff_size, + false); +} + +size_t mpmd_link_if_ctrl_liberio::get_mtu(const uhd::direction_t /*dir*/) const +{ + return LIBERIO_DEFAULT_MTU; // FIXME +} diff --git a/host/lib/usrp/mpmd/mpmd_link_if_ctrl_liberio.hpp b/host/lib/usrp/mpmd/mpmd_link_if_ctrl_liberio.hpp new file mode 100644 index 000000000..09fb24f8e --- /dev/null +++ b/host/lib/usrp/mpmd/mpmd_link_if_ctrl_liberio.hpp @@ -0,0 +1,64 @@ +// +// Copyright 2017 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_MPMD_XPORT_CTRL_LIBERIO_HPP +#define INCLUDED_MPMD_XPORT_CTRL_LIBERIO_HPP + +#include "mpmd_link_if_ctrl_base.hpp" +#include <uhd/types/device_addr.hpp> + +namespace uhd { namespace mpmd { namespace xport { + +/*! Liberio transport manager + */ +class mpmd_link_if_ctrl_liberio : public mpmd_link_if_ctrl_base +{ +public: + /* For liberio, get_chdr_link_options returns information about DMA engines. + * We assume there is only ever one liberio link available + * first = tx path + * second = rx path + */ + using liberio_link_info_t = std::pair<std::string, std::string>; + + mpmd_link_if_ctrl_liberio(const uhd::device_addr_t& mb_args, + const mpmd_link_if_mgr::xport_info_list_t& xport_info); + + size_t get_num_links() const { return 1; } + + uhd::transport::both_links_t get_link( + const size_t link_idx, const uhd::transport::link_type_t link_type, + const uhd::device_addr_t& link_args); + + size_t get_mtu(const uhd::direction_t) const; + + double get_link_rate(const size_t /*link_idx*/) const { return _link_rate; } + + const uhd::rfnoc::chdr::chdr_packet_factory& get_packet_factory() const + { + return _pkt_factory; + } + +private: + + const uhd::device_addr_t _mb_args; + const uhd::dict<std::string, std::string> _recv_args; + const uhd::dict<std::string, std::string> _send_args; + //! A list of DMA channels we can use for links + std::vector<liberio_link_info_t> _dma_channels; + double _link_rate; + + /*! An index representing the next DMA channel to use, for a simple + * allocation of channels. For get_link(), increment for each new link, and + * throw an exception if _next_channel > number of DMA channels. + */ + size_t _next_channel = 0; + static const uhd::rfnoc::chdr::chdr_packet_factory _pkt_factory; +}; + +}}} /* namespace uhd::mpmd::xport */ + +#endif /* INCLUDED_MPMD_XPORT_CTRL_LIBERIO_HPP */ diff --git a/host/lib/usrp/mpmd/mpmd_link_if_ctrl_udp.cpp b/host/lib/usrp/mpmd/mpmd_link_if_ctrl_udp.cpp new file mode 100644 index 000000000..c2d746f92 --- /dev/null +++ b/host/lib/usrp/mpmd/mpmd_link_if_ctrl_udp.cpp @@ -0,0 +1,279 @@ +// +// Copyright 2017 Ettus Research, National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "mpmd_link_if_ctrl_udp.hpp" +#include "mpmd_impl.hpp" +#include "mpmd_link_if_mgr.hpp" +#include <uhd/transport/udp_constants.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhdlib/transport/inline_io_service.hpp> +#include <uhdlib/transport/udp_boost_asio_link.hpp> +#include <uhdlib/utils/narrow.hpp> +#include <string> + +using namespace uhd; +using namespace uhd::transport; +using namespace uhd::mpmd::xport; + +const uhd::rfnoc::chdr::chdr_packet_factory mpmd_link_if_ctrl_udp::_pkt_factory( + uhd::rfnoc::CHDR_W_64, ENDIANNESS_BIG); + +namespace { + +//! Maximum CHDR packet size in bytes +const size_t MPMD_10GE_DATA_FRAME_MAX_SIZE = 8000; + +//! Maximum CHDR packet size in bytes +const size_t MPMD_10GE_ASYNCMSG_FRAME_MAX_SIZE = 1472; + +//! Number of send/recv frames +const size_t MPMD_ETH_NUM_FRAMES = 32; + +//! +const double MPMD_BUFFER_DEPTH = 20.0e-3; // s +//! For MTU discovery, the time we wait for a packet before calling it +// oversized (seconds). +const double MPMD_MTU_DISCOVERY_TIMEOUT = 0.02; + +// TODO: move these to appropriate header file for all other devices +const size_t MAX_RATE_1GIGE = 1e9 / 8; // byte/s +const size_t MAX_RATE_10GIGE = 10e9 / 8; // byte/s + + +mpmd_link_if_ctrl_udp::udp_link_info_map get_udp_info_from_xport_info( + const mpmd_link_if_mgr::xport_info_list_t& link_info_list) +{ + mpmd_link_if_ctrl_udp::udp_link_info_map result; + for (const auto& link_info : link_info_list) { + if (!link_info.count("ipv4")) { + UHD_LOG_ERROR("MPMD::XPORT::UDP", + "Invalid response from get_chdr_link_options()! No `ipv4' key!"); + throw uhd::runtime_error( + "Invalid response from get_chdr_link_options()! No `ipv4' key!"); + } + if (!link_info.count("port")) { + UHD_LOG_ERROR("MPMD::XPORT::UDP", + "Invalid response from get_chdr_link_options()! No `port' key!"); + throw uhd::runtime_error( + "Invalid response from get_chdr_link_options()! No `port' key!"); + } + const std::string udp_port = link_info.at("port"); + const size_t link_rate = link_info.count("link_rate") + ? std::stoul(link_info.at("link_rate")) + : MAX_RATE_1GIGE; + result.emplace(link_info.at("ipv4"), + mpmd_link_if_ctrl_udp::udp_link_info_t{udp_port, link_rate}); + } + + return result; +} + +std::vector<std::string> get_addrs_from_mb_args(const uhd::device_addr_t& mb_args, + const mpmd_link_if_ctrl_udp::udp_link_info_map& link_info_list) +{ + // mb_args must always include addr + if (not mb_args.has_key(FIRST_ADDR_KEY)) { + UHD_LOG_WARNING("MPMD::XPORT::UDP", + "The `" << FIRST_ADDR_KEY + << "' key must be specified in " + "device args to create an Ethernet transport to an RFNoC block"); + return {}; + } + std::vector<std::string> addrs{mb_args[FIRST_ADDR_KEY]}; + if (mb_args.has_key(SECOND_ADDR_KEY)) { + addrs.push_back(mb_args[SECOND_ADDR_KEY]); + } + // This is where in UHD we encode the knowledge about what + // get_chdr_link_options() returns to us. + for (const auto& ip_addr : addrs) { + if (link_info_list.count(ip_addr)) { + continue; + } + UHD_LOG_WARNING("MPMD::XPORT::UDP", + "Cannot create UDP link to device: The IP address `" + << ip_addr << "' is requested, but not reachable."); + return {}; + } + + return addrs; +} + +/*! Do a binary search to discover MTU + * + * Uses the MPM echo service to figure out MTU. We simply send a bunch of + * packets and see if they come back until we converged on the path MTU. + * The end result must lie between \p min_frame_size and \p max_frame_size. + * + * \param address IP address + * \param port UDP port (yeah it's a string!) + * \param min_frame_size Minimum frame size, initialize algorithm to start + * with this value + * \param max_frame_size Maximum frame size, initialize algorithm to start + * with this value + * \param echo_timeout Timeout value in seconds. For frame sizes that + * exceed the MTU, we don't expect a response, and this + * is the amount of time we'll wait before we assume + * the frame size exceeds the MTU. + */ +size_t discover_mtu(const std::string& address, + const std::string& port, + size_t min_frame_size, + size_t max_frame_size, + const double echo_timeout = 0.020) +{ + const size_t echo_prefix_offset = uhd::mpmd::mpmd_impl::MPM_ECHO_CMD.size(); + const size_t mtu_hdr_len = echo_prefix_offset + 10; + UHD_ASSERT_THROW(min_frame_size < max_frame_size); + UHD_ASSERT_THROW(min_frame_size % 4 == 0); + UHD_ASSERT_THROW(max_frame_size % 4 == 0); + UHD_ASSERT_THROW(min_frame_size >= echo_prefix_offset + mtu_hdr_len); + using namespace uhd::transport; + // The return port will probably differ from the discovery port, so we + // need a "broadcast" UDP connection; using make_connected() would + // drop packets + udp_simple::sptr udp = udp_simple::make_broadcast(address, port); + std::string send_buf(uhd::mpmd::mpmd_impl::MPM_ECHO_CMD); + send_buf.resize(max_frame_size, '#'); + UHD_ASSERT_THROW(send_buf.size() == max_frame_size); + std::vector<uint8_t> recv_buf; + recv_buf.resize(max_frame_size, ' '); + + // Little helper to check returned packets match the sent ones + auto require_bufs_match = [&recv_buf, &send_buf, mtu_hdr_len](const size_t len) { + if (len < mtu_hdr_len + or std::memcmp((void*)&recv_buf[0], (void*)&send_buf[0], mtu_hdr_len) != 0) { + throw uhd::runtime_error("Unexpected content of MTU " + "discovery return packet!"); + } + }; + UHD_LOG_TRACE("MPMD", "Determining UDP MTU... "); + size_t seq_no = 0; + while (min_frame_size < max_frame_size) { + // Only test multiples of 4 bytes! + const size_t test_frame_size = (max_frame_size / 2 + min_frame_size / 2 + 3) + & ~size_t(3); + // Encode sequence number and current size in the string, makes it + // easy to debug in code or Wireshark. Is also used for identifying + // response packets. + std::sprintf( + &send_buf[echo_prefix_offset], ";%04lu,%04lu", seq_no++, test_frame_size); + UHD_LOG_TRACE("MPMD", "Testing frame size " << test_frame_size); + udp->send(boost::asio::buffer(&send_buf[0], test_frame_size)); + + const size_t len = udp->recv(boost::asio::buffer(recv_buf), echo_timeout); + if (len == 0) { + // Nothing received, so this is probably too big + max_frame_size = test_frame_size - 4; + } else if (len >= test_frame_size) { + // Size went through, so bump the minimum + require_bufs_match(len); + min_frame_size = test_frame_size; + } else if (len < test_frame_size) { + // This is an odd case. Something must have snipped the packet + // on the way back. Still, we'll just back off and try + // something smaller. + UHD_LOG_DEBUG("MPMD", "Unexpected packet truncation during MTU discovery."); + require_bufs_match(len); + max_frame_size = len; + } + } + UHD_LOG_DEBUG("MPMD", "Path MTU for address " << address << ": " << min_frame_size); + return min_frame_size; +} + +} // namespace + + +/****************************************************************************** + * Structors + *****************************************************************************/ +mpmd_link_if_ctrl_udp::mpmd_link_if_ctrl_udp(const uhd::device_addr_t& mb_args, + const mpmd_link_if_mgr::xport_info_list_t& xport_info) + : _mb_args(mb_args) + , _recv_args(filter_args(mb_args, "recv")) + , _send_args(filter_args(mb_args, "send")) + , _udp_info(get_udp_info_from_xport_info(xport_info)) + , _mtu(MPMD_10GE_DATA_FRAME_MAX_SIZE) +{ + const std::string mpm_discovery_port = _mb_args.get( + mpmd_impl::MPM_DISCOVERY_PORT_KEY, std::to_string(mpmd_impl::MPM_DISCOVERY_PORT)); + auto discover_mtu_for_ip = [mpm_discovery_port](const std::string& ip_addr) { + return discover_mtu(ip_addr, + mpm_discovery_port, + IP_PROTOCOL_MIN_MTU_SIZE - IP_PROTOCOL_UDP_PLUS_IP_HEADER, + MPMD_10GE_DATA_FRAME_MAX_SIZE, + MPMD_MTU_DISCOVERY_TIMEOUT); + }; + + const std::vector<std::string> requested_addrs( + get_addrs_from_mb_args(mb_args, _udp_info)); + for (const auto& ip_addr : requested_addrs) { + try { + // If MTU discovery fails, we gracefully recover, but declare that + // link invalid. + _mtu = std::min(_mtu, discover_mtu_for_ip(ip_addr)); + _available_addrs.push_back(ip_addr); + } catch (const uhd::exception& ex) { + UHD_LOG_WARNING("MPMD::XPORT::UDP", + "Error during MTU discovery on address " << ip_addr << ": " << ex.what()); + } + } +} + +/****************************************************************************** + * API + *****************************************************************************/ +uhd::transport::both_links_t mpmd_link_if_ctrl_udp::get_link(const size_t link_idx, + const uhd::transport::link_type_t /*link_type*/, + const uhd::device_addr_t& /*link_args*/) +{ + UHD_ASSERT_THROW(link_idx < _available_addrs.size()); + const std::string ip_addr = _available_addrs.at(link_idx); + const std::string udp_port = _udp_info.at(ip_addr).udp_port; + + /* FIXME: Should have common infrastructure for creating I/O services */ + auto io_srv = uhd::transport::inline_io_service::make(); + link_params_t link_params; + link_params.num_recv_frames = MPMD_ETH_NUM_FRAMES; // FIXME + link_params.num_send_frames = MPMD_ETH_NUM_FRAMES; // FIXME + link_params.recv_frame_size = get_mtu(uhd::RX_DIRECTION); // FIXME + link_params.send_frame_size = get_mtu(uhd::TX_DIRECTION); // FIXME + link_params.recv_buff_size = MPMD_BUFFER_DEPTH * MAX_RATE_10GIGE; // FIXME + link_params.send_buff_size = MPMD_BUFFER_DEPTH * MAX_RATE_10GIGE; // FIXME + auto link = uhd::transport::udp_boost_asio_link::make(ip_addr, + udp_port, + link_params, + link_params.recv_buff_size, // FIXME + link_params.send_buff_size); // FIXME + io_srv->attach_send_link(link); + io_srv->attach_recv_link(link); + return std::tuple<io_service::sptr, + send_link_if::sptr, + size_t, + recv_link_if::sptr, + size_t, + bool>( + io_srv, link, link_params.send_buff_size, link, link_params.recv_buff_size, true); +} + +size_t mpmd_link_if_ctrl_udp::get_num_links() const +{ + return _available_addrs.size(); +} + +//! Return the rate of the underlying link in bytes/sec +double mpmd_link_if_ctrl_udp::get_link_rate(const size_t link_idx) const +{ + UHD_ASSERT_THROW(link_idx < get_num_links()); + return _udp_info.at(_available_addrs.at(link_idx)).link_rate; +} + +const uhd::rfnoc::chdr::chdr_packet_factory& +mpmd_link_if_ctrl_udp::get_packet_factory() const +{ + return _pkt_factory; +} diff --git a/host/lib/usrp/mpmd/mpmd_link_if_ctrl_udp.hpp b/host/lib/usrp/mpmd/mpmd_link_if_ctrl_udp.hpp new file mode 100644 index 000000000..4c8ecade7 --- /dev/null +++ b/host/lib/usrp/mpmd/mpmd_link_if_ctrl_udp.hpp @@ -0,0 +1,61 @@ +// +// Copyright 2017 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_MPMD_XPORT_CTRL_UDP_HPP +#define INCLUDED_MPMD_XPORT_CTRL_UDP_HPP + +#include "mpmd_link_if_ctrl_base.hpp" +#include "mpmd_link_if_mgr.hpp" +#include <uhd/types/device_addr.hpp> +#include <unordered_map> + +namespace uhd { namespace mpmd { namespace xport { + +/*! UDP link interface controller + * + * Opens UDP sockets + */ +class mpmd_link_if_ctrl_udp : public mpmd_link_if_ctrl_base +{ +public: + struct udp_link_info_t + { + std::string udp_port; + size_t link_rate; + }; + + using udp_link_info_map = std::unordered_map<std::string, udp_link_info_t>; + + mpmd_link_if_ctrl_udp(const uhd::device_addr_t& mb_args, + const mpmd_link_if_mgr::xport_info_list_t& xport_info); + + size_t get_num_links() const; + uhd::transport::both_links_t get_link(const size_t link_idx, + const uhd::transport::link_type_t link_type, + const uhd::device_addr_t& link_args); + size_t get_mtu(const uhd::direction_t) const + { + return _mtu; + } + double get_link_rate(const size_t link_idx) const; + const uhd::rfnoc::chdr::chdr_packet_factory& get_packet_factory() const; + +private: + const uhd::device_addr_t _mb_args; + const uhd::dict<std::string, std::string> _recv_args; + const uhd::dict<std::string, std::string> _send_args; + //! + udp_link_info_map _udp_info; + //! A list of IP addresses we can connect our CHDR connections to + std::vector<std::string> _available_addrs; + //! MTU + size_t _mtu; + static const uhd::rfnoc::chdr::chdr_packet_factory _pkt_factory; +}; + +}}} /* namespace uhd::mpmd::xport */ + +#endif /* INCLUDED_MPMD_XPORT_CTRL_UDP_HPP */ diff --git a/host/lib/usrp/mpmd/mpmd_link_if_mgr.cpp b/host/lib/usrp/mpmd/mpmd_link_if_mgr.cpp new file mode 100644 index 000000000..6bb6cae3a --- /dev/null +++ b/host/lib/usrp/mpmd/mpmd_link_if_mgr.cpp @@ -0,0 +1,135 @@ +// +// Copyright 2017 Ettus Research, National Instruments Company +// Copyright 2019 Ettus Research, National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "mpmd_link_if_mgr.hpp" +#include "mpmd_impl.hpp" +#include "mpmd_link_if_ctrl_base.hpp" +#include "mpmd_link_if_ctrl_udp.hpp" +#ifdef HAVE_LIBERIO +# include "mpmd_link_if_ctrl_liberio.hpp" +#endif +#ifdef HAVE_DPDK +# include "mpmd_link_if_ctrl_dpdk_udp.hpp" +#endif + +uhd::dict<std::string, std::string> uhd::mpmd::xport::filter_args( + const uhd::device_addr_t& args, const std::string& prefix) +{ + uhd::dict<std::string, std::string> filtered_args; + for (const std::string& key : args.keys()) { + if (key.find(prefix) != std::string::npos) { + filtered_args[key] = args[key]; + } + } + + return filtered_args; +} + +using namespace uhd::mpmd::xport; + +class mpmd_link_if_mgr_impl : public mpmd_link_if_mgr +{ +public: + mpmd_link_if_mgr_impl(const uhd::device_addr_t& mb_args) : _mb_args(mb_args) {} + + /************************************************************************** + * API (see mpmd_link_if_mgr.hpp) + *************************************************************************/ + bool connect(const std::string& link_type, const xport_info_list_t& xport_info) + { + auto link_if_ctrl = make_link_if_ctrl(link_type, xport_info); + if (!link_if_ctrl) { + UHD_LOG_WARNING( + "MPMD::XPORT", "Unable to create xport ctrl for link type " << link_type); + return false; + } + if (link_if_ctrl->get_num_links() == 0) { + UHD_LOG_TRACE("MPMD::XPORT", + "Link type " << link_type + << " has no valid links in this configuration."); + return false; + } + const size_t xport_idx = _link_if_ctrls.size(); + for (size_t link_idx = 0; link_idx < link_if_ctrl->get_num_links(); link_idx++) { + _link_link_if_ctrl_map.push_back(std::make_pair(xport_idx, link_idx)); + } + _link_if_ctrls.push_back(std::move(link_if_ctrl)); + return true; + } + + size_t get_num_links() + { + return _link_link_if_ctrl_map.size(); + } + + uhd::transport::both_links_t get_link(const size_t link_idx, + const uhd::transport::link_type_t link_type, + const uhd::device_addr_t& link_args) + { + const size_t link_if_ctrl_idx = _link_link_if_ctrl_map.at(link_idx).first; + const size_t xport_link_idx = _link_link_if_ctrl_map.at(link_idx).second; + return _link_if_ctrls.at(link_if_ctrl_idx) + ->get_link(xport_link_idx, link_type, link_args); + } + + size_t get_mtu(const size_t link_idx, const uhd::direction_t dir) const + { + return _link_if_ctrls.at(_link_link_if_ctrl_map.at(link_idx).first)->get_mtu(dir); + } + + const uhd::rfnoc::chdr::chdr_packet_factory& get_packet_factory( + const size_t link_idx) const + { + const size_t link_if_ctrl_idx = _link_link_if_ctrl_map.at(link_idx).first; + return _link_if_ctrls.at(link_if_ctrl_idx)->get_packet_factory(); + } + +private: + /************************************************************************** + * Private methods / helpers + *************************************************************************/ + mpmd_link_if_ctrl_base::uptr make_link_if_ctrl( + const std::string& link_type, const xport_info_list_t& xport_info) + { + // Here, we hard-code the list of available transport types + if (link_type == "udp") { +#ifdef HAVE_DPDK + // if (_mb_args.has_key("use_dpdk")) { + // return std::make_unique<mpmd_link_if_ctrl_dpdk_udp>(_mb_args, + // xport_info); + //} +#endif + return std::make_unique<mpmd_link_if_ctrl_udp>(_mb_args, xport_info); +#ifdef HAVE_LIBERIO + } else if (link_type == "liberio") { + return std::make_unique<mpmd_link_if_ctrl_liberio>(_mb_args, xport_info); +#endif + } + UHD_LOG_WARNING("MPMD", "Cannot instantiate transport medium " << link_type); + return nullptr; + } + + /************************************************************************** + * Private attributes + *************************************************************************/ + //! Cache available xport manager implementations + // + // Should only every be populated by connect() + std::vector<mpmd_link_if_ctrl_base::uptr> _link_if_ctrls; + // Maps link index to link_if_ctrl index. To look up the xport ctrl for link + // number L, do something like this: + // auto& link_if_ctrl = _link_if_ctrls.at(_link_link_if_ctrl_map.at(L).first); + std::vector<std::pair<size_t, size_t>> _link_link_if_ctrl_map; + + //! Motherboard args, can contain things like 'recv_buff_size' + const uhd::device_addr_t _mb_args; +}; + +mpmd_link_if_mgr::uptr mpmd_link_if_mgr::make(const uhd::device_addr_t& mb_args) +{ + return std::make_unique<mpmd_link_if_mgr_impl>(mb_args); +} diff --git a/host/lib/usrp/mpmd/mpmd_xport_mgr.hpp b/host/lib/usrp/mpmd/mpmd_link_if_mgr.hpp index 3d96e5ec6..4b0ba4212 100644 --- a/host/lib/usrp/mpmd/mpmd_xport_mgr.hpp +++ b/host/lib/usrp/mpmd/mpmd_link_if_mgr.hpp @@ -4,11 +4,14 @@ // SPDX-License-Identifier: GPL-3.0-or-later // -#ifndef INCLUDED_MPMD_XPORT_MGR_HPP -#define INCLUDED_MPMD_XPORT_MGR_HPP +#ifndef INCLUDED_MPMD_LINK_IF_MGR_HPP +#define INCLUDED_MPMD_LINK_IF_MGR_HPP -#include "../device3/device3_impl.hpp" +#include <uhd/types/device_addr.hpp> #include <uhd/types/dict.hpp> +#include <uhd/types/direction.hpp> +#include <uhdlib/rfnoc/chdr_packet.hpp> +#include <uhdlib/transport/links.hpp> #include <map> #include <memory> #include <string> @@ -20,8 +23,6 @@ namespace uhd { namespace mpmd { namespace xport { * Transport specifiers */ -//! Ethernet address for management and RPC communication -const std::string MGMT_ADDR_KEY = "mgmt_addr"; //! Primary Ethernet address for streaming and RFNoC communication const std::string FIRST_ADDR_KEY = "addr"; //! Secondary Ethernet address for streaming and RFNoC communication @@ -45,13 +46,13 @@ uhd::dict<std::string, std::string> filter_args( * medium. For example, if the medium is Ethernet/UDP, this class will create * sockets. */ -class mpmd_xport_mgr +class mpmd_link_if_mgr { public: - using uptr = std::unique_ptr<mpmd_xport_mgr>; + using uptr = std::unique_ptr<mpmd_link_if_mgr>; using xport_info_t = std::map<std::string, std::string>; using xport_info_list_t = std::vector<std::map<std::string, std::string>>; - virtual ~mpmd_xport_mgr() {} + virtual ~mpmd_link_if_mgr() {} /*! Return a reference to a transport manager * @@ -65,6 +66,45 @@ public: */ static uptr make(const uhd::device_addr_t& mb_args); + /*! Attempt to open a CHDR-capable link to the remote device + * + * This will compare the mb_args (passed in at construction) with + * \p xport_info to see if it can connect this way. For example, if + * \p xport_type is "udp", then it will see if it can find the `addr` key + * from mb_args in the \p xport_info. If yes, it will use that for + * connections. + * + * \param xport_type The type of xport ("udp", "liberio", ...) + * \param xport_info The available information on this transport. For + * example, if the xport_type is "udp", then this would + * contain the available IP addresses. + * \returns true on success + */ + virtual bool connect( + const std::string& xport_type, const xport_info_list_t& xport_info) = 0; + + /*! The number of available links + * + * If zero, it means that there is no valid connection to the device. + * + */ + virtual size_t get_num_links() = 0; + + /*! Return links object + * + * \param link_idx The number of the link to use. link_idx < get_num_links() + * must hold true. link_idx is often 0. Example: When + * the underlying transport is Ethernet, and the user + * specified both addr and second_addr, then get_num_links() + * equals 2 and link_idx can also be 1. + * \param link_type CTRL, RX_DATA, or TX_DATA (for configuring the link) + * \param link_args Link-specific additional information that the underlying + * mpmd_link_if_ctrl instantiation can use + */ + virtual uhd::transport::both_links_t get_link(const size_t link_idx, + const uhd::transport::link_type_t link_type, + const uhd::device_addr_t& link_args) = 0; + /*! Create a transports object * * Implementation details depend on the underlying implementation. @@ -90,16 +130,28 @@ public: * The latter needs to get sent back to MPM to complete the * transport handshake. */ - virtual both_xports_t make_transport(const xport_info_list_t& xport_info_list, - const usrp::device3_impl::xport_type_t xport_type, - const uhd::device_addr_t& xport_args, - xport_info_t& xport_info_out) = 0; + //virtual both_xports_t make_transport(const xport_info_list_t& xport_info_list, + //const usrp::device3_impl::xport_type_t xport_type, + //const uhd::device_addr_t& xport_args, + //xport_info_t& xport_info_out) = 0; /*! Return the path MTU for whatever this manager lets us do */ - virtual size_t get_mtu(const uhd::direction_t dir) const = 0; + virtual size_t get_mtu(const size_t link_idx, const uhd::direction_t dir) const = 0; + + /*! Get packet factory from associated link_mgr + * + * \param link_idx The number of the link to use. link_idx < get_num_links() + * must hold true. link_idx is often 0. Example: When + * the underlying transport is Ethernet, and the user + * specified both addr and second_addr, then get_num_links() + * equals 2 and link_idx can also be 1. + * \return a CHDR packet factory + */ + virtual const uhd::rfnoc::chdr::chdr_packet_factory& get_packet_factory( + const size_t link_idx) const = 0; }; }}} /* namespace uhd::mpmd::xport */ -#endif /* INCLUDED_MPMD_XPORT_MGR_HPP */ +#endif /* INCLUDED_MPMD_LINK_IF_MGR_HPP */ diff --git a/host/lib/usrp/mpmd/mpmd_mb_controller.cpp b/host/lib/usrp/mpmd/mpmd_mb_controller.cpp index 6c2954fb8..e9310d01d 100644 --- a/host/lib/usrp/mpmd/mpmd_mb_controller.cpp +++ b/host/lib/usrp/mpmd/mpmd_mb_controller.cpp @@ -4,11 +4,34 @@ // SPDX-License-Identifier: GPL-3.0-or-later // -#include "mpmd_mb_controller.hpp" +#include <uhdlib/usrp/common/mpmd_mb_controller.hpp> using namespace uhd::rfnoc; +using namespace uhd; +namespace { + //! Default timeout value for tRPC calls that we know can take long (ms) + constexpr size_t MPMD_DEFAULT_LONG_TIMEOUT = 12000; // ms +} + +mpmd_mb_controller::mpmd_mb_controller( + uhd::rpc_client::sptr rpcc, uhd::device_addr_t device_info) + : _rpc(rpcc), _device_info(device_info) +{ + const size_t num_tks = _rpc->request_with_token<size_t>("get_num_timekeepers"); + for (size_t tk_idx = 0; tk_idx < num_tks; tk_idx++) { + register_timekeeper(tk_idx, std::make_shared<mpmd_timekeeper>(tk_idx, _rpc)); + } + + auto sensor_list = + _rpc->request_with_token<std::vector<std::string>>("get_mb_sensors"); + UHD_LOG_DEBUG("MPMD", "Found " << sensor_list.size() << " motherboard sensors."); + _sensor_names.insert(sensor_list.cbegin(), sensor_list.cend()); +} +/****************************************************************************** + * Timekeeper API + *****************************************************************************/ uint64_t mpmd_mb_controller::mpmd_timekeeper::get_ticks_now() { return _rpc->request_with_token<uint64_t>("get_timekeeper_time", _tk_idx, false); @@ -34,3 +57,120 @@ void mpmd_mb_controller::mpmd_timekeeper::set_period(const uint64_t period_ns) _rpc->notify_with_token("set_tick_period", _tk_idx, period_ns); } +void mpmd_mb_controller::mpmd_timekeeper::update_tick_rate(const double tick_rate) +{ + set_tick_rate(tick_rate); +} + +/****************************************************************************** + * Motherboard Control API (see mb_controller.hpp) + *****************************************************************************/ +std::string mpmd_mb_controller::get_mboard_name() const +{ + return _device_info.get("name", "UNKNOWN"); +} + +void mpmd_mb_controller::set_time_source(const std::string& source) +{ + _rpc->notify_with_token(MPMD_DEFAULT_LONG_TIMEOUT, "set_time_source", source); +} + +std::string mpmd_mb_controller::get_time_source() const +{ + return _rpc->request_with_token<std::string>("get_time_source"); +} + +std::vector<std::string> mpmd_mb_controller::get_time_sources() const +{ + return _rpc->request_with_token<std::vector<std::string>>("get_time_sources"); +} + +void mpmd_mb_controller::set_clock_source(const std::string& source) +{ + _rpc->notify_with_token(MPMD_DEFAULT_LONG_TIMEOUT, "set_clock_source", source); +} + +std::string mpmd_mb_controller::get_clock_source() const +{ + return _rpc->request_with_token<std::string>("get_clock_source"); +} + +std::vector<std::string> mpmd_mb_controller::get_clock_sources() const +{ + return _rpc->request_with_token<std::vector<std::string>>("get_clock_sources"); +} + +void mpmd_mb_controller::set_sync_source(const std::string& clock_source, const std::string& time_source) +{ + uhd::device_addr_t sync_source; + sync_source["clock_source"] = clock_source; + sync_source["time_source"] = time_source; + set_sync_source(sync_source); +} + +void mpmd_mb_controller::set_sync_source(const device_addr_t& sync_source) +{ + std::map<std::string, std::string> sync_source_map; + for (const auto& key : sync_source.keys()) { + sync_source_map[key] = sync_source.get(key); + } + _rpc->notify_with_token( + MPMD_DEFAULT_LONG_TIMEOUT, "set_clock_source", sync_source_map); +} + +device_addr_t mpmd_mb_controller::get_sync_source() const +{ + const auto sync_source_map = + _rpc->request_with_token<std::map<std::string, std::string>>("get_sync_source"); + return device_addr_t(sync_source_map); +} + +std::vector<device_addr_t> mpmd_mb_controller::get_sync_sources() +{ + std::vector<device_addr_t> result; + const auto sync_sources = + _rpc->request_with_token<std::vector<std::map<std::string, std::string>>>( + "get_sync_sources"); + for (auto& sync_source : sync_sources) { + result.push_back(device_addr_t(sync_source)); + } + + return result; +} + +void mpmd_mb_controller::set_clock_source_out(const bool /*enb*/) +{ + throw uhd::not_implemented_error( + "set_clock_source_out() not implemented on this device!"); +} + +void mpmd_mb_controller::set_time_source_out(const bool /*enb*/) +{ + throw uhd::not_implemented_error( + "set_time_source_out() not implemented on this device!"); +} + +sensor_value_t mpmd_mb_controller::get_sensor(const std::string& name) +{ + if (!_sensor_names.count(name)) { + throw uhd::key_error(std::string("Invalid motherboard sensor name: ") + name); + } + return sensor_value_t( + _rpc->request_with_token<sensor_value_t::sensor_map_t>("get_mb_sensor", name)); +} + +std::vector<std::string> mpmd_mb_controller::get_sensor_names() +{ + std::vector<std::string> sensor_names(_sensor_names.cbegin(), _sensor_names.cend()); + return sensor_names; +} + +uhd::usrp::mboard_eeprom_t mpmd_mb_controller::get_eeprom() +{ + auto mb_eeprom = + _rpc->request_with_token<std::map<std::string, std::string>>("get_mb_eeprom"); + uhd::usrp::mboard_eeprom_t mb_eeprom_dict( + mb_eeprom.cbegin(), mb_eeprom.cend()); + return mb_eeprom_dict; +} + diff --git a/host/lib/usrp/mpmd/mpmd_mb_controller.hpp b/host/lib/usrp/mpmd/mpmd_mb_controller.hpp deleted file mode 100644 index 65e5dc468..000000000 --- a/host/lib/usrp/mpmd/mpmd_mb_controller.hpp +++ /dev/null @@ -1,73 +0,0 @@ -// -// Copyright 2019 Ettus Research, a National Instruments Brand -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#ifndef INCLUDED_LIBUHD_MPMD_MB_CONTROLLER_HPP -#define INCLUDED_LIBUHD_MPMD_MB_CONTROLLER_HPP - -#include <uhd/rfnoc/mb_controller.hpp> -#include <uhdlib/utils/rpc.hpp> - -namespace uhd { namespace rfnoc { - -/*! X300-Specific version of the mb_controller - * - * Reminder: There is one of these per motherboard. - */ -class mpmd_mb_controller : public mb_controller -{ -public: - - - //! Return reference to the RPC client - uhd::rpc_client::sptr get_rpc_client() { return _rpc; } - - //! X300-specific version of the timekeeper controls - class mpmd_timekeeper : public mb_controller::timekeeper - { - public: - mpmd_timekeeper(const size_t tk_idx, uhd::rpc_client::sptr rpc_client) - : _tk_idx(tk_idx), _rpc(rpc_client) - { - // nop - } - - uint64_t get_ticks_now(); - - uint64_t get_ticks_last_pps(); - - void set_ticks_now(const uint64_t ticks); - - void set_ticks_next_pps(const uint64_t ticks); - - void set_period(const uint64_t period_ns); - - private: - /*! Shorthand to perform an RPC request. Saves some typing. - */ - template <typename return_type, typename... Args> - return_type request(std::string const& func_name, Args&&... args) - { - UHD_LOG_TRACE("X300MBCTRL", "[RPC] Calling " << func_name); - return _rpc->request_with_token<return_type>( - func_name, std::forward<Args>(args)...); - }; - - const size_t _tk_idx; - - uhd::rpc_client::sptr _rpc; - }; - -private: - /************************************************************************** - * Attributes - *************************************************************************/ - //! Reference to RPC interface - uhd::rpc_client::sptr _rpc; -}; - -}} // namespace uhd::rfnoc - -#endif /* INCLUDED_LIBUHD_MPMD_MB_CONTROLLER_HPP */ diff --git a/host/lib/usrp/mpmd/mpmd_mb_iface.cpp b/host/lib/usrp/mpmd/mpmd_mb_iface.cpp new file mode 100644 index 000000000..e713cc7a3 --- /dev/null +++ b/host/lib/usrp/mpmd/mpmd_mb_iface.cpp @@ -0,0 +1,301 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "mpmd_mb_iface.hpp" +#include "mpmd_link_if_mgr.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/log.hpp> +#include <uhdlib/rfnoc/device_id.hpp> + +using namespace uhd::rfnoc; +using namespace uhd::mpmd; + +mpmd_mboard_impl::mpmd_mb_iface::mpmd_mb_iface( + const uhd::device_addr_t& mb_args, uhd::rpc_client::sptr rpc) + : _mb_args(mb_args), _rpc(rpc), _link_if_mgr(xport::mpmd_link_if_mgr::make(mb_args)) +{ + _remote_device_id = allocate_device_id(); + UHD_LOG_TRACE("MPMD::MB_IFACE", "Assigning device_id " << _remote_device_id); + _rpc->notify_with_token("set_device_id", _remote_device_id); +} + +/****************************************************************************** + * mpmd_mb_iface API + *****************************************************************************/ +void mpmd_mboard_impl::mpmd_mb_iface::init() +{ + UHD_LOG_TRACE("MPMD::MB_IFACE", "Requesting clock ifaces..."); + auto clock_ifaces = _rpc->request_with_token<clock_iface_list_t>("get_clocks"); + for (auto& clock : clock_ifaces) { + auto iface = std::make_shared<uhd::rfnoc::clock_iface>( + clock.at("name"), std::stod(clock.at("freq")), clock.count("mutable")); + _clock_ifaces[clock.at("name")] = iface; + iface->set_running(true); + UHD_LOG_DEBUG("MPMD::MB_IFACE", + "Adding clock iface `" + << clock.at("name") << "`, frequency: " << (iface->get_freq() / 1e6) + << " MHz, mutable: " << (iface->is_mutable() ? "Yes" : "No")); + } + UHD_LOG_TRACE("MPMD::MB_IFACE", "Requesting CHDR link types..."); + auto chdr_link_types = + _rpc->request_with_token<std::vector<std::string>>("get_chdr_link_types"); + UHD_LOG_TRACE( + "MPMD::MB_IFACE", "Found " << chdr_link_types.size() << " link type(s)"); + for (const auto& type : chdr_link_types) { + UHD_LOG_TRACE("MPMD::MB_IFACE", "Trying link type `" << type << "'"); + const auto xport_info = + _rpc->request_with_token<xport::mpmd_link_if_mgr::xport_info_list_t>( + "get_chdr_link_options", type); + // User may have specified: addr=192.168.10.2, second_addr= + // MPM may have said: "my addresses are 192.168.10.2 and 192.168.20.2" + if (_link_if_mgr->connect(type, xport_info)) { + UHD_LOG_TRACE("MPMD::MB_IFACE", "Link type " << type << " successful."); + } + } + + if (_link_if_mgr->get_num_links() == 0) { + UHD_LOG_ERROR("MPMD::MB_IFACE", "No CHDR connection available!"); + throw uhd::runtime_error("No CHDR connection available!"); + } + + for (size_t link_idx = 0; link_idx < _link_if_mgr->get_num_links(); link_idx++) { + _local_device_id_map.emplace(allocate_device_id(), link_idx); + } +} + +/****************************************************************************** + * mb_iface API + *****************************************************************************/ +uint16_t mpmd_mboard_impl::mpmd_mb_iface::get_proto_ver() +{ + return _rpc->request_with_token<uint16_t>("get_proto_ver"); +} + +uhd::rfnoc::chdr_w_t mpmd_mboard_impl::mpmd_mb_iface::get_chdr_w() +{ + const auto chdr_w_bits = _rpc->request_with_token<size_t>("get_chdr_width"); + switch (chdr_w_bits) { + case 512: + return CHDR_W_512; + case 256: + return CHDR_W_256; + case 128: + return CHDR_W_128; + case 64: + return CHDR_W_64; + } + throw uhd::runtime_error(std::string("Device reporting invalid CHDR width: ") + + std::to_string(chdr_w_bits)); +} + +uhd::endianness_t mpmd_mboard_impl::mpmd_mb_iface::get_endianness( + const uhd::rfnoc::device_id_t local_device_id) +{ + uhd::rfnoc::device_id_t lookup_id = local_device_id; + if (lookup_id == NULL_DEVICE_ID) { + for (auto& ids : _local_device_id_map) { + lookup_id = ids.first; + break; + } + } + const size_t link_idx = _local_device_id_map.at(lookup_id); + auto& pkt_factory = _link_if_mgr->get_packet_factory(link_idx); + return pkt_factory.get_endianness(); +} + +uhd::rfnoc::device_id_t mpmd_mboard_impl::mpmd_mb_iface::get_remote_device_id() +{ + return _remote_device_id; +} + +std::vector<device_id_t> mpmd_mboard_impl::mpmd_mb_iface::get_local_device_ids() +{ + std::vector<device_id_t> device_ids; + for (auto& local_dev_id_pair : _local_device_id_map) { + device_ids.push_back(local_dev_id_pair.first); + } + return device_ids; +} + +uhd::transport::adapter_id_t mpmd_mboard_impl::mpmd_mb_iface::get_adapter_id( + const uhd::rfnoc::device_id_t local_device_id) +{ + return _adapter_map.at(local_device_id); +} + +void mpmd_mboard_impl::mpmd_mb_iface::reset_network() +{ + // FIXME +} + +uhd::rfnoc::clock_iface::sptr mpmd_mboard_impl::mpmd_mb_iface::get_clock_iface( + const std::string& clock_name) +{ + if (_clock_ifaces.count(clock_name)) { + return _clock_ifaces.at(clock_name); + } else { + UHD_LOG_ERROR("MPMD::MB_IFACE", "Invalid timebase clock name: " + clock_name); + throw uhd::key_error( + "[MPMD_MB::IFACE] Invalid timebase clock name: " + clock_name); + } +} + +uhd::rfnoc::chdr_ctrl_xport::sptr mpmd_mboard_impl::mpmd_mb_iface::make_ctrl_transport( + uhd::rfnoc::device_id_t local_device_id, const uhd::rfnoc::sep_id_t& local_epid) +{ + if (!_local_device_id_map.count(local_device_id)) { + throw uhd::key_error(std::string("[MPMD::MB_IFACE] Cannot create control " + "transport: Unknown local device ID ") + + std::to_string(local_device_id)); + } + const size_t link_idx = _local_device_id_map.at(local_device_id); + uhd::transport::io_service::sptr io_srv; + uhd::transport::send_link_if::sptr send_link; + uhd::transport::recv_link_if::sptr recv_link; + std::tie(io_srv, send_link, std::ignore, recv_link, std::ignore, std::ignore) = + _link_if_mgr->get_link( + link_idx, uhd::transport::link_type_t::CTRL, uhd::device_addr_t()); + + /* Associate local device ID with the adapter */ + _adapter_map[local_device_id] = send_link->get_send_adapter_id(); + + auto pkt_factory = _link_if_mgr->get_packet_factory(link_idx); + auto xport = uhd::rfnoc::chdr_ctrl_xport::make(io_srv, + send_link, + recv_link, + pkt_factory, + local_epid, + send_link->get_num_send_frames(), + recv_link->get_num_recv_frames()); + return xport; +} + +uhd::rfnoc::chdr_rx_data_xport::uptr +mpmd_mboard_impl::mpmd_mb_iface::make_rx_data_transport( + uhd::rfnoc::mgmt::mgmt_portal& mgmt_portal, + const uhd::rfnoc::sep_addr_pair_t& addrs, + const uhd::rfnoc::sep_id_pair_t& epids, + const uhd::rfnoc::sw_buff_t pyld_buff_fmt, + const uhd::rfnoc::sw_buff_t mdata_buff_fmt, + const uhd::device_addr_t& xport_args) +{ + const uhd::rfnoc::sep_addr_t local_sep_addr = addrs.second; + + if (!_local_device_id_map.count(local_sep_addr.first)) { + throw uhd::key_error(std::string("[MPMD::MB_IFACE] Cannot create RX data " + "transport: Unknown local device ID ") + + std::to_string(local_sep_addr.first)); + } + const size_t link_idx = _local_device_id_map.at(local_sep_addr.first); + + uhd::transport::io_service::sptr io_srv; + uhd::transport::send_link_if::sptr send_link; + uhd::transport::recv_link_if::sptr recv_link; + bool lossy_xport; + size_t recv_buff_size; + std::tie(io_srv, send_link, std::ignore, recv_link, recv_buff_size, lossy_xport) = + _link_if_mgr->get_link( + link_idx, uhd::transport::link_type_t::RX_DATA, xport_args); + + /* Associate local device ID with the adapter */ + _adapter_map[local_sep_addr.first] = send_link->get_send_adapter_id(); + + // TODO: configure this based on the transport type + const stream_buff_params_t recv_capacity = { + recv_buff_size, uhd::rfnoc::MAX_FC_CAPACITY_PKTS}; + + const double ratio = 1.0 / 32; + + // Configure flow control frequency to use bytes only for UDP + stream_buff_params_t fc_freq = { + static_cast<uint64_t>(std::ceil(double(recv_buff_size) * ratio)), + uhd::rfnoc::MAX_FC_FREQ_PKTS}; + + stream_buff_params_t fc_headroom = {0, 0}; + + // Create the data transport + auto pkt_factory = _link_if_mgr->get_packet_factory(link_idx); + auto fc_params = chdr_rx_data_xport::configure_sep(io_srv, + recv_link, + send_link, + pkt_factory, + mgmt_portal, + epids, + pyld_buff_fmt, + mdata_buff_fmt, + recv_capacity, + fc_freq, + fc_headroom, + lossy_xport); + auto rx_xport = std::make_unique<chdr_rx_data_xport>(io_srv, + recv_link, + send_link, + pkt_factory, + epids, + recv_link->get_num_recv_frames(), + fc_params); + + return rx_xport; +} + +uhd::rfnoc::chdr_tx_data_xport::uptr +mpmd_mboard_impl::mpmd_mb_iface::make_tx_data_transport( + uhd::rfnoc::mgmt::mgmt_portal& mgmt_portal, + const uhd::rfnoc::sep_addr_pair_t& addrs, + const uhd::rfnoc::sep_id_pair_t& epids, + const uhd::rfnoc::sw_buff_t pyld_buff_fmt, + const uhd::rfnoc::sw_buff_t mdata_buff_fmt, + const uhd::device_addr_t& xport_args) +{ + const uhd::rfnoc::sep_addr_t local_sep_addr = addrs.first; + + if (!_local_device_id_map.count(local_sep_addr.first)) { + throw uhd::key_error(std::string("[MPMD::MB_IFACE] Cannot create TX data " + "transport: Unknown local device ID ") + + std::to_string(local_sep_addr.first)); + } + const size_t link_idx = _local_device_id_map.at(local_sep_addr.first); + + uhd::transport::io_service::sptr io_srv; + uhd::transport::send_link_if::sptr send_link; + uhd::transport::recv_link_if::sptr recv_link; + bool lossy_xport; + std::tie(io_srv, send_link, std::ignore, recv_link, std::ignore, lossy_xport) = + _link_if_mgr->get_link( + link_idx, uhd::transport::link_type_t::TX_DATA, xport_args); + + /* Associate local device ID with the adapter */ + _adapter_map[local_sep_addr.first] = send_link->get_send_adapter_id(); + + // TODO: configure this based on the transport type + const double fc_freq_ratio = 1.0 / 8; + const double fc_headroom_ratio = 0; + + auto pkt_factory = _link_if_mgr->get_packet_factory(link_idx); + const auto buff_capacity = chdr_tx_data_xport::configure_sep(io_srv, + recv_link, + send_link, + pkt_factory, + mgmt_portal, + epids, + pyld_buff_fmt, + mdata_buff_fmt, + fc_freq_ratio, + fc_headroom_ratio); + + // Create the data transport + auto tx_xport = std::make_unique<chdr_tx_data_xport>(io_srv, + recv_link, + send_link, + pkt_factory, + epids, + send_link->get_num_send_frames(), + buff_capacity); + + + return tx_xport; +} diff --git a/host/lib/usrp/mpmd/mpmd_mb_iface.hpp b/host/lib/usrp/mpmd/mpmd_mb_iface.hpp new file mode 100644 index 000000000..4e47dd35a --- /dev/null +++ b/host/lib/usrp/mpmd/mpmd_mb_iface.hpp @@ -0,0 +1,68 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_MPMD_MB_IFACE_HPP +#define INCLUDED_MPMD_MB_IFACE_HPP + +#include "mpmd_impl.hpp" +#include "mpmd_link_if_mgr.hpp" +#include <uhdlib/rfnoc/mb_iface.hpp> +#include <map> +#include <unordered_map> + +namespace uhd { namespace mpmd { + +class mpmd_mboard_impl::mpmd_mb_iface : public uhd::rfnoc::mb_iface +{ +public: + using uptr = std::unique_ptr<mpmd_mb_iface>; + using clock_iface_list_t = std::vector<std::map<std::string, std::string>>; + mpmd_mb_iface(const uhd::device_addr_t& mb_args, uhd::rpc_client::sptr rpc); + ~mpmd_mb_iface() = default; + + /*** mpmd_mb_iface API calls *****************************************/ + //! Initialize transports + void init(); + + /*** mb_iface API calls **********************************************/ + uint16_t get_proto_ver(); + uhd::rfnoc::chdr_w_t get_chdr_w(); + uhd::endianness_t get_endianness(const uhd::rfnoc::device_id_t local_device_id); + uhd::rfnoc::device_id_t get_remote_device_id(); + std::vector<uhd::rfnoc::device_id_t> get_local_device_ids(); + uhd::transport::adapter_id_t get_adapter_id(const uhd::rfnoc::device_id_t local_device_id); + void reset_network(); + uhd::rfnoc::clock_iface::sptr get_clock_iface(const std::string& clock_name); + uhd::rfnoc::chdr_ctrl_xport::sptr make_ctrl_transport( + uhd::rfnoc::device_id_t local_device_id, const uhd::rfnoc::sep_id_t& local_epid); + uhd::rfnoc::chdr_rx_data_xport::uptr make_rx_data_transport( + uhd::rfnoc::mgmt::mgmt_portal& mgmt_portal, + const uhd::rfnoc::sep_addr_pair_t& addrs, + const uhd::rfnoc::sep_id_pair_t& epids, + const uhd::rfnoc::sw_buff_t pyld_buff_fmt, + const uhd::rfnoc::sw_buff_t mdata_buff_fmt, + const uhd::device_addr_t& xport_args); + uhd::rfnoc::chdr_tx_data_xport::uptr make_tx_data_transport( + uhd::rfnoc::mgmt::mgmt_portal& mgmt_portal, + const uhd::rfnoc::sep_addr_pair_t& addrs, + const uhd::rfnoc::sep_id_pair_t& epids, + const uhd::rfnoc::sw_buff_t pyld_buff_fmt, + const uhd::rfnoc::sw_buff_t mdata_buff_fmt, + const uhd::device_addr_t& xport_args); + +private: + uhd::device_addr_t _mb_args; + uhd::rpc_client::sptr _rpc; + xport::mpmd_link_if_mgr::uptr _link_if_mgr; + uhd::rfnoc::device_id_t _remote_device_id; + std::map<uhd::rfnoc::device_id_t, size_t> _local_device_id_map; + std::unordered_map<uhd::rfnoc::device_id_t, uhd::transport::adapter_id_t> _adapter_map; + std::map<std::string, uhd::rfnoc::clock_iface::sptr> _clock_ifaces; +}; + +}} /* namespace uhd::mpmd */ + +#endif /* INCLUDED_MPMD_MB_IFACE_HPP */ diff --git a/host/lib/usrp/mpmd/mpmd_mboard_impl.cpp b/host/lib/usrp/mpmd/mpmd_mboard_impl.cpp index 83b47b485..5c8fd5485 100644 --- a/host/lib/usrp/mpmd/mpmd_mboard_impl.cpp +++ b/host/lib/usrp/mpmd/mpmd_mboard_impl.cpp @@ -6,11 +6,13 @@ // #include "mpmd_impl.hpp" +#include "mpmd_mb_iface.hpp" #include <uhd/transport/udp_simple.hpp> #include <uhd/utils/log.hpp> #include <uhd/utils/safe_call.hpp> #include <chrono> #include <thread> +#include <memory> namespace { /************************************************************************* @@ -177,8 +179,8 @@ boost::optional<device_addr_t> mpmd_mboard_impl::is_device_reachable( { UHD_LOG_TRACE( "MPMD", "Checking accessibility of device `" << device_addr.to_string() << "'"); - UHD_ASSERT_THROW(device_addr.has_key(xport::MGMT_ADDR_KEY)); - const std::string rpc_addr = device_addr.get(xport::MGMT_ADDR_KEY); + UHD_ASSERT_THROW(device_addr.has_key(MGMT_ADDR_KEY)); + const std::string rpc_addr = device_addr.get(MGMT_ADDR_KEY); const size_t rpc_port = device_addr.cast<size_t>(mpmd_impl::MPM_RPC_PORT_KEY, mpmd_impl::MPM_RPC_PORT); // 1) Read back device info @@ -258,15 +260,10 @@ mpmd_mboard_impl::mpmd_mboard_impl( const device_addr_t& mb_args_, const std::string& rpc_server_addr) : mb_args(mb_args_) , rpc(make_mpm_rpc_client(rpc_server_addr, mb_args_)) - , num_xbars(rpc->request<size_t>("get_num_xbars")) , _claim_rpc(make_mpm_rpc_client(rpc_server_addr, mb_args, MPMD_CLAIMER_RPC_TIMEOUT)) - // xbar_local_addrs is not yet valid after this! - , xbar_local_addrs(num_xbars, 0xFF) - , _xport_mgr(xport::mpmd_xport_mgr::make(mb_args)) { UHD_LOGGER_TRACE("MPMD") << "Initializing mboard, connecting to RPC server address: " - << rpc_server_addr << " mboard args: " << mb_args.to_string() - << " number of crossbars: " << num_xbars; + << rpc_server_addr << " mboard args: " << mb_args.to_string(); _claimer_task = claim_device_and_make_task(); if (mb_args_.has_key(MPMD_MEAS_LATENCY_KEY)) { @@ -278,7 +275,7 @@ mpmd_mboard_impl::mpmd_mboard_impl( for (const auto& info_pair : device_info_dict) { device_info[info_pair.first] = info_pair.second; } - UHD_LOGGER_TRACE("MPMD") << "MPM reports device info: " << device_info.to_string(); + UHD_LOG_DEBUG("MPMD", "MPM reports device info: " << device_info.to_string()); /// Get dboard info const auto dboards_info = rpc->request<std::vector<dev_info>>("get_dboard_info"); UHD_ASSERT_THROW(this->dboard_info.size() == 0); @@ -293,19 +290,16 @@ mpmd_mboard_impl::mpmd_mboard_impl( this->dboard_info.push_back(this_db_info); } - for (const std::string& key : mb_args_.keys()) { - if (key.find("recv") != std::string::npos) - recv_args[key] = mb_args_[key]; - if (key.find("send") != std::string::npos) - send_args[key] = mb_args_[key]; - } + // Initialize mb_iface and mb_controller + mb_iface = std::make_unique<mpmd_mb_iface>(mb_args, rpc); + mb_ctrl = std::make_shared<rfnoc::mpmd_mb_controller>(rpc, device_info); } mpmd_mboard_impl::~mpmd_mboard_impl() { // Destroy the claimer task to avoid spurious asynchronous reclaim call // after the unclaim. - UHD_SAFE_CALL(dump_logs(); _claimer_task.reset(); _xport_mgr.reset(); + UHD_SAFE_CALL(dump_logs(); _claimer_task.reset(); if (not rpc->request_with_token<bool>("unclaim")) { UHD_LOG_WARNING("MPMD", "Failure to ack unclaim!"); }); @@ -317,79 +311,15 @@ mpmd_mboard_impl::~mpmd_mboard_impl() void mpmd_mboard_impl::init() { init_device(rpc, mb_args); - // RFNoC block clocks are now on. Noc-IDs can be read back. + mb_iface->init(); } /***************************************************************************** * API ****************************************************************************/ -void mpmd_mboard_impl::set_xbar_local_addr( - const size_t xbar_index, const size_t local_addr) -{ - UHD_ASSERT_THROW( - rpc->request_with_token<bool>("set_xbar_local_addr", xbar_index, local_addr)); - UHD_ASSERT_THROW(xbar_index < xbar_local_addrs.size()); - xbar_local_addrs.at(xbar_index) = local_addr; -} - -uhd::both_xports_t mpmd_mboard_impl::make_transport(const sid_t& sid, - usrp::device3_impl::xport_type_t xport_type, - const uhd::device_addr_t& xport_args) -{ - const std::string xport_type_str = [xport_type]() { - switch (xport_type) { - case mpmd_impl::CTRL: - return "CTRL"; - case mpmd_impl::ASYNC_MSG: - return "ASYNC_MSG"; - case mpmd_impl::RX_DATA: - return "RX_DATA"; - case mpmd_impl::TX_DATA: - return "TX_DATA"; - default: - UHD_THROW_INVALID_CODE_PATH(); - }; - }(); - - UHD_LOGGER_TRACE("MPMD") << __func__ - << "(): Creating new transport of type: " << xport_type_str; - - using namespace uhd::mpmd::xport; - const auto xport_info_list = - rpc->request_with_token<mpmd_xport_mgr::xport_info_list_t>( - "request_xport", sid.get_dst(), sid.get_src(), xport_type_str); - UHD_LOGGER_TRACE("MPMD") << __func__ << "(): request_xport() gave us " - << xport_info_list.size() << " option(s)."; - if (xport_info_list.empty()) { - UHD_LOG_ERROR("MPMD", "No viable transport path found!"); - throw uhd::runtime_error("No viable transport path found!"); - } - - xport::mpmd_xport_mgr::xport_info_t xport_info_out; - auto xports = _xport_mgr->make_transport( - xport_info_list, xport_type, xport_args, xport_info_out); - - if (not rpc->request_with_token<bool>("commit_xport", xport_info_out)) { - UHD_LOG_ERROR("MPMD", "Failed to create UDP transport!"); - throw uhd::runtime_error("commit_xport() failed!"); - } - - return xports; -} - -size_t mpmd_mboard_impl::get_mtu(const uhd::direction_t dir) const -{ - return _xport_mgr->get_mtu(dir); -} - -uhd::device_addr_t mpmd_mboard_impl::get_rx_hints() const -{ - return recv_args; -} - -uhd::device_addr_t mpmd_mboard_impl::get_tx_hints() const +uhd::rfnoc::mb_iface& mpmd_mboard_impl::get_mb_iface() { - return send_args; + return *(mb_iface.get()); } /***************************************************************************** diff --git a/host/lib/usrp/mpmd/mpmd_prop_tree.cpp b/host/lib/usrp/mpmd/mpmd_prop_tree.cpp index 51b88b0e6..dc559f91b 100644 --- a/host/lib/usrp/mpmd/mpmd_prop_tree.cpp +++ b/host/lib/usrp/mpmd/mpmd_prop_tree.cpp @@ -194,16 +194,4 @@ void mpmd_impl::init_property_tree( return _get_component_info(comp_name, mb); }); // Done adding component to property tree } - - /*** MTUs ***********************************************************/ - tree->create<size_t>(mb_path / "mtu/recv") - .add_coerced_subscriber([](const size_t) { - throw uhd::runtime_error("Attempting to write read-only value (MTU)!"); - }) - .set_publisher([mb]() { return mb->get_mtu(uhd::RX_DIRECTION); }); - tree->create<size_t>(mb_path / "mtu/send") - .add_coerced_subscriber([](const size_t) { - throw uhd::runtime_error("Attempting to write read-only value (MTU)!"); - }) - .set_publisher([mb]() { return mb->get_mtu(uhd::TX_DIRECTION); }); } diff --git a/host/lib/usrp/mpmd/mpmd_xport.cpp b/host/lib/usrp/mpmd/mpmd_xport.cpp deleted file mode 100644 index 3ef6a074c..000000000 --- a/host/lib/usrp/mpmd/mpmd_xport.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// -// Copyright 2017 Ettus Research, National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -// make_transport logic for mpmd_impl. Note that mpmd_xport_mgr.* has most of -// the actual transport logic, this for transport-related APIs. - -#include "mpmd_impl.hpp" -#include "mpmd_xport_mgr.hpp" - -using namespace uhd; -using namespace uhd::mpmd; - -uhd::device_addr_t mpmd_impl::get_rx_hints(size_t mb_index) -{ - return _mb.at(mb_index)->get_rx_hints(); -} - -uhd::device_addr_t mpmd_impl::get_tx_hints(size_t mb_index) -{ - return _mb.at(mb_index)->get_tx_hints(); -} - -size_t mpmd_impl::identify_mboard_by_xbar_addr(const size_t xbar_addr) const -{ - for (size_t mb_index = 0; mb_index < _mb.size(); mb_index++) { - for (size_t xbar_index = 0; xbar_index < _mb[mb_index]->num_xbars; xbar_index++) { - if (_mb.at(mb_index)->get_xbar_local_addr(xbar_index) == xbar_addr) { - return mb_index; - } - } - } - throw uhd::lookup_error( - str(boost::format("Cannot identify mboard for crossbar address %d") % xbar_addr)); -} - -both_xports_t mpmd_impl::make_transport(const sid_t& dst_address, - usrp::device3_impl::xport_type_t xport_type, - const uhd::device_addr_t& args) -{ - const size_t mb_index = identify_mboard_by_xbar_addr(dst_address.get_dst_addr()); - - const sid_t sid(0, - 0, // Not actually an address, more of an 'ignore me' value - dst_address.get_dst_addr(), - dst_address.get_dst_endpoint()); - UHD_LOGGER_TRACE("MPMD") << "Creating new transport to mboard: " << mb_index - << " SID: " << sid.to_pp_string_hex() - << " User-defined xport args: " << args.to_string(); - - both_xports_t xports = _mb[mb_index]->make_transport(sid, xport_type, args); - UHD_LOGGER_TRACE("MPMD") << "xport info: send_sid==" - << xports.send_sid.to_pp_string_hex() - << " recv_sid==" << xports.recv_sid.to_pp_string_hex() - << " endianness==" - << (xports.endianness == uhd::ENDIANNESS_BIG ? "BE" : "LE") - << " recv_buff_size==" << xports.recv_buff_size - << " send_buff_size==" << xports.send_buff_size; - return xports; -} diff --git a/host/lib/usrp/mpmd/mpmd_xport_ctrl_base.hpp b/host/lib/usrp/mpmd/mpmd_xport_ctrl_base.hpp deleted file mode 100644 index a7fff9262..000000000 --- a/host/lib/usrp/mpmd/mpmd_xport_ctrl_base.hpp +++ /dev/null @@ -1,44 +0,0 @@ -// -// Copyright 2017 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#ifndef INCLUDED_MPMD_XPORT_CTRL_BASE_HPP -#define INCLUDED_MPMD_XPORT_CTRL_BASE_HPP - -#include "../device3/device3_impl.hpp" -#include "mpmd_xport_mgr.hpp" -#include <uhd/types/device_addr.hpp> -#include <memory> - -namespace uhd { namespace mpmd { namespace xport { - -/*! Transport manager implementation base - */ -class mpmd_xport_ctrl_base -{ -public: - using uptr = std::unique_ptr<mpmd_xport_ctrl_base>; - virtual ~mpmd_xport_ctrl_base() {} - - /*! This is the final step of a make_transport() sequence - * - * \param xport_info Contains all necessary transport info. The - * implementation may update this! - * \param xport_type CTRL, ASYNC_MSG, ... (see xport_type_t) - * \param xport_args Additional arguments. These can come from the user. - */ - virtual both_xports_t make_transport(mpmd_xport_mgr::xport_info_t& xport_info, - const usrp::device3_impl::xport_type_t xport_type, - const uhd::device_addr_t& xport_args) = 0; - - //! Assert if an xport_info is even valid/feasible/available - virtual bool is_valid(const mpmd_xport_mgr::xport_info_t& xport_info) const = 0; - - virtual size_t get_mtu(const uhd::direction_t dir) const = 0; -}; - -}}} /* namespace uhd::mpmd::xport */ - -#endif /* INCLUDED_MPMD_XPORT_CTRL_BASE_HPP */ diff --git a/host/lib/usrp/mpmd/mpmd_xport_ctrl_liberio.cpp b/host/lib/usrp/mpmd/mpmd_xport_ctrl_liberio.cpp deleted file mode 100644 index c53eb97a1..000000000 --- a/host/lib/usrp/mpmd/mpmd_xport_ctrl_liberio.cpp +++ /dev/null @@ -1,194 +0,0 @@ -// -// Copyright 2017 Ettus Research, National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "mpmd_xport_ctrl_liberio.hpp" -#include "../transport/liberio_zero_copy.hpp" -#include <uhd/transport/udp_zero_copy.hpp> -#include <uhd/utils/byteswap.hpp> -#include <uhd/rfnoc/constants.hpp> - -using namespace uhd; -using namespace uhd::mpmd::xport; - -namespace { - -//! Max frame size of a control packet in bytes -const size_t LIBERIO_CTRL_FRAME_MAX_SIZE = 128; -//! Max frame size of an async message packet in bytes -const size_t LIBERIO_ASYNC_FRAME_MAX_SIZE = 256; -//! Max frame size of a flow control packet in bytes -const size_t LIBERIO_FC_FRAME_MAX_SIZE = 64; -//! The max MTU will be this number times the page size -const size_t LIBERIO_PAGES_PER_BUF = 2; -//! Number of descriptors that liberio allocates (receive) -const size_t LIBERIO_NUM_RECV_FRAMES = 128; -//! Number of descriptors that liberio allocates (send) -const size_t LIBERIO_NUM_SEND_FRAMES = 128; - -uint32_t extract_sid_from_pkt(void* pkt, size_t) -{ - return uhd::sid_t(uhd::wtohx(static_cast<const uint32_t*>(pkt)[1])).get_dst(); -} - -} // namespace - -mpmd_xport_ctrl_liberio::mpmd_xport_ctrl_liberio(const uhd::device_addr_t& mb_args) - : _mb_args(mb_args) - , _recv_args(filter_args(mb_args, "recv")) - , _send_args(filter_args(mb_args, "send")) -{ - // nop -} - - -uhd::both_xports_t mpmd_xport_ctrl_liberio::make_transport( - mpmd_xport_mgr::xport_info_t& xport_info, - const usrp::device3_impl::xport_type_t xport_type, - const uhd::device_addr_t& xport_args_) -{ - auto xport_args = (xport_type == usrp::device3_impl::CTRL) ? - uhd::device_addr_t() : xport_args_; - - // Constrain by this transport's MTU and the MTU passed in - const size_t send_mtu = std::min(get_mtu(uhd::TX_DIRECTION), - xport_args.cast<size_t>("mtu", get_mtu(uhd::TX_DIRECTION))); - const size_t recv_mtu = std::min(get_mtu(uhd::RX_DIRECTION), - xport_args.cast<size_t>("mtu", get_mtu(uhd::RX_DIRECTION))); - size_t send_frame_size = xport_args.cast<size_t>("send_frame_size", send_mtu); - size_t recv_frame_size = xport_args.cast<size_t>("recv_frame_size", recv_mtu); - - // Check any user supplied frame sizes and constrain to MTU - if (xport_args.has_key("send_frame_size") and - xport_type == usrp::device3_impl::TX_DATA) - { - if (send_frame_size > send_mtu) { - UHD_LOGGER_WARNING("MPMD") - << boost::format("Requested send_frame_size of %d exceeds the " - "maximum supported by the hardware. Using %d.") - % send_frame_size % send_mtu; - send_frame_size = send_mtu; - } - } - if (xport_args.has_key("recv_frame_size") and - xport_type == usrp::device3_impl::RX_DATA) - { - size_t recv_frame_size = xport_args.cast<size_t>("recv_frame_size", recv_mtu); - if (recv_frame_size > recv_mtu) { - UHD_LOGGER_WARNING("MPMD") - << boost::format("Requested recv_frame_size of %d exceeds the " - "maximum supported by the hardware. Using %d.") - % recv_frame_size % recv_mtu; - recv_frame_size = recv_mtu; - } - } - - transport::zero_copy_xport_params default_buff_args; - /* default ones for RX / TX, override below */ - - default_buff_args.send_frame_size = send_mtu; - default_buff_args.recv_frame_size = recv_mtu; - default_buff_args.num_recv_frames = LIBERIO_NUM_RECV_FRAMES; - default_buff_args.num_send_frames = LIBERIO_NUM_SEND_FRAMES; - - if (xport_type == usrp::device3_impl::CTRL) { - default_buff_args.send_frame_size = LIBERIO_CTRL_FRAME_MAX_SIZE; - default_buff_args.recv_frame_size = LIBERIO_CTRL_FRAME_MAX_SIZE; - default_buff_args.num_recv_frames = uhd::rfnoc::CMD_FIFO_SIZE / - uhd::rfnoc::MAX_CMD_PKT_SIZE; - default_buff_args.num_send_frames = uhd::rfnoc::CMD_FIFO_SIZE / - uhd::rfnoc::MAX_CMD_PKT_SIZE; - } else if (xport_type == usrp::device3_impl::ASYNC_MSG) { - default_buff_args.send_frame_size = LIBERIO_ASYNC_FRAME_MAX_SIZE; - default_buff_args.recv_frame_size = LIBERIO_ASYNC_FRAME_MAX_SIZE; - } else if (xport_type == usrp::device3_impl::RX_DATA) { - default_buff_args.recv_frame_size = recv_frame_size; - default_buff_args.send_frame_size = LIBERIO_FC_FRAME_MAX_SIZE; - } else { - default_buff_args.recv_frame_size = LIBERIO_FC_FRAME_MAX_SIZE; - default_buff_args.send_frame_size = send_frame_size; - } - - const std::string tx_dev = xport_info["tx_dev"]; - const std::string rx_dev = xport_info["rx_dev"]; - - both_xports_t xports; - xports.lossless = true; - xports.endianness = uhd::ENDIANNESS_LITTLE; - xports.send_sid = sid_t(xport_info["send_sid"]); - xports.recv_sid = xports.send_sid.reversed(); - - if (xport_type == usrp::device3_impl::CTRL) { - UHD_ASSERT_THROW(xport_info["muxed"] == "True"); - if (not _ctrl_dma_xport) { - _ctrl_dma_xport = - make_muxed_liberio_xport(tx_dev, rx_dev, default_buff_args, - uhd::rfnoc::MAX_NUM_BLOCKS * uhd::rfnoc::MAX_NUM_PORTS); - } - - UHD_LOGGER_TRACE("MPMD") - << "Making (muxed) stream with num " << xports.recv_sid.get_dst(); - xports.recv = _ctrl_dma_xport->make_stream(xports.recv_sid.get_dst()); - } else if (xport_type == usrp::device3_impl::ASYNC_MSG) { - UHD_ASSERT_THROW(xport_info["muxed"] == "True"); - if (not _async_msg_dma_xport) { - _async_msg_dma_xport = - make_muxed_liberio_xport(tx_dev, rx_dev, default_buff_args, - uhd::rfnoc::MAX_NUM_BLOCKS * uhd::rfnoc::MAX_NUM_PORTS); - } - - UHD_LOGGER_TRACE("MPMD") - << "making (muxed) stream with num " << xports.recv_sid.get_dst(); - xports.recv = _async_msg_dma_xport->make_stream(xports.recv_sid.get_dst()); - } else { - // Create muxed transport in case of less DMA channels - if (xport_info["muxed"] == "True") { - if (not _data_dma_xport) { - _data_dma_xport = - make_muxed_liberio_xport(tx_dev, rx_dev, default_buff_args, - uhd::rfnoc::MAX_NUM_BLOCKS * uhd::rfnoc::MAX_NUM_PORTS); - } - - UHD_LOGGER_TRACE("MPMD") - << "Making (muxed) stream with num " << xports.recv_sid.get_dst(); - xports.recv = _data_dma_xport->make_stream(xports.recv_sid.get_dst()); - } - else { - xports.recv = - transport::liberio_zero_copy::make(tx_dev, rx_dev, default_buff_args); - } - } - - // Finish both_xports_t object and return: - xports.recv_buff_size = default_buff_args.recv_frame_size * - default_buff_args.num_recv_frames; - xports.send_buff_size = default_buff_args.send_frame_size * - default_buff_args.num_send_frames; - xports.send = xports.recv; - return xports; -} - -bool mpmd_xport_ctrl_liberio::is_valid( - const mpmd_xport_mgr::xport_info_t& xport_info) const -{ - return xport_info.at("type") == "liberio"; -} - -size_t mpmd_xport_ctrl_liberio::get_mtu(const uhd::direction_t /*dir*/) const -{ - return LIBERIO_PAGES_PER_BUF * getpagesize(); -} - -uhd::transport::muxed_zero_copy_if::sptr -mpmd_xport_ctrl_liberio::make_muxed_liberio_xport(const std::string& tx_dev, - const std::string& rx_dev, - const uhd::transport::zero_copy_xport_params& buff_args, - const size_t max_muxed_ports) -{ - auto base_xport = transport::liberio_zero_copy::make(tx_dev, rx_dev, buff_args); - - return uhd::transport::muxed_zero_copy_if::make( - base_xport, extract_sid_from_pkt, max_muxed_ports); -} diff --git a/host/lib/usrp/mpmd/mpmd_xport_ctrl_liberio.hpp b/host/lib/usrp/mpmd/mpmd_xport_ctrl_liberio.hpp deleted file mode 100644 index 36f02fe46..000000000 --- a/host/lib/usrp/mpmd/mpmd_xport_ctrl_liberio.hpp +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright 2017 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#ifndef INCLUDED_MPMD_XPORT_ctrl_liberio_HPP -#define INCLUDED_MPMD_XPORT_ctrl_liberio_HPP - -#include "../device3/device3_impl.hpp" -#include "mpmd_xport_ctrl_base.hpp" -#include <uhd/transport/muxed_zero_copy_if.hpp> -#include <uhd/types/device_addr.hpp> - -namespace uhd { namespace mpmd { namespace xport { - -/*! Liberio transport manager - */ -class mpmd_xport_ctrl_liberio : public mpmd_xport_ctrl_base -{ -public: - mpmd_xport_ctrl_liberio(const uhd::device_addr_t& mb_args); - - /*! Open DMA interface to kernel (and thus to FPGA DMA engine) - */ - both_xports_t make_transport(mpmd_xport_mgr::xport_info_t& xport_info, - const usrp::device3_impl::xport_type_t xport_type, - const uhd::device_addr_t& xport_args); - - bool is_valid(const mpmd_xport_mgr::xport_info_t& xport_info) const; - - size_t get_mtu(const uhd::direction_t dir) const; - -private: - /*! Create a muxed liberio transport for control packets */ - uhd::transport::muxed_zero_copy_if::sptr make_muxed_liberio_xport( - const std::string& tx_dev, - const std::string& rx_dev, - const uhd::transport::zero_copy_xport_params& buff_args, - const size_t max_muxed_ports); - - const uhd::device_addr_t _mb_args; - const uhd::dict<std::string, std::string> _recv_args; - const uhd::dict<std::string, std::string> _send_args; - - //! Control transport for one liberio connection - uhd::transport::muxed_zero_copy_if::sptr _ctrl_dma_xport; - //! Data transport for one liberio connection - uhd::transport::muxed_zero_copy_if::sptr _data_dma_xport; - //! Control transport for one liberio connection - uhd::transport::muxed_zero_copy_if::sptr _async_msg_dma_xport; -}; - -}}} /* namespace uhd::mpmd::xport */ - -#endif /* INCLUDED_MPMD_XPORT_ctrl_liberio_HPP */ diff --git a/host/lib/usrp/mpmd/mpmd_xport_ctrl_udp.cpp b/host/lib/usrp/mpmd/mpmd_xport_ctrl_udp.cpp deleted file mode 100644 index 20b94899c..000000000 --- a/host/lib/usrp/mpmd/mpmd_xport_ctrl_udp.cpp +++ /dev/null @@ -1,265 +0,0 @@ -// -// Copyright 2017 Ettus Research, National Instruments Company -// Copyright 2019 Ettus Research, National Instruments Brand -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "mpmd_xport_ctrl_udp.hpp" -#include "mpmd_impl.hpp" -#include "mpmd_xport_mgr.hpp" -#include <uhd/transport/udp_constants.hpp> -#include <uhd/transport/udp_simple.hpp> -#include <uhd/transport/udp_zero_copy.hpp> - - -using namespace uhd; -using namespace uhd::mpmd::xport; - -namespace { - -//! Maximum CHDR packet size in bytes -const size_t MPMD_10GE_DATA_FRAME_MAX_SIZE = 4000; - -//! Default number of send frames -const size_t MPMD_UDP_DEFAULT_NUM_SEND_FRAMES = 1; -//! Default number of recv frames -const size_t MPMD_UDP_DEFAULT_NUM_RECV_FRAMES = 1; -//! Default message frame size -const size_t MPMD_UDP_MSG_FRAME_SIZE = 256; -//! Default 1GbE send frame size -const size_t MPMD_UDP_1GE_DEFAULT_SEND_FRAME_SIZE = 1472; -//! Default 1GbE receive frame size -const size_t MPMD_UDP_1GE_DEFAULT_RECV_FRAME_SIZE = 1472; -//! Default 10GbE send frame size -const size_t MPMD_UDP_10GE_DEFAULT_SEND_FRAME_SIZE = 4000; -//! Default 10GbE receive frame size -const size_t MPMD_UDP_10GE_DEFAULT_RECV_FRAME_SIZE = 4000; - -//! -const double MPMD_BUFFER_DEPTH = 50.0e-3; // s -//! For MTU discovery, the time we wait for a packet before calling it -// oversized (seconds). -const double MPMD_MTU_DISCOVERY_TIMEOUT = 0.02; - -// TODO: move these to appropriate header file for all other devices -const double MAX_RATE_1GIGE = 1e9 / 8; // byte/s -const double MAX_RATE_10GIGE = 10e9 / 8; // byte/s - -std::vector<std::string> get_addrs_from_mb_args(const uhd::device_addr_t& mb_args) -{ - // mb_args must always include addr - if (not mb_args.has_key(FIRST_ADDR_KEY)) { - throw uhd::runtime_error( - "The " + FIRST_ADDR_KEY - + " key must be specified in " - "device args to create an Ethernet transport to an RFNoC block"); - } - std::vector<std::string> addrs{mb_args[FIRST_ADDR_KEY]}; - if (mb_args.has_key(SECOND_ADDR_KEY)) { - addrs.push_back(mb_args[SECOND_ADDR_KEY]); - } - return addrs; -} - -/*! Do a binary search to discover MTU - * - * Uses the MPM echo service to figure out MTU. We simply send a bunch of - * packets and see if they come back until we converged on the path MTU. - * The end result must lie between \p min_frame_size and \p max_frame_size. - * - * \param address IP address - * \param port UDP port (yeah it's a string!) - * \param min_frame_size Minimum frame size, initialize algorithm to start - * with this value - * \param max_frame_size Maximum frame size, initialize algorithm to start - * with this value - * \param echo_timeout Timeout value in seconds. For frame sizes that - * exceed the MTU, we don't expect a response, and this - * is the amount of time we'll wait before we assume - * the frame size exceeds the MTU. - */ -size_t discover_mtu(const std::string& address, - const std::string& port, - size_t min_frame_size, - size_t max_frame_size, - const double echo_timeout = 0.020) -{ - const size_t echo_prefix_offset = uhd::mpmd::mpmd_impl::MPM_ECHO_CMD.size(); - const size_t mtu_hdr_len = echo_prefix_offset + 10; - UHD_ASSERT_THROW(min_frame_size < max_frame_size); - UHD_ASSERT_THROW(min_frame_size % 4 == 0); - UHD_ASSERT_THROW(max_frame_size % 4 == 0); - UHD_ASSERT_THROW(min_frame_size >= echo_prefix_offset + mtu_hdr_len); - using namespace uhd::transport; - // The return port will probably differ from the discovery port, so we - // need a "broadcast" UDP connection; using make_connected() would - // drop packets - udp_simple::sptr udp = udp_simple::make_broadcast(address, port); - std::string send_buf(uhd::mpmd::mpmd_impl::MPM_ECHO_CMD); - send_buf.resize(max_frame_size, '#'); - UHD_ASSERT_THROW(send_buf.size() == max_frame_size); - std::vector<uint8_t> recv_buf; - recv_buf.resize(max_frame_size, ' '); - - // Little helper to check returned packets match the sent ones - auto require_bufs_match = [&recv_buf, &send_buf, mtu_hdr_len](const size_t len) { - if (len < mtu_hdr_len - or std::memcmp((void*)&recv_buf[0], (void*)&send_buf[0], mtu_hdr_len) != 0) { - throw uhd::runtime_error("Unexpected content of MTU " - "discovery return packet!"); - } - }; - UHD_LOG_TRACE("MPMD", "Determining UDP MTU... "); - size_t seq_no = 0; - while (min_frame_size < max_frame_size) { - // Only test multiples of 4 bytes! - const size_t test_frame_size = (max_frame_size / 2 + min_frame_size / 2 + 3) - & ~size_t(3); - // Encode sequence number and current size in the string, makes it - // easy to debug in code or Wireshark. Is also used for identifying - // response packets. - std::sprintf( - &send_buf[echo_prefix_offset], ";%04lu,%04lu", seq_no++, test_frame_size); - UHD_LOG_TRACE("MPMD", "Testing frame size " << test_frame_size); - udp->send(boost::asio::buffer(&send_buf[0], test_frame_size)); - - const size_t len = udp->recv(boost::asio::buffer(recv_buf), echo_timeout); - if (len == 0) { - // Nothing received, so this is probably too big - max_frame_size = test_frame_size - 4; - } else if (len >= test_frame_size) { - // Size went through, so bump the minimum - require_bufs_match(len); - min_frame_size = test_frame_size; - } else if (len < test_frame_size) { - // This is an odd case. Something must have snipped the packet - // on the way back. Still, we'll just back off and try - // something smaller. - UHD_LOG_DEBUG("MPMD", "Unexpected packet truncation during MTU discovery."); - require_bufs_match(len); - max_frame_size = len; - } - } - UHD_LOG_DEBUG("MPMD", "Path MTU for address " << address << ": " << min_frame_size); - return min_frame_size; -} - -} // namespace - - -mpmd_xport_ctrl_udp::mpmd_xport_ctrl_udp(const uhd::device_addr_t& mb_args) - : _mb_args(mb_args) - , _recv_args(filter_args(mb_args, "recv")) - , _send_args(filter_args(mb_args, "send")) - , _available_addrs(get_addrs_from_mb_args(mb_args)) - , _mtu(MPMD_10GE_DATA_FRAME_MAX_SIZE) -{ - const std::string mpm_discovery_port = _mb_args.get( - mpmd_impl::MPM_DISCOVERY_PORT_KEY, std::to_string(mpmd_impl::MPM_DISCOVERY_PORT)); - auto discover_mtu_for_ip = [mpm_discovery_port](const std::string& ip_addr) { - return discover_mtu(ip_addr, - mpm_discovery_port, - IP_PROTOCOL_MIN_MTU_SIZE - IP_PROTOCOL_UDP_PLUS_IP_HEADER, - MPMD_10GE_DATA_FRAME_MAX_SIZE, - MPMD_MTU_DISCOVERY_TIMEOUT); - }; - - for (const auto& ip_addr : _available_addrs) { - _mtu = std::min(_mtu, discover_mtu_for_ip(ip_addr)); - } -} - -uhd::both_xports_t mpmd_xport_ctrl_udp::make_transport( - mpmd_xport_mgr::xport_info_t& xport_info, - const usrp::device3_impl::xport_type_t xport_type, - const uhd::device_addr_t& xport_args) -{ - - double link_speed = MAX_RATE_1GIGE; - if (xport_info.count("link_speed") == 0) { - UHD_LOG_WARNING("MPMD", - "Could not determine link speed; using 1GibE max speed of " - << MAX_RATE_1GIGE); - } else { - link_speed = xport_info.at("link_speed") == "10000" ? MAX_RATE_10GIGE - : MAX_RATE_1GIGE; - } - - // Constrain by this transport's MTU and the MTU in the xport_args - const size_t send_mtu = std::min(get_mtu(uhd::TX_DIRECTION), - xport_args.cast<size_t>("mtu", get_mtu(uhd::TX_DIRECTION))); - const size_t recv_mtu = std::min(get_mtu(uhd::RX_DIRECTION), - xport_args.cast<size_t>("mtu", get_mtu(uhd::RX_DIRECTION))); - - // Create actual UDP transport - transport::zero_copy_xport_params default_buff_args; - default_buff_args.num_send_frames = MPMD_UDP_DEFAULT_NUM_SEND_FRAMES; - default_buff_args.num_recv_frames = MPMD_UDP_DEFAULT_NUM_RECV_FRAMES; - default_buff_args.recv_frame_size = MPMD_UDP_MSG_FRAME_SIZE; - default_buff_args.send_frame_size = MPMD_UDP_MSG_FRAME_SIZE; - default_buff_args.recv_buff_size = link_speed * MPMD_BUFFER_DEPTH; - default_buff_args.send_buff_size = link_speed * MPMD_BUFFER_DEPTH; - if (xport_type == usrp::device3_impl::CTRL) { - default_buff_args.num_recv_frames = - uhd::rfnoc::CMD_FIFO_SIZE / uhd::rfnoc::MAX_CMD_PKT_SIZE; - } else if (xport_type == usrp::device3_impl::TX_DATA) { - const size_t default_frame_size = (link_speed == MAX_RATE_10GIGE ? - MPMD_UDP_10GE_DEFAULT_SEND_FRAME_SIZE : - MPMD_UDP_1GE_DEFAULT_SEND_FRAME_SIZE); - default_buff_args.send_frame_size = - xport_args.cast<size_t>("send_frame_size", - std::min(default_frame_size, send_mtu)); - default_buff_args.num_send_frames = - xport_args.cast<size_t>("num_send_frames", - default_buff_args.num_send_frames); - default_buff_args.send_buff_size = - xport_args.cast<size_t>("send_buff_size", - default_buff_args.send_buff_size); - } else if (xport_type == usrp::device3_impl::RX_DATA) { - const size_t default_frame_size = (link_speed == MAX_RATE_10GIGE ? - MPMD_UDP_10GE_DEFAULT_RECV_FRAME_SIZE : - MPMD_UDP_1GE_DEFAULT_RECV_FRAME_SIZE); - default_buff_args.recv_frame_size = - xport_args.cast<size_t>("recv_frame_size", - std::min(default_frame_size, recv_mtu)); - default_buff_args.num_recv_frames = - xport_args.cast<size_t>("num_recv_frames", - default_buff_args.num_recv_frames); - default_buff_args.recv_buff_size = - xport_args.cast<size_t>("recv_buff_size", - default_buff_args.recv_buff_size); - } - transport::udp_zero_copy::buff_params buff_params; - auto recv = transport::udp_zero_copy::make(xport_info["ipv4"], - xport_info["port"], - default_buff_args, - buff_params); - const uint16_t port = recv->get_local_port(); - const std::string src_ip_addr = recv->get_local_addr(); - xport_info["src_port"] = std::to_string(port); - xport_info["src_ipv4"] = src_ip_addr; - - // Create both_xports_t object and finish: - both_xports_t xports; - xports.endianness = uhd::ENDIANNESS_BIG; - xports.send_sid = sid_t(xport_info["send_sid"]); - xports.recv_sid = xports.send_sid.reversed(); - xports.recv_buff_size = buff_params.recv_buff_size; - xports.send_buff_size = buff_params.send_buff_size; - xports.recv = recv; // Note: This is a type cast! - xports.send = recv; // This too - return xports; -} - -bool mpmd_xport_ctrl_udp::is_valid(const mpmd_xport_mgr::xport_info_t& xport_info) const -{ - return std::find( - _available_addrs.cbegin(), _available_addrs.cend(), xport_info.at("ipv4")) - != _available_addrs.cend(); -} - -size_t mpmd_xport_ctrl_udp::get_mtu(const uhd::direction_t /*dir*/) const -{ - return _mtu; -} diff --git a/host/lib/usrp/mpmd/mpmd_xport_ctrl_udp.hpp b/host/lib/usrp/mpmd/mpmd_xport_ctrl_udp.hpp deleted file mode 100644 index 86301bb2a..000000000 --- a/host/lib/usrp/mpmd/mpmd_xport_ctrl_udp.hpp +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright 2017 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#ifndef INCLUDED_MPMD_XPORT_ctrl_udp_HPP -#define INCLUDED_MPMD_XPORT_ctrl_udp_HPP - -#include "../device3/device3_impl.hpp" -#include "mpmd_xport_ctrl_base.hpp" -#include <uhd/types/device_addr.hpp> - -namespace uhd { namespace mpmd { namespace xport { - -/*! UDP transport manager - * - * Opens UDP sockets - */ -class mpmd_xport_ctrl_udp : public mpmd_xport_ctrl_base -{ -public: - mpmd_xport_ctrl_udp(const uhd::device_addr_t& mb_args); - - both_xports_t make_transport(mpmd_xport_mgr::xport_info_t& xport_info, - const usrp::device3_impl::xport_type_t xport_type, - const uhd::device_addr_t& xport_args); - - bool is_valid(const mpmd_xport_mgr::xport_info_t& xport_info) const; - - size_t get_mtu(const uhd::direction_t dir) const; - -private: - const uhd::device_addr_t _mb_args; - const uhd::dict<std::string, std::string> _recv_args; - const uhd::dict<std::string, std::string> _send_args; - //! A list of IP addresses we can connect our CHDR connections to - const std::vector<std::string> _available_addrs; - //! MTU - size_t _mtu; -}; - -}}} /* namespace uhd::mpmd::xport */ - -#endif /* INCLUDED_MPMD_XPORT_ctrl_udp_HPP */ diff --git a/host/lib/usrp/mpmd/mpmd_xport_mgr.cpp b/host/lib/usrp/mpmd/mpmd_xport_mgr.cpp deleted file mode 100644 index d3023e3af..000000000 --- a/host/lib/usrp/mpmd/mpmd_xport_mgr.cpp +++ /dev/null @@ -1,173 +0,0 @@ -// -// Copyright 2017 Ettus Research, National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "mpmd_xport_mgr.hpp" -#include "mpmd_impl.hpp" -#include "mpmd_xport_ctrl_base.hpp" -#include "mpmd_xport_ctrl_udp.hpp" -#ifdef HAVE_LIBERIO -# include "mpmd_xport_ctrl_liberio.hpp" -#endif -#ifdef HAVE_DPDK -# include "mpmd_xport_ctrl_dpdk_udp.hpp" -#endif - -uhd::dict<std::string, std::string> uhd::mpmd::xport::filter_args( - const uhd::device_addr_t& args, const std::string& prefix) -{ - uhd::dict<std::string, std::string> filtered_args; - for (const std::string& key : args.keys()) { - if (key.find(prefix) != std::string::npos) { - filtered_args[key] = args[key]; - } - } - - return filtered_args; -} - -using namespace uhd::mpmd::xport; - -class mpmd_xport_mgr_impl : public mpmd_xport_mgr -{ -public: - mpmd_xport_mgr_impl(const uhd::device_addr_t& mb_args) : _mb_args(mb_args) - { - // nop - } - - /************************************************************************** - * API (see mpmd_xport_mgr.hpp) - *************************************************************************/ - uhd::both_xports_t make_transport(const xport_info_list_t& xport_info_list, - const uhd::usrp::device3_impl::xport_type_t xport_type, - const uhd::device_addr_t& xport_args, - xport_info_t& xport_info_out) - { - for (const auto& xport_info : xport_info_list) { - require_xport_mgr(xport_info.at("type")); - } - - // Run our incredibly smart selection algorithm - xport_info_out = select_xport_option(xport_info_list); - const std::string xport_medium = xport_info_out.at("type"); - UHD_LOG_TRACE("MPMD", __func__ << "(): xport medium is " << xport_medium); - - UHD_ASSERT_THROW(_xport_ctrls.count(xport_medium) > 0); - UHD_ASSERT_THROW(_xport_ctrls.at(xport_medium)); - // When we've picked our preferred option, pass it to the transport - // implementation for execution: - return _xport_ctrls.at(xport_medium) - ->make_transport(xport_info_out, xport_type, xport_args); - } - - size_t get_mtu(const uhd::direction_t dir) const - { - if (_xport_ctrls.empty()) { - UHD_LOG_WARNING("MPMD", - "Cannot determine MTU, no transport controls have been " - "established!"); - return 0; - } - - size_t mtu = ~size_t(0); - for (const auto& xport_ctrl_pair : _xport_ctrls) { - mtu = std::min(mtu, xport_ctrl_pair.second->get_mtu(dir)); - } - - return mtu; - } - - -private: - /************************************************************************** - * Private methods / helpers - *************************************************************************/ - /*! Picks a transport option based on available data - * - * \param xport_info_list List of available options, they all need to be - * valid choices. - * - * \returns One element of \p xport_info_list based on a selection - * algorithm. - */ - xport_info_t select_xport_option(const xport_info_list_t& xport_info_list) const - { - for (const auto& xport_info : xport_info_list) { - const std::string xport_medium = xport_info.at("type"); - if (_xport_ctrls.count(xport_medium) != 0 and _xport_ctrls.at(xport_medium) - and _xport_ctrls.at(xport_medium)->is_valid(xport_info)) { - return xport_info; - } - } - - throw uhd::runtime_error( - "Could not select a transport option! " - "Either a transport hint was not specified or the specified " - "hint does not support communication with RFNoC blocks."); - } - - //! Create an instance of an xport manager implementation - // - // \param xport_medium "UDP" or "liberio" - // \param mb_args Device args - mpmd_xport_ctrl_base::uptr make_mgr_impl( - const std::string& xport_medium, const uhd::device_addr_t& mb_args) const - { - if (xport_medium == "UDP") { -#ifdef HAVE_DPDK - if (mb_args.has_key("use_dpdk")) { - return mpmd_xport_ctrl_base::uptr(new mpmd_xport_ctrl_dpdk_udp(mb_args)); - } -#endif - return mpmd_xport_ctrl_base::uptr(new mpmd_xport_ctrl_udp(mb_args)); -#ifdef HAVE_LIBERIO - } else if (xport_medium == "liberio") { - return mpmd_xport_ctrl_base::uptr(new mpmd_xport_ctrl_liberio(mb_args)); -#endif - } else { - UHD_LOG_WARNING( - "MPMD", "Cannot instantiate transport medium " << xport_medium); - return nullptr; - } - } - - //! This will try to make _xport_ctrls contain a valid transport manager - // for \p xport_medium - // - // When this function returns, it will be possible to access - // this->_xport_ctrls[xport_medium]. - // - // \param xport_medium Type of transport, e.g. "UDP", "liberio", ... - // - // \throws uhd::key_error if \p xport_medium is not known or registered - void require_xport_mgr(const std::string& xport_medium) - { - if (_xport_ctrls.count(xport_medium) == 0) { - UHD_LOG_TRACE( - "MPMD", "Instantiating transport manager `" << xport_medium << "'"); - auto mgr_impl = make_mgr_impl(xport_medium, _mb_args); - if (mgr_impl) { - _xport_ctrls[xport_medium] = std::move(mgr_impl); - } - } - } - - /************************************************************************** - * Private attributes - *************************************************************************/ - //! Cache available xport manager implementations - // - // Should only every be populated by require_xport_mgr() - std::unordered_map<std::string, mpmd_xport_ctrl_base::uptr> _xport_ctrls; - - //! Motherboard args, can contain things like 'recv_buff_size' - const uhd::device_addr_t _mb_args; -}; - -mpmd_xport_mgr::uptr mpmd_xport_mgr::make(const uhd::device_addr_t& mb_args) -{ - return mpmd_xport_mgr::uptr(new mpmd_xport_mgr_impl(mb_args)); -} diff --git a/host/lib/usrp/x300/CMakeLists.txt b/host/lib/usrp/x300/CMakeLists.txt index a19a6a4e8..1bd71dab4 100644 --- a/host/lib/usrp/x300/CMakeLists.txt +++ b/host/lib/usrp/x300/CMakeLists.txt @@ -16,23 +16,23 @@ if(ENABLE_X300) LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/x300_claim.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/x300_radio_ctrl_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/x300_radio_control.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_fw_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_fw_uart.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_adc_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_dac_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_eth_mgr.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/x300_io_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_dboard_iface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_clock_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_image_loader.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/x300_mb_controller.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_mb_eeprom_iface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_mb_eeprom.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/x300_mboard_type.hpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_mboard_type.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_pcie_mgr.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/x300_mb_controller.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/x300_mb_iface.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/x300_prop_tree.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cdecode.c ) endif(ENABLE_X300) diff --git a/host/lib/usrp/x300/x300_conn_mgr.hpp b/host/lib/usrp/x300/x300_conn_mgr.hpp index 8aca2eb06..9ad870dfd 100644 --- a/host/lib/usrp/x300/x300_conn_mgr.hpp +++ b/host/lib/usrp/x300/x300_conn_mgr.hpp @@ -8,9 +8,12 @@ #define INCLUDED_X300_CONN_MGR_HPP #include <uhd/transport/if_addrs.hpp> +#include <uhd/types/device_addr.hpp> #include <uhd/types/direction.hpp> #include <uhd/types/wb_iface.hpp> #include <uhd/usrp/mboard_eeprom.hpp> +#include <uhdlib/rfnoc/rfnoc_common.hpp> +#include <uhdlib/transport/links.hpp> #include <string> namespace uhd { namespace usrp { namespace x300 { @@ -28,6 +31,14 @@ public: virtual uhd::wb_iface::sptr get_ctrl_iface() = 0; virtual size_t get_mtu(uhd::direction_t dir) = 0; + + virtual std::vector<uhd::rfnoc::device_id_t> get_local_device_ids() = 0; + + virtual uhd::transport::both_links_t get_links(uhd::transport::link_type_t link_type, + const uhd::rfnoc::device_id_t local_device_id, + const uhd::rfnoc::sep_id_t& local_epid, + const uhd::rfnoc::sep_id_t& remote_epid, + const uhd::device_addr_t& link_args) = 0; }; }}} // namespace uhd::usrp::x300 diff --git a/host/lib/usrp/x300/x300_defaults.hpp b/host/lib/usrp/x300/x300_defaults.hpp index 752298aff..ae10bf243 100644 --- a/host/lib/usrp/x300/x300_defaults.hpp +++ b/host/lib/usrp/x300/x300_defaults.hpp @@ -7,7 +7,7 @@ #ifndef INCLUDED_X300_DEFAULTS_HPP #define INCLUDED_X300_DEFAULTS_HPP -#include "../device3/device3_impl.hpp" +#include <uhd/transport/udp_simple.hpp> //mtu #include <string> #include <vector> diff --git a/host/lib/usrp/x300/x300_eth_mgr.cpp b/host/lib/usrp/x300/x300_eth_mgr.cpp index 3a71080ae..b1d9f40ee 100644 --- a/host/lib/usrp/x300/x300_eth_mgr.cpp +++ b/host/lib/usrp/x300/x300_eth_mgr.cpp @@ -6,21 +6,26 @@ #include "x300_eth_mgr.hpp" #include "x300_claim.hpp" +#include "x300_defaults.hpp" #include "x300_fw_common.h" #include "x300_mb_eeprom.hpp" #include "x300_mb_eeprom_iface.hpp" #include "x300_regs.hpp" #include <uhd/exception.hpp> +#include <uhd/rfnoc/defaults.hpp> #include <uhd/transport/if_addrs.hpp> #include <uhd/transport/udp_constants.hpp> #include <uhd/transport/udp_simple.hpp> #include <uhd/transport/udp_zero_copy.hpp> -#include <uhd/transport/zero_copy_recv_offload.hpp> -#ifdef HAVE_DPDK -# include <uhdlib/transport/dpdk_simple.hpp> -# include <uhdlib/transport/dpdk_zero_copy.hpp> -#endif +#include <uhd/utils/byteswap.hpp> +#include <uhdlib/rfnoc/device_id.hpp> +#include <uhdlib/transport/inline_io_service.hpp> +#include <uhdlib/transport/udp_boost_asio_link.hpp> #include <uhdlib/usrp/cores/i2c_core_100_wb32.hpp> +//#ifdef HAVE_DPDK +//# include <uhdlib/transport/dpdk_simple.hpp> +//# include <uhdlib/transport/dpdk_zero_copy.hpp> +//#endif #include <boost/asio.hpp> #include <string> @@ -29,6 +34,7 @@ uhd::wb_iface::sptr x300_make_ctrl_iface_enet( using namespace uhd; using namespace uhd::usrp; +using namespace uhd::rfnoc; using namespace uhd::transport; using namespace uhd::usrp::x300; namespace asio = boost::asio; @@ -47,12 +53,12 @@ constexpr size_t ETH_DATA_NUM_FRAMES = 32; constexpr size_t ETH_MSG_FRAME_SIZE = uhd::transport::udp_simple::mtu; // bytes constexpr size_t MAX_RATE_10GIGE = (size_t)( // bytes/s 10e9 / 8 * // wire speed multiplied by percentage of packets that is sample data - (float(x300::DATA_FRAME_MAX_SIZE - uhd::usrp::DEVICE3_TX_MAX_HDR_LEN) + (float(x300::DATA_FRAME_MAX_SIZE - CHDR_MAX_LEN_HDR) / float(x300::DATA_FRAME_MAX_SIZE + 8 /* UDP header */ + 20 /* Ethernet header length */))); constexpr size_t MAX_RATE_1GIGE = (size_t)( // bytes/s 10e9 / 8 * // wire speed multiplied by percentage of packets that is sample data - (float(GE_DATA_FRAME_RECV_SIZE - uhd::usrp::DEVICE3_TX_MAX_HDR_LEN) + (float(GE_DATA_FRAME_RECV_SIZE - CHDR_MAX_LEN_HDR) / float(GE_DATA_FRAME_RECV_SIZE + 8 /* UDP header */ + 20 /* Ethernet header length */))); @@ -67,15 +73,15 @@ eth_manager::udp_simple_factory_t eth_manager::x300_get_udp_factory( { udp_simple_factory_t udp_make_connected = udp_simple::make_connected; if (args.has_key("use_dpdk")) { -#ifdef HAVE_DPDK - udp_make_connected = [](const std::string& addr, const std::string& port) { - auto& ctx = uhd::transport::uhd_dpdk_ctx::get(); - return dpdk_simple::make_connected(ctx, addr, port); - }; -#else +//#ifdef HAVE_DPDK +// udp_make_connected = [](const std::string& addr, const std::string& port) { +// auto& ctx = uhd::transport::uhd_dpdk_ctx::get(); +// return dpdk_simple::make_connected(ctx, addr, port); +// }; +//#else UHD_LOG_WARNING( "DPDK", "Detected use_dpdk argument, but DPDK support not built in."); -#endif +//#endif } return udp_make_connected; } @@ -86,14 +92,14 @@ device_addrs_t eth_manager::find(const device_addr_t& hint) udp_simple_factory_t udp_make_connected = x300_get_udp_factory(hint); #ifdef HAVE_DPDK if (hint.has_key("use_dpdk")) { - auto& dpdk_ctx = uhd::transport::uhd_dpdk_ctx::get(); - if (not dpdk_ctx.is_init_done()) { - dpdk_ctx.init(hint); - } - udp_make_broadcast = [](const std::string& addr, const std::string& port) { - auto& ctx = uhd::transport::uhd_dpdk_ctx::get(); - return dpdk_simple::make_broadcast(ctx, addr, port); - }; +// auto& dpdk_ctx = uhd::transport::uhd_dpdk_ctx::get(); +// if (not dpdk_ctx.is_init_done()) { +// dpdk_ctx.init(hint); +// } +// udp_make_broadcast = [](const std::string& addr, const std::string& port) { +// auto& ctx = uhd::transport::uhd_dpdk_ctx::get(); +// return dpdk_simple::make_broadcast(ctx, addr, port); +// }; } #endif udp_simple::sptr comm = @@ -190,7 +196,9 @@ eth_manager::eth_manager(const x300_device_args_t& args, // In discover_eth(), we'll check and enable the other IP address, if given x300_eth_conn_t init; init.addr = args.get_first_addr(); - eth_conns.push_back(init); + auto device_id = allocate_device_id(); + _local_device_ids.push_back(device_id); + eth_conns[device_id] = init; _x300_make_udp_connected = x300_get_udp_factory(dev_addr); @@ -198,182 +206,144 @@ eth_manager::eth_manager(const x300_device_args_t& args, _tree = tree->subtree(root_path); } -both_xports_t eth_manager::make_transport(both_xports_t xports, - const uhd::usrp::device3_impl::xport_type_t xport_type, - const uhd::device_addr_t& args, - const size_t send_mtu, - const size_t recv_mtu, - std::function<uhd::sid_t(uint32_t, uint32_t)>&& allocate_sid) +both_links_t eth_manager::get_links(link_type_t link_type, + const device_id_t local_device_id, + const sep_id_t& /*local_epid*/, + const sep_id_t& /*remote_epid*/, + const device_addr_t& link_args) { - zero_copy_xport_params default_buff_args; - xports.endianness = ENDIANNESS_BIG; - xports.lossless = false; - xports.recv = nullptr; - - size_t& next_src_addr = xport_type == uhd::usrp::device3_impl::TX_DATA - ? _next_tx_src_addr - : xport_type == uhd::usrp::device3_impl::RX_DATA - ? _next_rx_src_addr - : _next_src_addr; - - // Decide on the IP/Interface pair based on the endpoint index - x300_eth_conn_t conn = eth_conns[next_src_addr]; - const uint32_t xbar_src_addr = next_src_addr == 0 ? x300::SRC_ADDR0 : x300::SRC_ADDR1; - const uint32_t xbar_src_dst = conn.type == X300_IFACE_ETH0 ? x300::XB_DST_E0 - : x300::XB_DST_E1; - - // Do not increment src addr for tx_data by default, using dual ethernet - // with the DMA FIFO causes sequence errors to DMA FIFO bandwidth - // limitations. - if (xport_type != uhd::usrp::device3_impl::TX_DATA - || _args.get_enable_tx_dual_eth()) { - next_src_addr = (next_src_addr + 1) % eth_conns.size(); + if (std::find(_local_device_ids.cbegin(), _local_device_ids.cend(), local_device_id) + == _local_device_ids.cend()) { + throw uhd::runtime_error( + std::string("[X300] Cannot create Ethernet link through local device ID ") + + std::to_string(local_device_id) + + ", no such device associated with this motherboard!"); } + // FIXME: We now need to properly associate local_device_id with the right + // entry in eth_conn. We should probably do the load balancing elsewhere, + // and do something like this: + // However, we might also have to make sure that we don't do 2x TX through + // a DMA FIFO, which is a device-specific thing. So punt on that for now. + + x300_eth_conn_t conn = eth_conns[local_device_id]; + zero_copy_xport_params default_buff_args; + + const size_t send_mtu = get_mtu(uhd::TX_DIRECTION); + const size_t recv_mtu = get_mtu(uhd::RX_DIRECTION); - xports.send_sid = allocate_sid(xbar_src_addr, xbar_src_dst); - xports.recv_sid = xports.send_sid.reversed(); // Set size and number of frames default_buff_args.send_frame_size = std::min(send_mtu, ETH_MSG_FRAME_SIZE); default_buff_args.recv_frame_size = std::min(recv_mtu, ETH_MSG_FRAME_SIZE); if (_args.get_use_dpdk()) { -#ifdef HAVE_DPDK - auto& dpdk_ctx = uhd::transport::uhd_dpdk_ctx::get(); - - default_buff_args.num_recv_frames = ETH_MSG_NUM_FRAMES; - default_buff_args.num_send_frames = ETH_MSG_NUM_FRAMES; - if (xport_type == uhd::usrp::device3_impl::CTRL) { - // Increasing number of recv frames here because ctrl_iface uses it - // to determine how many control packets can be in flight before it - // must wait for an ACK - default_buff_args.num_recv_frames = - uhd::rfnoc::CMD_FIFO_SIZE / uhd::rfnoc::MAX_CMD_PKT_SIZE; - } else if (xport_type == uhd::usrp::device3_impl::TX_DATA) { - size_t default_frame_size = conn.link_rate == MAX_RATE_1GIGE - ? GE_DATA_FRAME_SEND_SIZE - : XGE_DATA_FRAME_SEND_SIZE; - default_buff_args.send_frame_size = args.cast<size_t>( - "send_frame_size", std::min(default_frame_size, send_mtu)); - default_buff_args.num_send_frames = - args.cast<size_t>("num_send_frames", default_buff_args.num_send_frames); - default_buff_args.send_buff_size = args.cast<size_t>("send_buff_size", 0); - } else if (xport_type == uhd::usrp::device3_impl::RX_DATA) { - size_t default_frame_size = conn.link_rate == MAX_RATE_1GIGE - ? GE_DATA_FRAME_RECV_SIZE - : XGE_DATA_FRAME_RECV_SIZE; - default_buff_args.recv_frame_size = args.cast<size_t>( - "recv_frame_size", std::min(default_frame_size, recv_mtu)); - default_buff_args.num_recv_frames = - args.cast<size_t>("num_recv_frames", default_buff_args.num_recv_frames); - default_buff_args.recv_buff_size = args.cast<size_t>("recv_buff_size", 0); - } - - int dpdk_port_id = dpdk_ctx.get_route(conn.addr); - if (dpdk_port_id < 0) { - throw uhd::runtime_error( - "Could not find a DPDK port with route to " + conn.addr); - } - auto recv = transport::dpdk_zero_copy::make(dpdk_ctx, - (const unsigned int)dpdk_port_id, - conn.addr, - BOOST_STRINGIZE(X300_VITA_UDP_PORT), - "0", - default_buff_args, - uhd::device_addr_t()); - - xports.recv = recv; // Note: This is a type cast! - xports.send = xports.recv; - xports.recv_buff_size = - (default_buff_args.recv_frame_size - X300_UDP_RESERVED_FRAME_SIZE) - * default_buff_args.num_recv_frames; - xports.send_buff_size = - (default_buff_args.send_frame_size - X300_UDP_RESERVED_FRAME_SIZE) - * default_buff_args.num_send_frames; - UHD_LOG_TRACE("BUFF", - "num_recv_frames=" - << default_buff_args.num_recv_frames - << ", num_send_frames=" << default_buff_args.num_send_frames - << ", recv_frame_size=" << default_buff_args.recv_frame_size - << ", send_frame_size=" << default_buff_args.send_frame_size); - -#else +//#ifdef HAVE_DPDK + // auto& dpdk_ctx = uhd::transport::uhd_dpdk_ctx::get(); + + // default_buff_args.num_recv_frames = ETH_MSG_NUM_FRAMES; + // default_buff_args.num_send_frames = ETH_MSG_NUM_FRAMES; + // if (link_type == link_type_t::CTRL) { + //// Increasing number of recv frames here because ctrl_iface uses it + //// to determine how many control packets can be in flight before it + //// must wait for an ACK + // default_buff_args.num_recv_frames = + // uhd::rfnoc::CMD_FIFO_SIZE / uhd::rfnoc::MAX_CMD_PKT_SIZE; + //} else if (xport_type == uhd::usrp::device3_impl::TX_DATA) { + // size_t default_frame_size = conn.link_rate == MAX_RATE_1GIGE + //? GE_DATA_FRAME_SEND_SIZE + //: XGE_DATA_FRAME_SEND_SIZE; + // default_buff_args.send_frame_size = args.cast<size_t>( + //"send_frame_size", std::min(default_frame_size, send_mtu)); + // default_buff_args.num_send_frames = + // args.cast<size_t>("num_send_frames", default_buff_args.num_send_frames); + // default_buff_args.send_buff_size = args.cast<size_t>("send_buff_size", 0); + //} else if (xport_type == uhd::usrp::device3_impl::RX_DATA) { + // size_t default_frame_size = conn.link_rate == MAX_RATE_1GIGE + //? GE_DATA_FRAME_RECV_SIZE + //: XGE_DATA_FRAME_RECV_SIZE; + // default_buff_args.recv_frame_size = args.cast<size_t>( + //"recv_frame_size", std::min(default_frame_size, recv_mtu)); + // default_buff_args.num_recv_frames = + // args.cast<size_t>("num_recv_frames", default_buff_args.num_recv_frames); + // default_buff_args.recv_buff_size = args.cast<size_t>("recv_buff_size", 0); + //} + + // int dpdk_port_id = dpdk_ctx.get_route(conn.addr); + // if (dpdk_port_id < 0) { + // throw uhd::runtime_error( + //"Could not find a DPDK port with route to " + conn.addr); + //} + // auto recv = transport::dpdk_zero_copy::make(dpdk_ctx, + //(const unsigned int)dpdk_port_id, + // conn.addr, + // BOOST_STRINGIZE(X300_VITA_UDP_PORT), + //"0", + // default_buff_args, + // uhd::device_addr_t()); + +//#else UHD_LOG_WARNING("X300", "Cannot create DPDK transport, falling back to UDP"); -#endif +//#endif } - if (!xports.recv) { - // Buffering is done in the socket buffers, so size them relative to - // the link rate - default_buff_args.send_buff_size = conn.link_rate / 50; // 20ms - default_buff_args.recv_buff_size = std::max(conn.link_rate / 50, - ETH_MSG_NUM_FRAMES * ETH_MSG_FRAME_SIZE); // enough to hold greater of 20ms or - // number of msg frames - // There is no need for more than 1 send and recv frame since the - // buffering is done in the socket buffers - default_buff_args.num_send_frames = 1; - default_buff_args.num_recv_frames = 1; - if (xport_type == uhd::usrp::device3_impl::CTRL) { - // Increasing number of recv frames here because ctrl_iface uses it - // to determine how many control packets can be in flight before it - // must wait for an ACK - default_buff_args.num_recv_frames = - uhd::rfnoc::CMD_FIFO_SIZE / uhd::rfnoc::MAX_CMD_PKT_SIZE; - } else if (xport_type == uhd::usrp::device3_impl::TX_DATA) { - size_t default_frame_size = conn.link_rate == MAX_RATE_1GIGE - ? GE_DATA_FRAME_SEND_SIZE - : XGE_DATA_FRAME_SEND_SIZE; - default_buff_args.send_frame_size = args.cast<size_t>( - "send_frame_size", std::min(default_frame_size, send_mtu)); - default_buff_args.num_send_frames = - args.cast<size_t>("num_send_frames", default_buff_args.num_send_frames); - default_buff_args.send_buff_size = - args.cast<size_t>("send_buff_size", default_buff_args.send_buff_size); - } else if (xport_type == uhd::usrp::device3_impl::RX_DATA) { - size_t default_frame_size = conn.link_rate == MAX_RATE_1GIGE - ? GE_DATA_FRAME_RECV_SIZE - : XGE_DATA_FRAME_RECV_SIZE; - default_buff_args.recv_frame_size = args.cast<size_t>( - "recv_frame_size", std::min(default_frame_size, recv_mtu)); - // set some buffers so the offload thread actually offloads the - // socket I/O - default_buff_args.num_recv_frames = args.cast<size_t>("num_recv_frames", 2); - default_buff_args.recv_buff_size = - args.cast<size_t>("recv_buff_size", default_buff_args.recv_buff_size); - } - // make a new transport - fpga has no idea how to talk to us on this yet - udp_zero_copy::buff_params buff_params; - xports.recv = udp_zero_copy::make(conn.addr, - BOOST_STRINGIZE(X300_VITA_UDP_PORT), - default_buff_args, - buff_params); - - // Create a threaded transport for the receive chain only - if (xport_type == uhd::usrp::device3_impl::RX_DATA) { - xports.recv = zero_copy_recv_offload::make( - xports.recv, x300::RECV_OFFLOAD_BUFFER_TIMEOUT); - } - - xports.send = xports.recv; - - // For the UDP transport the buffer size is the size of the socket buffer - // in the kernel - xports.recv_buff_size = buff_params.recv_buff_size; - xports.send_buff_size = buff_params.send_buff_size; + // Buffering is done in the socket buffers, so size them relative to + // the link rate + default_buff_args.send_buff_size = conn.link_rate / 50; // 20ms + default_buff_args.recv_buff_size = std::max(conn.link_rate / 50, + ETH_MSG_NUM_FRAMES * ETH_MSG_FRAME_SIZE); // enough to hold greater of 20ms or + // number of msg frames + // There is no need for more than 1 send and recv frame since the + // buffering is done in the socket buffers + default_buff_args.num_send_frames = 1; // or 2? + default_buff_args.num_recv_frames = 1; + if (link_type == link_type_t::CTRL) { + // Increasing number of recv frames here because ctrl_iface uses it + // to determine how many control packets can be in flight before it + // must wait for an ACK + // FIXME this is no longer true, find a good value + default_buff_args.num_recv_frames = 85; // 256/3 + } else if (link_type == link_type_t::TX_DATA) { + size_t default_frame_size = conn.link_rate == MAX_RATE_1GIGE + ? GE_DATA_FRAME_SEND_SIZE + : XGE_DATA_FRAME_SEND_SIZE; + default_buff_args.send_frame_size = link_args.cast<size_t>( + "send_frame_size", std::min(default_frame_size, send_mtu)); + default_buff_args.num_send_frames = link_args.cast<size_t>( + "num_send_frames", default_buff_args.num_send_frames); + default_buff_args.send_buff_size = link_args.cast<size_t>( + "send_buff_size", default_buff_args.send_buff_size); + } else if (link_type == link_type_t::RX_DATA) { + size_t default_frame_size = conn.link_rate == MAX_RATE_1GIGE + ? GE_DATA_FRAME_RECV_SIZE + : XGE_DATA_FRAME_RECV_SIZE; + default_buff_args.recv_frame_size = link_args.cast<size_t>( + "recv_frame_size", std::min(default_frame_size, recv_mtu)); + // set some buffers so the offload thread actually offloads the + // socket I/O + default_buff_args.num_recv_frames = + link_args.cast<size_t>("num_recv_frames", 2); + default_buff_args.recv_buff_size = link_args.cast<size_t>( + "recv_buff_size", default_buff_args.recv_buff_size); } - // send a mini packet with SID into the ZPU - // ZPU will reprogram the ethernet framer - UHD_LOGGER_DEBUG("X300") << "programming packet for new xport on " << conn.addr - << " sid " << xports.send_sid; - // YES, get a __send__ buffer from the __recv__ socket - //-- this is the only way to program the framer for recv: - managed_send_buffer::sptr buff = xports.recv->get_send_buff(); - buff->cast<uint32_t*>()[0] = 0; // eth dispatch looks for != 0 - buff->cast<uint32_t*>()[1] = uhd::htonx(xports.send_sid.get()); - buff->commit(8); - buff.reset(); - - return xports; + /* FIXME: Should have common infrastructure for creating I/O services */ + auto io_srv = uhd::transport::inline_io_service::make(); + link_params_t link_params; + link_params.num_recv_frames = default_buff_args.num_recv_frames; + link_params.num_send_frames = default_buff_args.num_send_frames; + link_params.recv_frame_size = default_buff_args.recv_frame_size; + link_params.send_frame_size = default_buff_args.send_frame_size; + link_params.recv_buff_size = default_buff_args.recv_buff_size; + link_params.send_buff_size = default_buff_args.send_buff_size; + + size_t recv_buff_size, send_buff_size; + auto link = uhd::transport::udp_boost_asio_link::make(conn.addr, + BOOST_STRINGIZE(X300_VITA_UDP_PORT), + link_params, + recv_buff_size, + send_buff_size); + io_srv->attach_send_link(link); + io_srv->attach_recv_link(link); + return std::make_tuple(io_srv, link, send_buff_size, link, recv_buff_size, true); } /****************************************************************************** @@ -390,7 +360,7 @@ void eth_manager::init_link( { double link_max_rate = 0.0; - // Discover ethernet interfaces + // Discover ethernet interfaces on the device discover_eth(mb_eeprom, loaded_fpga_image); /* This is an ETH connection. Figure out what the maximum supported frame @@ -428,9 +398,9 @@ void eth_manager::init_link( determine_max_frame_size(get_pri_eth().addr, req_max_frame_size); _max_frame_sizes = pri_frame_sizes; - if (eth_conns.size() > 1) { + if (_local_device_ids.size() > 1) { frame_size_t sec_frame_sizes = - determine_max_frame_size(eth_conns.at(1).addr, req_max_frame_size); + determine_max_frame_size(eth_conns.at(_local_device_ids.at(1)).addr, req_max_frame_size); // Choose the minimum of the max frame sizes // to ensure we don't exceed any one of the links' MTU @@ -469,7 +439,8 @@ void eth_manager::init_link( } // Check frame sizes - for (auto conn : eth_conns) { + for (auto conn_pair : eth_conns) { + auto conn = conn_pair.second; link_max_rate += conn.link_rate; size_t rec_send_frame_size = conn.link_rate == MAX_RATE_1GIGE @@ -527,14 +498,14 @@ void eth_manager::discover_eth( ip_addrs.push_back(_args.get_second_addr()); } - // Clear any previous addresses added - eth_conns.clear(); + // Grab the device ID used during init + auto init_dev_id = _local_device_ids.at(0); // Index the MB EEPROM addresses std::vector<std::string> mb_eeprom_addrs; const size_t num_mb_eeprom_addrs = 4; for (size_t i = 0; i < num_mb_eeprom_addrs; i++) { - const std::string key = "ip-addr" + boost::to_string(i); + const std::string key = "ip-addr" + std::to_string(i); // Show a warning if there exists duplicate addresses in the mboard eeprom if (std::find(mb_eeprom_addrs.begin(), mb_eeprom_addrs.end(), mb_eeprom[key]) @@ -630,13 +601,20 @@ void eth_manager::discover_eth( str(boost::format("X300 Initialization Error: Invalid address %s") % conn_iface.addr)); } - eth_conns.push_back(conn_iface); + if (conn_iface.addr == eth_conns.at(init_dev_id).addr) { + eth_conns[init_dev_id] = conn_iface; + } else { + auto device_id = allocate_device_id(); + _local_device_ids.push_back(device_id); + eth_conns[device_id] = conn_iface; + } } } - if (eth_conns.size() == 0) + if (eth_conns.size() == 0) { throw uhd::assertion_error( - "X300 Initialization Error: No ethernet interfaces specified."); + "X300 Initialization Error: No valid Ethernet interfaces specified."); + } } eth_manager::frame_size_t eth_manager::determine_max_frame_size( diff --git a/host/lib/usrp/x300/x300_eth_mgr.hpp b/host/lib/usrp/x300/x300_eth_mgr.hpp index 022e14bbd..1f4013e17 100644 --- a/host/lib/usrp/x300/x300_eth_mgr.hpp +++ b/host/lib/usrp/x300/x300_eth_mgr.hpp @@ -10,13 +10,14 @@ #include "x300_conn_mgr.hpp" #include "x300_device_args.hpp" #include "x300_mboard_type.hpp" +#include <uhd/property_tree.hpp> #include <uhd/transport/if_addrs.hpp> #include <uhd/transport/udp_constants.hpp> #include <uhd/transport/udp_simple.hpp> //mtu -#include <uhd/transport/udp_zero_copy.hpp> #include <uhd/types/device_addr.hpp> -#include <uhdlib/rfnoc/xports.hpp> +#include <uhd/types/direction.hpp> #include <functional> +#include <map> #include <vector> namespace uhd { namespace usrp { namespace x300 { @@ -52,12 +53,20 @@ public: */ void release_ctrl_iface(std::function<void(void)>&& release_fn); - both_xports_t make_transport(both_xports_t xports, - const uhd::usrp::device3_impl::xport_type_t xport_type, - const uhd::device_addr_t& args, - const size_t send_mtu, - const size_t recv_mtu, - std::function<uhd::sid_t(uint32_t, uint32_t)>&& allocate_sid); + /*! Return the list of local device IDs associated with this link + * + * Note: this will only be valid after init_link() is called. + */ + std::vector<uhd::rfnoc::device_id_t> get_local_device_ids() + { + return _local_device_ids; + } + + uhd::transport::both_links_t get_links(uhd::transport::link_type_t link_type, + const uhd::rfnoc::device_id_t local_device_id, + const uhd::rfnoc::sep_id_t& local_epid, + const uhd::rfnoc::sep_id_t& remote_epid, + const uhd::device_addr_t& link_args); private: //! Function to create a udp_simple::sptr (kernel-based or DPDK-based) @@ -87,7 +96,7 @@ private: // Get the primary ethernet connection inline const x300_eth_conn_t& get_pri_eth() const { - return eth_conns[0]; + return eth_conns.at(_local_device_ids.at(0)); } static udp_simple_factory_t x300_get_udp_factory(const device_addr_t& args); @@ -111,15 +120,14 @@ private: udp_simple_factory_t _x300_make_udp_connected; - std::vector<x300_eth_conn_t> eth_conns; - size_t _next_src_addr = 0; - size_t _next_tx_src_addr = 0; - size_t _next_rx_src_addr = 0; + std::map<uhd::rfnoc::device_id_t, x300_eth_conn_t> eth_conns; frame_size_t _max_frame_sizes; uhd::device_addr_t recv_args; uhd::device_addr_t send_args; + + std::vector<uhd::rfnoc::device_id_t> _local_device_ids; }; }}} // namespace uhd::usrp::x300 diff --git a/host/lib/usrp/x300/x300_fw_common.h b/host/lib/usrp/x300/x300_fw_common.h index 45301640a..a966bcd13 100644 --- a/host/lib/usrp/x300/x300_fw_common.h +++ b/host/lib/usrp/x300/x300_fw_common.h @@ -23,7 +23,7 @@ extern "C" { #define X300_REVISION_MIN 2 #define X300_FW_COMPAT_MAJOR 6 #define X300_FW_COMPAT_MINOR 0 -#define X300_FPGA_COMPAT_MAJOR 0x24 +#define X300_FPGA_COMPAT_MAJOR 0x25 //shared memory sections - in between the stack and the program space #define X300_FW_SHMEM_BASE 0x6000 diff --git a/host/lib/usrp/x300/x300_impl.cpp b/host/lib/usrp/x300/x300_impl.cpp index 223b112ec..d1d7b43f7 100644 --- a/host/lib/usrp/x300/x300_impl.cpp +++ b/host/lib/usrp/x300/x300_impl.cpp @@ -10,19 +10,16 @@ #include "x300_claim.hpp" #include "x300_eth_mgr.hpp" #include "x300_mb_eeprom.hpp" +#include "x300_mb_controller.hpp" #include "x300_mb_eeprom_iface.hpp" #include "x300_mboard_type.hpp" #include "x300_pcie_mgr.hpp" #include <uhd/transport/if_addrs.hpp> -#include <uhd/types/sid.hpp> -#include <uhd/usrp/subdev_spec.hpp> #include <uhd/utils/log.hpp> -#include <uhd/utils/math.hpp> #include <uhd/utils/paths.hpp> #include <uhd/utils/safe_call.hpp> #include <uhd/utils/static.hpp> -#include <boost/algorithm/string.hpp> -#include <boost/make_shared.hpp> +#include <uhdlib/rfnoc/device_id.hpp> #include <chrono> #include <fstream> #include <thread> @@ -35,6 +32,17 @@ using namespace uhd::rfnoc; using namespace uhd::usrp::x300; namespace asio = boost::asio; +namespace uhd { namespace usrp { namespace x300 { + +void init_prop_tree( + const size_t mb_idx, uhd::rfnoc::x300_mb_controller* mbc, property_tree::sptr pt); + +}}} // namespace uhd::usrp::x300 + + +const uhd::rfnoc::chdr::chdr_packet_factory x300_impl::_pkt_factory( + CHDR_W_64, ENDIANNESS_BIG); + /*********************************************************************** * Discovery over the udp and pcie transport @@ -168,10 +176,10 @@ static void x300_load_fw(wb_iface::sptr fw_reg_ctrl, const std::string& file_nam UHD_LOGGER_INFO("X300") << "Firmware loaded!"; } -x300_impl::x300_impl(const uhd::device_addr_t& dev_addr) : device3_impl(), _sid_framer(0) +x300_impl::x300_impl(const uhd::device_addr_t& dev_addr) + : rfnoc_device() { UHD_LOGGER_INFO("X300") << "X300 initialization sequence..."; - _tree->create<std::string>("/name").set("X-Series Device"); const device_addrs_t device_args = separate_device_addr(dev_addr); _mb.resize(device_args.size()); @@ -216,6 +224,10 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t& dev_addr) mb.send_args[key] = dev_addr[key]; } + mb.device_id = allocate_device_id(); + UHD_LOG_DEBUG( + "X300", "Motherboard " << mb_i << " has remote device ID: " << mb.device_id); + UHD_LOGGER_DEBUG("X300") << "Setting up basic communication..."; if (mb.xport_path == xport_path_t::NIRIO) { mb.conn_mgr = std::make_shared<pcie_manager>(mb.args, _tree, mb_path); @@ -243,9 +255,6 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t& dev_addr) this->check_fpga_compat(mb_path, mb); this->check_fw_compat(mb_path, mb); - mb.fw_regmap = boost::make_shared<fw_regmap_t>(); - mb.fw_regmap->initialize(*mb.zpu_ctrl.get(), true); - // store which FPGA image is loaded mb.loaded_fpga_image = get_fpga_option(mb.zpu_ctrl); @@ -256,28 +265,6 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t& dev_addr) mb.zpu_i2c->set_clock_rate(x300::BUS_CLOCK_RATE / 2); //////////////////////////////////////////////////////////////////// - // print network routes mapping - //////////////////////////////////////////////////////////////////// - /* - const uint32_t routes_addr = mb.zpu_ctrl->peek32(SR_ADDR(X300_FW_SHMEM_BASE, - X300_FW_SHMEM_ROUTE_MAP_ADDR)); const uint32_t routes_len = - mb.zpu_ctrl->peek32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_ROUTE_MAP_LEN)); - UHD_VAR(routes_len); - for (size_t i = 0; i < routes_len; i+=1) - { - const uint32_t node_addr = mb.zpu_ctrl->peek32(SR_ADDR(routes_addr, i*2+0)); - const uint32_t nbor_addr = mb.zpu_ctrl->peek32(SR_ADDR(routes_addr, i*2+1)); - if (node_addr != 0 and nbor_addr != 0) - { - UHD_LOGGER_INFO("X300") << boost::format("%u: %s -> %s") - % i - % asio::ip::address_v4(node_addr).to_string() - % asio::ip::address_v4(nbor_addr).to_string(); - } - } - */ - - //////////////////////////////////////////////////////////////////// // setup the mboard eeprom //////////////////////////////////////////////////////////////////// UHD_LOGGER_DEBUG("X300") << "Loading values from EEPROM..."; @@ -322,8 +309,6 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t& dev_addr) "reprogramming."); } } - _tree->create<std::string>(mb_path / "name").set(product_name); - _tree->create<std::string>(mb_path / "codename").set("Yetti"); //////////////////////////////////////////////////////////////////// // discover interfaces, frame sizes, and link rates @@ -344,190 +329,56 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t& dev_addr) // create clock control objects //////////////////////////////////////////////////////////////////// UHD_LOGGER_DEBUG("X300") << "Setting up RF frontend clocking..."; - - // Initialize clock control registers. NOTE: This does not configure the LMK yet. + // Initialize clock control registers. + // NOTE: This does not configure the LMK yet. mb.clock = x300_clock_ctrl::make(mb.zpu_spi, 1 /*slaveno*/, mb.hw_rev, mb.args.get_master_clock_rate(), mb.args.get_dboard_clock_rate(), mb.args.get_system_ref_rate()); - mb.fw_regmap->ref_freq_reg.write( - fw_regmap_t::ref_freq_reg_t::REF_FREQ, uint32_t(mb.args.get_system_ref_rate())); - - // Initialize clock source to use internal reference and generate - // a valid radio clock. This may change after configuration is done. - // This will configure the LMK and wait for lock - update_clock_source(mb, mb.args.get_clock_source()); //////////////////////////////////////////////////////////////////// - // create clock properties + // create motherboard controller //////////////////////////////////////////////////////////////////// - _tree->create<double>(mb_path / "master_clock_rate").set_publisher([&mb]() { - return mb.clock->get_master_clock_rate(); - }); + // Now we have all the peripherals, create the MB controller. It will also + // initialize the clock source, and the time source. + auto mb_ctrl = std::make_shared<x300_mb_controller>( + mb.hw_rev, product_name, mb.zpu_i2c, mb.zpu_ctrl, mb.clock, mb_eeprom, mb.args); + register_mb_controller(mb_i, mb_ctrl); + // Clock should be up now! UHD_LOGGER_INFO("X300") << "Radio 1x clock: " << (mb.clock->get_master_clock_rate() / 1e6) << " MHz"; //////////////////////////////////////////////////////////////////// - // Create the GPSDO control - //////////////////////////////////////////////////////////////////// - static constexpr uint32_t dont_look_for_gpsdo = 0x1234abcdul; - - // otherwise if not disabled, look for the internal GPSDO - if (mb.zpu_ctrl->peek32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_GPSDO_STATUS)) - != dont_look_for_gpsdo) { - UHD_LOG_DEBUG("X300", "Detecting internal GPSDO...."); - try { - // gps_ctrl will print its own log statements if a GPSDO was found - mb.gps = gps_ctrl::make(x300_make_uart_iface(mb.zpu_ctrl)); - } catch (std::exception& e) { - UHD_LOGGER_ERROR("X300") - << "An error occurred making GPSDO control: " << e.what(); - } - if (mb.gps and mb.gps->gps_detected()) { - for (const std::string& name : mb.gps->get_sensors()) { - _tree->create<sensor_value_t>(mb_path / "sensors" / name) - .set_publisher([&mb, name]() { return mb.gps->get_sensor(name); }); - } - } else { - mb.zpu_ctrl->poke32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_GPSDO_STATUS), - dont_look_for_gpsdo); - } - } - - //////////////////////////////////////////////////////////////////// - // setup time sources and properties - //////////////////////////////////////////////////////////////////// - _tree->create<std::string>(mb_path / "time_source" / "value") - .set(mb.args.get_time_source()) - .add_coerced_subscriber([this, &mb](const std::string& time_source) { - this->update_time_source(mb, time_source); - }); - _tree->create<std::vector<std::string>>(mb_path / "time_source" / "options") - .set(TIME_SOURCE_OPTIONS); - - // setup the time output, default to ON - _tree->create<bool>(mb_path / "time_source" / "output") - .add_coerced_subscriber([this, &mb](const bool time_output) { - this->set_time_source_out(mb, time_output); - }) - .set(true); - - //////////////////////////////////////////////////////////////////// - // setup clock sources and properties + // setup properties //////////////////////////////////////////////////////////////////// - _tree->create<std::string>(mb_path / "clock_source" / "value") - .set(mb.args.get_clock_source()) - .add_coerced_subscriber([this, &mb](const std::string& clock_source) { - this->update_clock_source(mb, clock_source); - }); - _tree->create<std::vector<std::string>>(mb_path / "clock_source" / "options") - .set(CLOCK_SOURCE_OPTIONS); - - // setup external reference options. default to 10 MHz input reference - _tree->create<std::string>(mb_path / "clock_source" / "external"); - _tree - ->create<std::vector<double>>( - mb_path / "clock_source" / "external" / "freq" / "options") - .set(x300::EXTERNAL_FREQ_OPTIONS); - _tree->create<double>(mb_path / "clock_source" / "external" / "value") - .set(mb.clock->get_sysref_clock_rate()); - // FIXME the external clock source settings need to be more robust - - // setup the clock output, default to ON - _tree->create<bool>(mb_path / "clock_source" / "output") - .add_coerced_subscriber( - [&mb](const bool clock_output) { mb.clock->set_ref_out(clock_output); }); - - // Initialize tick rate (must be done before setting time) - // Note: The master tick rate can't be changed at runtime! - const double master_clock_rate = mb.clock->get_master_clock_rate(); - _tree->create<double>(mb_path / "tick_rate") - .set_coercer([master_clock_rate](const double rate) { - // The contract of multi_usrp::set_master_clock_rate() is to coerce - // and not throw, so we'll follow that behaviour here. - if (!uhd::math::frequencies_are_equal(rate, master_clock_rate)) { - UHD_LOGGER_WARNING("X300") - << "Cannot update master clock rate! X300 Series does not " - "allow changing the clock rate during runtime."; - } - return master_clock_rate; - }) - .add_coerced_subscriber([this](const double) { this->update_tx_streamers(); }) - .add_coerced_subscriber([this](const double) { this->update_rx_streamers(); }) - .set(master_clock_rate); + init_prop_tree(mb_i, mb_ctrl.get(), _tree); //////////////////////////////////////////////////////////////////// - // and do the misc mboard sensors + // RFNoC Stuff //////////////////////////////////////////////////////////////////// - _tree->create<sensor_value_t>(mb_path / "sensors" / "ref_locked") - .set_publisher([this, &mb]() { return this->get_ref_locked(mb); }); - - //////////////// RFNOC ///////////////// - const size_t n_rfnoc_blocks = mb.zpu_ctrl->peek32(SR_ADDR(SET0_BASE, ZPU_RB_NUM_CE)); - enumerate_rfnoc_blocks(mb_i, - n_rfnoc_blocks, - x300::XB_DST_PCI + 1, /* base port */ - uhd::sid_t(x300::SRC_ADDR0, 0, x300::DST_ADDR + mb_i, 0), - dev_addr); - //////////////// RFNOC ///////////////// - - // If we have a radio, we must configure its codec control: - const std::string radio_blockid_hint = str(boost::format("%d/Radio") % mb_i); - std::vector<rfnoc::block_id_t> radio_ids = - find_blocks<rfnoc::x300_radio_ctrl_impl>(radio_blockid_hint); - if (not radio_ids.empty()) { - if (radio_ids.size() > 2) { - UHD_LOGGER_WARNING("X300") - << "Too many Radio Blocks found. Using only the first two."; - radio_ids.resize(2); - } - - for (const rfnoc::block_id_t& id : radio_ids) { - rfnoc::x300_radio_ctrl_impl::sptr radio( - get_block_ctrl<rfnoc::x300_radio_ctrl_impl>(id)); - mb.radios.push_back(radio); - radio->setup_radio(mb.zpu_i2c, - mb.clock, - mb.args.get_ignore_cal_file(), - mb.args.get_self_cal_adc_delay()); - } - - //////////////////////////////////////////////////////////////////// - // ADC test and cal - //////////////////////////////////////////////////////////////////// - if (mb.args.get_self_cal_adc_delay()) { - rfnoc::x300_radio_ctrl_impl::self_cal_adc_xfer_delay(mb.radios, - mb.clock, - [this, &mb](const double timeout) { - return this->wait_for_clk_locked( - mb, fw_regmap_t::clk_status_reg_t::LMK_LOCK, timeout); - }, - true /* Apply ADC delay */); - } - if (mb.args.get_ext_adc_self_test()) { - rfnoc::x300_radio_ctrl_impl::extended_adc_test( - mb.radios, mb.args.get_ext_adc_self_test_duration()); - } else { - for (size_t i = 0; i < mb.radios.size(); i++) { - mb.radios.at(i)->self_test_adc(); - } + // Set the remote device ID + mb.zpu_ctrl->poke32(SR_ADDR(SET0_BASE, ZPU_SR_XB_LOCAL), mb.device_id); + // Configure the CHDR port number in the dispatcher + mb.zpu_ctrl->poke32(SR_ADDR(SET0_BASE, (ZPU_SR_ETHINT0 + 8 + 3)), X300_VITA_UDP_PORT); + mb.zpu_ctrl->poke32(SR_ADDR(SET0_BASE, (ZPU_SR_ETHINT1 + 8 + 3)), X300_VITA_UDP_PORT); + // Peek to finish transaction + mb.zpu_ctrl->peek32(0); + + { // Need to lock access to _mb_ifaces, so we can run setup_mb() in + // parallel + std::lock_guard<std::mutex> l(_mb_iface_mutex); + _mb_ifaces.insert({mb_i, + x300_mb_iface(mb.conn_mgr, mb.clock->get_master_clock_rate(), mb.device_id)}); + UHD_LOG_DEBUG("X300", "Motherboard " << mb_i << " has local device IDs: "); + for (const auto local_dev_id : _mb_ifaces.at(mb_i).get_local_device_ids()) { + UHD_LOG_DEBUG("X300", "* " << local_dev_id); } + } // End of locked section - //////////////////////////////////////////////////////////////////// - // Synchronize times (dboard initialization can desynchronize them) - //////////////////////////////////////////////////////////////////// - if (radio_ids.size() == 2) { - this->sync_times(mb, mb.radios[0]->get_time_now()); - } - - } else { - UHD_LOGGER_INFO("X300") << "No Radio Block found. Assuming radio-less operation."; - } /* end of radio block(s) initialization */ - - mb.initialization_done = true; + mb_ctrl->set_initialization_done(); } x300_impl::~x300_impl(void) @@ -548,283 +399,6 @@ x300_impl::~x300_impl(void) } } -uhd::both_xports_t x300_impl::make_transport(const uhd::sid_t& address, - const xport_type_t xport_type, - const uhd::device_addr_t& args) -{ - const size_t mb_index = address.get_dst_addr() - x300::DST_ADDR; - mboard_members_t& mb = _mb[mb_index]; - both_xports_t xports; - - // Calculate MTU based on MTU in args and device limitations - const size_t send_mtu = args.cast<size_t>("mtu", - get_mtu(mb_index, uhd::TX_DIRECTION)); - const size_t recv_mtu = args.cast<size_t>("mtu", - get_mtu(mb_index, uhd::RX_DIRECTION)); - - if (mb.xport_path == xport_path_t::NIRIO) { - xports.send_sid = - this->allocate_sid(mb, address, x300::SRC_ADDR0, x300::XB_DST_PCI); - xports.recv_sid = xports.send_sid.reversed(); - return std::dynamic_pointer_cast<pcie_manager>(mb.conn_mgr) - ->make_transport(xports, xport_type, args, send_mtu, recv_mtu); - } else if (mb.xport_path == xport_path_t::ETH) { - xports = std::dynamic_pointer_cast<eth_manager>(mb.conn_mgr) - ->make_transport(xports, - xport_type, - args, - send_mtu, - recv_mtu, - [this, &mb, address]( - const uint32_t src_addr, const uint32_t src_dst) { - return this->allocate_sid(mb, address, src_addr, src_dst); - }); - - // reprogram the ethernet dispatcher's udp port (should be safe to always set) - UHD_LOGGER_TRACE("X300") - << "reprogram the ethernet dispatcher's udp port to " << X300_VITA_UDP_PORT; - mb.zpu_ctrl->poke32( - SR_ADDR(SET0_BASE, (ZPU_SR_ETHINT0 + 8 + 3)), X300_VITA_UDP_PORT); - mb.zpu_ctrl->poke32( - SR_ADDR(SET0_BASE, (ZPU_SR_ETHINT1 + 8 + 3)), X300_VITA_UDP_PORT); - - // Do a peek to an arbitrary address to guarantee that the - // ethernet framer has been programmed before we return. - mb.zpu_ctrl->peek32(0); - - return xports; - } - UHD_THROW_INVALID_CODE_PATH(); -} - - -uhd::sid_t x300_impl::allocate_sid(mboard_members_t& mb, - const uhd::sid_t& address, - const uint32_t src_addr, - const uint32_t src_dst) -{ - uhd::sid_t sid = address; - sid.set_src_addr(src_addr); - sid.set_src_endpoint(_sid_framer++); // increment for next setup - - // TODO Move all of this setup_mb() - // Program the X300 to recognise it's own local address. - mb.zpu_ctrl->poke32(SR_ADDR(SET0_BASE, ZPU_SR_XB_LOCAL), address.get_dst_addr()); - // Program CAM entry for outgoing packets matching a X300 resource (for example a - // Radio) This type of packet matches the XB_LOCAL address and is looked up in the - // upper half of the CAM - mb.zpu_ctrl->poke32(SR_ADDR(SETXB_BASE, 256 + address.get_dst_endpoint()), - address.get_dst_xbarport()); - // Program CAM entry for returning packets to us (for example GR host via Eth0) - // This type of packet does not match the XB_LOCAL address and is looked up in the - // lower half of the CAM - mb.zpu_ctrl->poke32(SR_ADDR(SETXB_BASE, 0 + src_addr), src_dst); - - UHD_LOGGER_TRACE("X300") << "done router config for sid " << sid; - - return sid; -} - -/*********************************************************************** - * clock and time control logic - **********************************************************************/ -void x300_impl::set_time_source_out(mboard_members_t& mb, const bool enb) -{ - mb.fw_regmap->clock_ctrl_reg.write( - fw_regmap_t::clk_ctrl_reg_t::PPS_OUT_EN, enb ? 1 : 0); -} - -void x300_impl::update_clock_source(mboard_members_t& mb, const std::string& source) -{ - // Optimize for the case when the current source is internal and we are trying - // to set it to internal. This is the only case where we are guaranteed that - // the clock has not gone away so we can skip setting the MUX and reseting the LMK. - const bool reconfigure_clks = (mb.current_refclk_src != "internal") - or (source != "internal"); - if (reconfigure_clks) { - // Update the clock MUX on the motherboard to select the requested source - if (source == "internal") { - mb.fw_regmap->clock_ctrl_reg.set(fw_regmap_t::clk_ctrl_reg_t::CLK_SOURCE, - fw_regmap_t::clk_ctrl_reg_t::SRC_INTERNAL); - mb.fw_regmap->clock_ctrl_reg.set(fw_regmap_t::clk_ctrl_reg_t::TCXO_EN, 1); - } else if (source == "external") { - mb.fw_regmap->clock_ctrl_reg.set(fw_regmap_t::clk_ctrl_reg_t::CLK_SOURCE, - fw_regmap_t::clk_ctrl_reg_t::SRC_EXTERNAL); - mb.fw_regmap->clock_ctrl_reg.set(fw_regmap_t::clk_ctrl_reg_t::TCXO_EN, 0); - } else if (source == "gpsdo") { - mb.fw_regmap->clock_ctrl_reg.set(fw_regmap_t::clk_ctrl_reg_t::CLK_SOURCE, - fw_regmap_t::clk_ctrl_reg_t::SRC_GPSDO); - mb.fw_regmap->clock_ctrl_reg.set(fw_regmap_t::clk_ctrl_reg_t::TCXO_EN, 0); - } else { - throw uhd::key_error("update_clock_source: unknown source: " + source); - } - mb.fw_regmap->clock_ctrl_reg.flush(); - - // Reset the LMK to make sure it re-locks to the new reference - mb.clock->reset_clocks(); - } - - // Wait for the LMK to lock (always, as a sanity check that the clock is useable) - //* Currently the LMK can take as long as 30 seconds to lock to a reference but we - // don't - //* want to wait that long during initialization. - // TODO: Need to verify timeout and settings to make sure lock can be achieved in - // < 1.0 seconds - double timeout = mb.initialization_done ? 30.0 : 1.0; - - // The programming code in x300_clock_ctrl is not compatible with revs <= 4 and may - // lead to locking issues. So, disable the ref-locked check for older (unsupported) - // boards. - if (mb.hw_rev > 4) { - if (not wait_for_clk_locked( - mb, fw_regmap_t::clk_status_reg_t::LMK_LOCK, timeout)) { - // failed to lock on reference - if (mb.initialization_done) { - throw uhd::runtime_error( - (boost::format("Reference Clock PLL failed to lock to %s source.") - % source) - .str()); - } else { - // TODO: Re-enable this warning when we figure out a reliable lock time - // UHD_LOGGER_WARNING("X300") << "Reference clock failed to lock to " + - // source + " during device initialization. " << - // "Check for the lock before operation or ignore this warning if using - // another clock source." ; - } - } - } - - if (reconfigure_clks) { - // Reset the radio clock PLL in the FPGA - mb.zpu_ctrl->poke32( - SR_ADDR(SET0_BASE, ZPU_SR_SW_RST), ZPU_SR_SW_RST_RADIO_CLK_PLL); - mb.zpu_ctrl->poke32(SR_ADDR(SET0_BASE, ZPU_SR_SW_RST), 0); - - // Wait for radio clock PLL to lock - if (not wait_for_clk_locked( - mb, fw_regmap_t::clk_status_reg_t::RADIO_CLK_LOCK, 0.01)) { - throw uhd::runtime_error( - (boost::format("Reference Clock PLL in FPGA failed to lock to %s source.") - % source) - .str()); - } - - // Reset the IDELAYCTRL used to calibrate the data interface delays - mb.zpu_ctrl->poke32( - SR_ADDR(SET0_BASE, ZPU_SR_SW_RST), ZPU_SR_SW_RST_ADC_IDELAYCTRL); - mb.zpu_ctrl->poke32(SR_ADDR(SET0_BASE, ZPU_SR_SW_RST), 0); - - // Wait for the ADC IDELAYCTRL to be ready - if (not wait_for_clk_locked( - mb, fw_regmap_t::clk_status_reg_t::IDELAYCTRL_LOCK, 0.01)) { - throw uhd::runtime_error( - (boost::format( - "ADC Calibration Clock in FPGA failed to lock to %s source.") - % source) - .str()); - } - - // Reset ADCs and DACs - for (rfnoc::x300_radio_ctrl_impl::sptr r : mb.radios) { - r->reset_codec(); - } - } - - // Update cache value - mb.current_refclk_src = source; -} - -void x300_impl::update_time_source(mboard_members_t& mb, const std::string& source) -{ - if (source == "internal") { - mb.fw_regmap->clock_ctrl_reg.write(fw_regmap_t::clk_ctrl_reg_t::PPS_SELECT, - fw_regmap_t::clk_ctrl_reg_t::SRC_INTERNAL); - } else if (source == "external") { - mb.fw_regmap->clock_ctrl_reg.write(fw_regmap_t::clk_ctrl_reg_t::PPS_SELECT, - fw_regmap_t::clk_ctrl_reg_t::SRC_EXTERNAL); - } else if (source == "gpsdo") { - mb.fw_regmap->clock_ctrl_reg.write(fw_regmap_t::clk_ctrl_reg_t::PPS_SELECT, - fw_regmap_t::clk_ctrl_reg_t::SRC_GPSDO); - } else { - throw uhd::key_error("update_time_source: unknown source: " + source); - } - - /* TODO - Implement intelligent PPS detection - //check for valid pps - if (!is_pps_present(mb)) { - throw uhd::runtime_error((boost::format("The %d PPS was not detected. Please - check the PPS source and try again.") % source).str()); - } - */ -} - -void x300_impl::sync_times(mboard_members_t& mb, const uhd::time_spec_t& t) -{ - std::vector<rfnoc::block_id_t> radio_ids = - find_blocks<rfnoc::x300_radio_ctrl_impl>("Radio"); - for (const rfnoc::block_id_t& id : radio_ids) { - get_block_ctrl<rfnoc::x300_radio_ctrl_impl>(id)->set_time_sync(t); - } - - mb.fw_regmap->clock_ctrl_reg.write(fw_regmap_t::clk_ctrl_reg_t::TIME_SYNC, 0); - mb.fw_regmap->clock_ctrl_reg.write(fw_regmap_t::clk_ctrl_reg_t::TIME_SYNC, 1); - mb.fw_regmap->clock_ctrl_reg.write(fw_regmap_t::clk_ctrl_reg_t::TIME_SYNC, 0); -} - -bool x300_impl::wait_for_clk_locked(mboard_members_t& mb, uint32_t which, double timeout) -{ - const auto timeout_time = std::chrono::steady_clock::now() - + std::chrono::milliseconds(int64_t(timeout * 1000)); - do { - if (mb.fw_regmap->clock_status_reg.read(which) == 1) { - return true; - } - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } while (std::chrono::steady_clock::now() < timeout_time); - - // Check one last time - return (mb.fw_regmap->clock_status_reg.read(which) == 1); -} - -sensor_value_t x300_impl::get_ref_locked(mboard_members_t& mb) -{ - mb.fw_regmap->clock_status_reg.refresh(); - const bool lock = - (mb.fw_regmap->clock_status_reg.get(fw_regmap_t::clk_status_reg_t::LMK_LOCK) == 1) - && (mb.fw_regmap->clock_status_reg.get( - fw_regmap_t::clk_status_reg_t::RADIO_CLK_LOCK) - == 1) - && (mb.fw_regmap->clock_status_reg.get( - fw_regmap_t::clk_status_reg_t::IDELAYCTRL_LOCK) - == 1); - return sensor_value_t("Ref", lock, "locked", "unlocked"); -} - -bool x300_impl::is_pps_present(mboard_members_t& mb) -{ - // The ZPU_RB_CLK_STATUS_PPS_DETECT bit toggles with each rising edge of the PPS. - // We monitor it for up to 1.5 seconds looking for it to toggle. - uint32_t pps_detect = - mb.fw_regmap->clock_status_reg.read(fw_regmap_t::clk_status_reg_t::PPS_DETECT); - for (int i = 0; i < 15; i++) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - if (pps_detect - != mb.fw_regmap->clock_status_reg.read( - fw_regmap_t::clk_status_reg_t::PPS_DETECT)) - return true; - } - return false; -} - -/*********************************************************************** - * Frame size detection - **********************************************************************/ -size_t x300_impl::get_mtu(const size_t mb_index, const uhd::direction_t dir) -{ - auto& mb = _mb.at(mb_index); - return mb.conn_mgr->get_mtu(dir); -} - /*********************************************************************** * compat checks **********************************************************************/ diff --git a/host/lib/usrp/x300/x300_impl.hpp b/host/lib/usrp/x300/x300_impl.hpp index 2ae586a5d..600d224a5 100644 --- a/host/lib/usrp/x300/x300_impl.hpp +++ b/host/lib/usrp/x300/x300_impl.hpp @@ -14,41 +14,64 @@ #include "x300_defaults.hpp" #include "x300_device_args.hpp" #include "x300_fw_common.h" +#include "x300_mb_controller.hpp" #include "x300_mboard_type.hpp" -#include "x300_radio_ctrl_impl.hpp" #include "x300_regs.hpp" +#include <uhd/property_tree.hpp> #include <uhd/types/device_addr.hpp> #include <uhd/types/sensors.hpp> #include <uhd/types/wb_iface.hpp> -#include <uhd/usrp/gps_ctrl.hpp> #include <uhd/usrp/subdev_spec.hpp> +#include <uhd/utils/tasks.hpp> +#include <uhdlib/rfnoc/chdr_types.hpp> +#include <uhdlib/rfnoc/clock_iface.hpp> +#include <uhdlib/rfnoc/mb_iface.hpp> +#include <uhdlib/rfnoc/mgmt_portal.hpp> +#include <uhdlib/rfnoc/rfnoc_common.hpp> +#include <uhdlib/rfnoc/rfnoc_device.hpp> +#include <uhdlib/transport/links.hpp> #include <uhdlib/usrp/cores/i2c_core_100_wb32.hpp> +#include <uhdlib/usrp/cores/spi_core_3000.hpp> #include <atomic> +#include <functional> #include <memory> +#include <mutex> + uhd::device_addrs_t x300_find(const uhd::device_addr_t& hint_); -class x300_impl : public uhd::usrp::device3_impl +class x300_impl : public uhd::rfnoc::detail::rfnoc_device { public: x300_impl(const uhd::device_addr_t&); void setup_mb(const size_t which, const uhd::device_addr_t&); ~x300_impl(void); -protected: - void subdev_to_blockid(const uhd::usrp::subdev_spec_pair_t& spec, - const size_t mb_i, - uhd::rfnoc::block_id_t& block_id, - uhd::device_addr_t& block_args); - uhd::usrp::subdev_spec_pair_t blockid_to_subdev( - const uhd::rfnoc::block_id_t& blockid, const uhd::device_addr_t& block_args); + /************************************************************************** + * rfnoc_device API + *************************************************************************/ + virtual uhd::rfnoc::mb_iface& get_mb_iface(const size_t mb_idx) + { + if (mb_idx >= _mb_ifaces.size()) { + throw uhd::index_error( + std::string("Cannot get mb_iface, invalid motherboard index: ") + + std::to_string(mb_idx)); + } + return _mb_ifaces.at(mb_idx); + } private: + /************************************************************************** + * Types + *************************************************************************/ // vector of member objects per motherboard struct mboard_members_t { uhd::usrp::x300::x300_device_args_t args; + //! Remote Device ID for this motherboard + uhd::rfnoc::device_id_t device_id; + bool initialization_done = false; uhd::task::sptr claimer_task; uhd::usrp::x300::xport_path_t xport_path; @@ -62,9 +85,6 @@ private: // other perifs on mboard x300_clock_ctrl::sptr clock; - uhd::gps_ctrl::sptr gps; - - uhd::usrp::x300::fw_regmap_t::sptr fw_regmap; // which FPGA image is loaded std::string loaded_fpga_image; @@ -72,46 +92,66 @@ private: size_t hw_rev; std::string current_refclk_src; - std::vector<uhd::rfnoc::x300_radio_ctrl_impl::sptr> radios; - uhd::usrp::x300::conn_manager::sptr conn_mgr; }; - std::vector<mboard_members_t> _mb; - - std::atomic<size_t> _sid_framer; - - uhd::sid_t allocate_sid(mboard_members_t& mb, - const uhd::sid_t& address, - const uint32_t src_addr, - const uint32_t src_dst); - uhd::both_xports_t make_transport(const uhd::sid_t& address, - const xport_type_t xport_type, - const uhd::device_addr_t& args); - //! get mtu - size_t get_mtu(const size_t, const uhd::direction_t); - - bool _ignore_cal_file; - - void update_clock_control(mboard_members_t&); - void initialize_clock_control(mboard_members_t& mb); - void set_time_source_out(mboard_members_t&, const bool); - void update_clock_source(mboard_members_t&, const std::string&); - void update_time_source(mboard_members_t&, const std::string&); - void sync_times(mboard_members_t&, const uhd::time_spec_t&); - - uhd::sensor_value_t get_ref_locked(mboard_members_t& mb); - bool wait_for_clk_locked(mboard_members_t& mb, uint32_t which, double timeout); - bool is_pps_present(mboard_members_t& mb); + //! X300-Specific Implementation of rfnoc::mb_iface + class x300_mb_iface : public uhd::rfnoc::mb_iface + { + public: + x300_mb_iface(uhd::usrp::x300::conn_manager::sptr conn_mgr, + const double radio_clk_freq, + const uhd::rfnoc::device_id_t remote_dev_id); + ~x300_mb_iface(); + uint16_t get_proto_ver(); + uhd::rfnoc::chdr_w_t get_chdr_w(); + uhd::endianness_t get_endianness(const uhd::rfnoc::device_id_t local_device_id); + uhd::rfnoc::device_id_t get_remote_device_id(); + std::vector<uhd::rfnoc::device_id_t> get_local_device_ids(); + uhd::transport::adapter_id_t get_adapter_id(const uhd::rfnoc::device_id_t local_device_id); + void reset_network(); + uhd::rfnoc::clock_iface::sptr get_clock_iface(const std::string& clock_name); + uhd::rfnoc::chdr_ctrl_xport::sptr make_ctrl_transport( + uhd::rfnoc::device_id_t local_device_id, + const uhd::rfnoc::sep_id_t& local_epid); + uhd::rfnoc::chdr_rx_data_xport::uptr make_rx_data_transport( + uhd::rfnoc::mgmt::mgmt_portal& mgmt_portal, + const uhd::rfnoc::sep_addr_pair_t& addrs, + const uhd::rfnoc::sep_id_pair_t& epids, + const uhd::rfnoc::sw_buff_t pyld_buff_fmt, + const uhd::rfnoc::sw_buff_t mdata_buff_fmt, + const uhd::device_addr_t& xport_args); + uhd::rfnoc::chdr_tx_data_xport::uptr make_tx_data_transport( + uhd::rfnoc::mgmt::mgmt_portal& mgmt_portal, + const uhd::rfnoc::sep_addr_pair_t& addrs, + const uhd::rfnoc::sep_id_pair_t& epids, + const uhd::rfnoc::sw_buff_t pyld_buff_fmt, + const uhd::rfnoc::sw_buff_t mdata_buff_fmt, + const uhd::device_addr_t& xport_args); + + private: + const uhd::rfnoc::device_id_t _remote_dev_id; + std::unordered_map<uhd::rfnoc::device_id_t, uhd::transport::adapter_id_t> _adapter_map; + uhd::rfnoc::clock_iface::sptr _bus_clk; + uhd::rfnoc::clock_iface::sptr _radio_clk; + uhd::usrp::x300::conn_manager::sptr _conn_mgr; + }; + /************************************************************************** + * Private Methods + *************************************************************************/ void check_fw_compat(const uhd::fs_path& mb_path, const mboard_members_t& members); void check_fpga_compat(const uhd::fs_path& mb_path, const mboard_members_t& members); - /// More IO stuff - uhd::device_addr_t get_tx_hints(size_t mb_index); - uhd::device_addr_t get_rx_hints(size_t mb_index); + /************************************************************************** + * Private Attributes + *************************************************************************/ + std::vector<mboard_members_t> _mb; + + std::mutex _mb_iface_mutex; + std::unordered_map<size_t, x300_mb_iface> _mb_ifaces; - void post_streamer_hooks(uhd::direction_t dir); + static const uhd::rfnoc::chdr::chdr_packet_factory _pkt_factory; }; #endif /* INCLUDED_X300_IMPL_HPP */ diff --git a/host/lib/usrp/x300/x300_io_impl.cpp b/host/lib/usrp/x300/x300_io_impl.cpp deleted file mode 100644 index 07e93173a..000000000 --- a/host/lib/usrp/x300/x300_io_impl.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// -// Copyright 2013-2014 Ettus Research LLC -// Copyright 2018 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "x300_impl.hpp" -#include "x300_regs.hpp" - -using namespace uhd; -using namespace uhd::usrp; - -/*********************************************************************** - * Hooks for get_tx_stream() and get_rx_stream() - **********************************************************************/ -device_addr_t x300_impl::get_rx_hints(size_t mb_index) -{ - device_addr_t rx_hints = _mb[mb_index].recv_args; - return rx_hints; -} - - -device_addr_t x300_impl::get_tx_hints(size_t mb_index) -{ - device_addr_t tx_hints = _mb[mb_index].send_args; - return tx_hints; -} - -void x300_impl::post_streamer_hooks(direction_t dir) -{ - if (dir != TX_DIRECTION) { - return; - } - - // Loop through all tx streamers. Find all radios connected to one - // streamer. Sync those. - for (const boost::weak_ptr<uhd::tx_streamer>& streamer_w : _tx_streamers.vals()) { - const boost::shared_ptr<device3_send_packet_streamer> streamer = - boost::dynamic_pointer_cast<device3_send_packet_streamer>(streamer_w.lock()); - if (not streamer) { - continue; - } - - std::vector<rfnoc::x300_radio_ctrl_impl::sptr> radio_ctrl_blks = - streamer->get_terminator() - ->find_downstream_node<rfnoc::x300_radio_ctrl_impl>(); - try { - // UHD_LOGGER_INFO("X300") << "[X300] syncing " << radio_ctrl_blks.size() << " - // radios " ; - rfnoc::x300_radio_ctrl_impl::synchronize_dacs(radio_ctrl_blks); - } catch (const uhd::io_error& ex) { - throw uhd::io_error( - str(boost::format("Failed to sync DACs! %s ") % ex.what())); - } - } -} - -// vim: sw=4 expandtab: diff --git a/host/lib/usrp/x300/x300_mb_controller.cpp b/host/lib/usrp/x300/x300_mb_controller.cpp index 9762f486d..fd70526ab 100644 --- a/host/lib/usrp/x300/x300_mb_controller.cpp +++ b/host/lib/usrp/x300/x300_mb_controller.cpp @@ -5,34 +5,751 @@ // #include "x300_mb_controller.hpp" +#include "x300_fw_common.h" +#include "x300_regs.hpp" +#include <uhd/exception.hpp> +#include <uhdlib/utils/narrow.hpp> +#include <chrono> +#include <thread> +uhd::uart_iface::sptr x300_make_uart_iface(uhd::wb_iface::sptr iface); + +using namespace uhd; using namespace uhd::rfnoc; +using namespace uhd::usrp::x300; +using namespace std::chrono_literals; + +namespace { +constexpr uint32_t DONT_LOOK_FOR_GPSDO = 0x1234abcdul; + +constexpr uint32_t ADC_SELF_TEST_DURATION = 100; // ms + +// When these regs are fixed, there is another fixme below to actually init the +// timekeepers +constexpr uint32_t TK_NUM_TIMEKEEPERS = 12; //Read-only +constexpr uint32_t TK_REG_BASE = 100; +constexpr uint32_t TK_REG_OFFSET = 48; +constexpr uint32_t TK_REG_TICKS_NOW_LO = 0x00; // Read-only +constexpr uint32_t TK_REG_TICKS_NOW_HI = 0x04; // Read-only +constexpr uint32_t TK_REG_TICKS_EVENT_LO = 0x08; // Write-only +constexpr uint32_t TK_REG_TICKS_EVENT_HI = 0x0C; // Write-only +constexpr uint32_t TK_REG_TICKS_CTRL = 0x10; // Write-only +constexpr uint32_t TK_REG_TICKS_PPS_LO = 0x14; // Read-only +constexpr uint32_t TK_REG_TICKS_PPS_HI = 0x18; // Read-only +constexpr uint32_t TK_REG_TICKS_PERIOD_LO = 0x1C; // Read-Write +constexpr uint32_t TK_REG_TICKS_PERIOD_HI = 0x20; // Read-Write + +constexpr char LOG_ID[] = "X300::MB_CTRL"; + +} // namespace + + +/****************************************************************************** + * Structors + *****************************************************************************/ +x300_mb_controller::x300_mb_controller(const size_t hw_rev, + const std::string product_name, + uhd::i2c_iface::sptr zpu_i2c, + uhd::wb_iface::sptr zpu_ctrl, + x300_clock_ctrl::sptr clock_ctrl, + uhd::usrp::mboard_eeprom_t mb_eeprom, + x300_device_args_t args) + : _hw_rev(hw_rev) + , _product_name(product_name) + , _zpu_i2c(zpu_i2c) + , _zpu_ctrl(zpu_ctrl) + , _clock_ctrl(clock_ctrl) + , _mb_eeprom(mb_eeprom) + , _args(args) +{ + _fw_regmap = std::make_shared<fw_regmap_t>(); + _fw_regmap->initialize(*_zpu_ctrl.get(), true); + _fw_regmap->ref_freq_reg.write( + fw_regmap_t::ref_freq_reg_t::REF_FREQ, uint32_t(args.get_system_ref_rate())); + // Initialize clock source to generate a valid radio clock. This may change + // after configuration is done. + // This will configure the LMK and wait for lock + x300_mb_controller::set_clock_source(args.get_clock_source()); + x300_mb_controller::set_time_source(args.get_time_source()); + const size_t num_tks = _zpu_ctrl->peek32(SR_ADDR(SET0_BASE, TK_NUM_TIMEKEEPERS)); + for (size_t i = 0; i < num_tks; i++) { + register_timekeeper(i, std::make_shared<x300_timekeeper>(i, _zpu_ctrl, clock_ctrl->get_master_clock_rate())); + } + + init_gps(); + _radio_refs.reserve(2); +} + +x300_mb_controller::~x300_mb_controller() {} + +/****************************************************************************** + * Timekeeper APIs + *****************************************************************************/ uint64_t x300_mb_controller::x300_timekeeper::get_ticks_now() { - // tbw - return 0; + uint32_t ticks_lo = _zpu_ctrl->peek32(get_tk_addr(TK_REG_TICKS_NOW_LO)); + uint32_t ticks_hi = _zpu_ctrl->peek32(get_tk_addr(TK_REG_TICKS_NOW_HI)); + return uint64_t(ticks_lo) | (uint64_t(ticks_hi) << 32); } uint64_t x300_mb_controller::x300_timekeeper::get_ticks_last_pps() { - // tbw - return 0; + uint32_t ticks_lo = _zpu_ctrl->peek32(get_tk_addr(TK_REG_TICKS_PPS_LO)); + uint32_t ticks_hi = _zpu_ctrl->peek32(get_tk_addr(TK_REG_TICKS_PPS_HI)); + return uint64_t(ticks_lo) | (uint64_t(ticks_hi) << 32); } void x300_mb_controller::x300_timekeeper::set_ticks_now(const uint64_t ticks) { - // tbw + _zpu_ctrl->poke32( + get_tk_addr(TK_REG_TICKS_EVENT_LO), narrow_cast<uint32_t>(ticks & 0xFFFFFFFF)); + _zpu_ctrl->poke32( + get_tk_addr(TK_REG_TICKS_EVENT_HI), narrow_cast<uint32_t>(ticks >> 32)); + _zpu_ctrl->poke32( + get_tk_addr(TK_REG_TICKS_CTRL), narrow_cast<uint32_t>(0x1)); } void x300_mb_controller::x300_timekeeper::set_ticks_next_pps(const uint64_t ticks) { - // tbw + _zpu_ctrl->poke32( + get_tk_addr(TK_REG_TICKS_EVENT_LO), narrow_cast<uint32_t>(ticks & 0xFFFFFFFF)); + _zpu_ctrl->poke32( + get_tk_addr(TK_REG_TICKS_EVENT_HI), narrow_cast<uint32_t>(ticks >> 32)); + _zpu_ctrl->poke32( + get_tk_addr(TK_REG_TICKS_CTRL), narrow_cast<uint32_t>(0x2)); } void x300_mb_controller::x300_timekeeper::set_period(const uint64_t period_ns) { - // tbw + _zpu_ctrl->poke32(get_tk_addr(TK_REG_TICKS_PERIOD_LO), + narrow_cast<uint32_t>(period_ns & 0xFFFFFFFF)); + _zpu_ctrl->poke32(get_tk_addr(TK_REG_TICKS_PERIOD_HI), + narrow_cast<uint32_t>(period_ns >> 32)); +} + +uint32_t x300_mb_controller::x300_timekeeper::get_tk_addr(const uint32_t tk_addr) +{ + return SR_ADDR(SET0_BASE, TK_REG_BASE + TK_REG_OFFSET * _tk_idx + tk_addr); +} + +/****************************************************************************** + * Motherboard Control API (see mb_controller.hpp) + *****************************************************************************/ +void x300_mb_controller::init() +{ + if (_radio_refs.empty()) { + UHD_LOG_WARNING(LOG_ID, "No radio registered! Skipping ADC checks."); + return; + } + // Check ADCs + if (_args.get_ext_adc_self_test()) { + extended_adc_test(_args.get_ext_adc_self_test_duration() / _radio_refs.size()); + } else if (_args.get_self_cal_adc_delay()) { + constexpr bool apply_delay = true; + self_cal_adc_xfer_delay(apply_delay); + } else { + for (auto& radio : _radio_refs) { + radio->self_test_adc(ADC_SELF_TEST_DURATION); + } + } +} + +std::string x300_mb_controller::get_mboard_name() const +{ + return _product_name; +} + +void x300_mb_controller::set_time_source(const std::string& source) +{ + if (source == "internal") { + _fw_regmap->clock_ctrl_reg.write(fw_regmap_t::clk_ctrl_reg_t::PPS_SELECT, + fw_regmap_t::clk_ctrl_reg_t::SRC_INTERNAL); + } else if (source == "external") { + _fw_regmap->clock_ctrl_reg.write(fw_regmap_t::clk_ctrl_reg_t::PPS_SELECT, + fw_regmap_t::clk_ctrl_reg_t::SRC_EXTERNAL); + } else if (source == "gpsdo") { + _fw_regmap->clock_ctrl_reg.write(fw_regmap_t::clk_ctrl_reg_t::PPS_SELECT, + fw_regmap_t::clk_ctrl_reg_t::SRC_GPSDO); + } else { + throw uhd::key_error("update_time_source: unknown source: " + source); + } + + /* TODO - Implement intelligent PPS detection + //check for valid pps + if (!is_pps_present(mb)) { + throw uhd::runtime_error((boost::format("The %d PPS was not detected. Please + check the PPS source and try again.") % source).str()); + } + */ +} + +std::string x300_mb_controller::get_time_source() const +{ + return _current_time_src; +} + +std::vector<std::string> x300_mb_controller::get_time_sources() const +{ + return {"internal", "external", "gpsdo"}; +} + +void x300_mb_controller::set_clock_source(const std::string& source) +{ + UHD_LOG_TRACE("X300::MB_CTRL", "Setting clock source to " << source); + // Optimize for the case when the current source is internal and we are trying + // to set it to internal. This is the only case where we are guaranteed that + // the clock has not gone away so we can skip setting the MUX and reseting the LMK. + const bool reconfigure_clks = (_current_refclk_src != "internal") + or (source != "internal"); + if (reconfigure_clks) { + // Update the clock MUX on the motherboard to select the requested source + if (source == "internal") { + _fw_regmap->clock_ctrl_reg.set(fw_regmap_t::clk_ctrl_reg_t::CLK_SOURCE, + fw_regmap_t::clk_ctrl_reg_t::SRC_INTERNAL); + _fw_regmap->clock_ctrl_reg.set(fw_regmap_t::clk_ctrl_reg_t::TCXO_EN, 1); + } else if (source == "external") { + _fw_regmap->clock_ctrl_reg.set(fw_regmap_t::clk_ctrl_reg_t::CLK_SOURCE, + fw_regmap_t::clk_ctrl_reg_t::SRC_EXTERNAL); + _fw_regmap->clock_ctrl_reg.set(fw_regmap_t::clk_ctrl_reg_t::TCXO_EN, 0); + } else if (source == "gpsdo") { + _fw_regmap->clock_ctrl_reg.set(fw_regmap_t::clk_ctrl_reg_t::CLK_SOURCE, + fw_regmap_t::clk_ctrl_reg_t::SRC_GPSDO); + _fw_regmap->clock_ctrl_reg.set(fw_regmap_t::clk_ctrl_reg_t::TCXO_EN, 0); + } else { + throw uhd::key_error("set_clock_source: unknown source: " + source); + } + _fw_regmap->clock_ctrl_reg.flush(); + + // Reset the LMK to make sure it re-locks to the new reference + _clock_ctrl->reset_clocks(); + } + + // Wait for the LMK to lock (always, as a sanity check that the clock is useable) + //* Currently the LMK can take as long as 30 seconds to lock to a reference but we + // don't + //* want to wait that long during initialization. + // TODO: Need to verify timeout and settings to make sure lock can be achieved in + // < 1.0 seconds + double timeout = _initialization_done ? 30.0 : 1.0; + + // The programming code in x300_clock_ctrl is not compatible with revs <= 4 and may + // lead to locking issues. So, disable the ref-locked check for older (unsupported) + // boards. + if (_hw_rev > 4) { + if (not wait_for_clk_locked(fw_regmap_t::clk_status_reg_t::LMK_LOCK, timeout)) { + // failed to lock on reference + if (_initialization_done) { + throw uhd::runtime_error( + (boost::format("Reference Clock PLL failed to lock to %s source.") + % source) + .str()); + } else { + // TODO: Re-enable this warning when we figure out a reliable lock time + // UHD_LOGGER_WARNING("X300::MB_CTRL") << "Reference clock failed to lock to " + + // source + " during device initialization. " << + // "Check for the lock before operation or ignore this warning if using + // another clock source." ; + } + } + } + + if (reconfigure_clks) { + // Reset the radio clock PLL in the FPGA + _zpu_ctrl->poke32(SR_ADDR(SET0_BASE, ZPU_SR_SW_RST), ZPU_SR_SW_RST_RADIO_CLK_PLL); + _zpu_ctrl->poke32(SR_ADDR(SET0_BASE, ZPU_SR_SW_RST), 0); + + // Wait for radio clock PLL to lock + if (not wait_for_clk_locked( + fw_regmap_t::clk_status_reg_t::RADIO_CLK_LOCK, 0.01)) { + throw uhd::runtime_error( + (boost::format("Reference Clock PLL in FPGA failed to lock to %s source.") + % source) + .str()); + } + + // Reset the IDELAYCTRL used to calibrate the data interface delays + _zpu_ctrl->poke32( + SR_ADDR(SET0_BASE, ZPU_SR_SW_RST), ZPU_SR_SW_RST_ADC_IDELAYCTRL); + _zpu_ctrl->poke32(SR_ADDR(SET0_BASE, ZPU_SR_SW_RST), 0); + + // Wait for the ADC IDELAYCTRL to be ready + if (not wait_for_clk_locked( + fw_regmap_t::clk_status_reg_t::IDELAYCTRL_LOCK, 0.01)) { + throw uhd::runtime_error( + (boost::format( + "ADC Calibration Clock in FPGA failed to lock to %s source.") + % source) + .str()); + } + + // Reset ADCs and DACs + reset_codecs(); + } + + // Update cache value + _current_refclk_src = source; +} + +std::string x300_mb_controller::get_clock_source() const +{ + return _current_refclk_src; +} + +std::vector<std::string> x300_mb_controller::get_clock_sources() const +{ + return {"internal", "external", "gpsdo"}; +} + +void x300_mb_controller::set_sync_source( + const std::string& clock_source, const std::string& time_source) +{ + device_addr_t sync_args; + sync_args["clock_source"] = clock_source; + sync_args["time_source"] = time_source; + set_sync_source(sync_args); +} + +void x300_mb_controller::set_sync_source(const device_addr_t& sync_source) { + if (sync_source.has_key("clock_source")) { + set_clock_source(sync_source["clock_source"]); + } + if (sync_source.has_key("time_source")) { + set_time_source(sync_source["time_source"]); + } +} + +device_addr_t x300_mb_controller::get_sync_source() const +{ + const std::string clock_source = get_clock_source(); + const std::string time_source = get_time_source(); + device_addr_t sync_source; + sync_source["clock_source"] = clock_source; + sync_source["time_source"] = time_source; + return sync_source; +} + +std::vector<device_addr_t> x300_mb_controller::get_sync_sources() +{ + const std::vector<std::pair<std::string, std::string>> clock_time_src_pairs = { + // Clock source, Time source + {"internal", "internal"}, + {"external", "internal"}, + {"external", "external"}, + {"gpsdo", "gpsdo"}, + {"gpsdo", "internal"} + }; + + // Now convert to vector of device_addr_t + std::vector<device_addr_t> sync_sources; + for (const auto& ct_pair : clock_time_src_pairs) { + device_addr_t sync_source; + sync_source["clock_source"] = ct_pair.first; + sync_source["time_source"] = ct_pair.second; + sync_sources.push_back(sync_source); + } + return sync_sources; +} + +void x300_mb_controller::set_clock_source_out(const bool enb) +{ + _clock_ctrl->set_ref_out(enb); } +void x300_mb_controller::set_time_source_out(const bool enb) +{ + _fw_regmap->clock_ctrl_reg.write( + fw_regmap_t::clk_ctrl_reg_t::PPS_OUT_EN, enb ? 1 : 0); +} + +sensor_value_t x300_mb_controller::get_sensor(const std::string& name) +{ + if (name == "ref_locked") { + return sensor_value_t("Ref", get_ref_locked(), "locked", "unlocked"); + } + // There are only GPS sensors and ref_locked, so we can take a shortcut here + // and directly ask the GPS for its sensor value: + if (_sensors.count(name)) { + return _gps->get_sensor(name); + } + throw uhd::key_error(std::string("Invalid sensor name: ") + name); +} + +std::vector<std::string> x300_mb_controller::get_sensor_names() +{ + return std::vector<std::string>(_sensors.cbegin(), _sensors.cend()); +} + +uhd::usrp::mboard_eeprom_t x300_mb_controller::get_eeprom() +{ + return _mb_eeprom; +} + +bool x300_mb_controller::synchronize(std::vector<mb_controller::sptr>& mb_controllers, + const uhd::time_spec_t& time_spec, + const bool quiet) +{ + if (!mb_controller::synchronize(mb_controllers, time_spec, quiet)) { + return false; + } + + std::vector<std::shared_ptr<x300_mb_controller>> mb_controller_copy; + mb_controller_copy.reserve(mb_controllers.size()); + for (auto mb_controller : mb_controllers) { + if (std::dynamic_pointer_cast<x300_mb_controller>(mb_controller)) { + mb_controller_copy.push_back( + std::dynamic_pointer_cast<x300_mb_controller>(mb_controller)); + } + } + // Now, mb_controller_copy contains only references of mb_controllers that + // are actually x300_mb_controllers + mb_controllers.clear(); + for (auto mb_controller : mb_controller_copy) { + mb_controllers.push_back(mb_controller); + } + + // Now we have the housekeeping out of the way, we can actually start + // synchronizing. The X300 needs to sync its DACs. First, we get a reference + // to all the radios (and thus to the DACs). + std::vector<uhd::usrp::x300::x300_radio_mbc_iface*> radios; + radios.reserve(2 * mb_controller_copy.size()); + for (auto& mbc : mb_controller_copy) { + for (auto radio_ref : mbc->_radio_refs) { + radios.push_back(radio_ref); + } + } + + UHD_LOG_TRACE(LOG_ID, "Running DAC sync on " << radios.size() << " radios."); + + // **PRECONDITION** + // This function assumes that all the VITA times for "radios" are + // synchronized to a common reference, which we did earlier. + + // Get a rough estimate of the cumulative command latency + auto t_start = std::chrono::steady_clock::now(); + for (auto radio : radios) { + radio->get_adc_rx_word(); // Discard value. We are just timing the call + } + auto t_elapsed = std::chrono::duration_cast<std::chrono::microseconds>( + std::chrono::steady_clock::now() - t_start); + // Add 100% of headroom + uncertainty to the command time + uint64_t t_sync_us = (t_elapsed.count() * 2) + 16000 /* Scheduler latency */; + + const double radio_clk_rate = _clock_ctrl->get_master_clock_rate(); + std::string err_str; + // Try to sync 3 times before giving up + constexpr size_t MAX_ATTEMPTS = 3; + for (size_t attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + try { + // Reinitialize and resync all DACs + for (auto radio : radios) { + radio->sync_dac(); + } + + // Make sure FRAMEP/N is 0 + for (auto radio : radios) { + radio->set_dac_sync(false); + } + + // Pick radios[0] as the time reference. + uhd::time_spec_t sync_time = + mb_controller_copy.front()->get_timekeeper(0)->get_time_now() + + uhd::time_spec_t(((double)t_sync_us) / 1e6); + + // Send the sync command + for (auto radio : radios) { + // Arm FRAMEP/N sync pulse by asserting a rising edge + radio->set_dac_sync(true, sync_time); + } + + // Reset FRAMEP/N to 0 after 2 clock cycles, and reset command time + for (auto radio : radios) { + radio->set_dac_sync(false, sync_time + (2.0 / radio_clk_rate)); + } + + // Wait and check status + std::this_thread::sleep_for(std::chrono::microseconds(t_sync_us)); + for (auto radio : radios) { + radio->dac_verify_sync(); + } + + UHD_LOG_TRACE(LOG_ID, "DAC sync passed on attempt " << attempt); + return true; + } catch (const uhd::runtime_error& e) { + err_str = e.what(); + RFNOC_LOG_DEBUG("Retrying DAC synchronization: " << err_str); + } + } + throw uhd::runtime_error(err_str); +} + +/****************************************************************************** + * Private Methods + *****************************************************************************/ +std::string x300_mb_controller::get_unique_id() +{ + return std::string("X300::MB_CTRL") + ""; // FIXME +} + +void x300_mb_controller::init_gps() +{ + // otherwise if not disabled, look for the internal GPSDO + if (_zpu_ctrl->peek32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_GPSDO_STATUS)) + != DONT_LOOK_FOR_GPSDO) { + UHD_LOG_TRACE("X300::MB_CTRL", "Detecting internal GPSDO...."); + try { + // gps_ctrl will print its own log statements if a GPSDO was found + _gps = gps_ctrl::make(x300_make_uart_iface(_zpu_ctrl)); + } catch (std::exception& e) { + UHD_LOGGER_WARNING("X300::MB_CTRL") + << "An error occurred making GPSDO control: " << e.what() + << " Continuing without GPS."; + } + if (_gps and _gps->gps_detected()) { + auto sensors = _gps->get_sensors(); + _sensors.insert(sensors.cbegin(), sensors.cend()); + } else { + UHD_LOG_TRACE("X300::MB_CTRL", + "No GPS found, setting register to save time on next run."); + _zpu_ctrl->poke32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_GPSDO_STATUS), + DONT_LOOK_FOR_GPSDO); + } + } else { + UHD_LOG_TRACE("X300::MB_CTRL", + "Not detecting internal GPSDO, previous run already failed to find it."); + } +} + +void x300_mb_controller::reset_codecs() +{ + for (auto& callback : _reset_cbs) { + UHD_LOG_TRACE("X300::MB_CTRL", "Calling DAC/ADC reset callback"); + callback(); + } +} + +bool x300_mb_controller::wait_for_clk_locked(uint32_t which, double timeout) +{ + const auto timeout_time = std::chrono::steady_clock::now() + + std::chrono::milliseconds(int64_t(timeout * 1000)); + do { + if (_fw_regmap->clock_status_reg.read(which) == 1) { + return true; + } + std::this_thread::sleep_for(5ms); + } while (std::chrono::steady_clock::now() < timeout_time); + + // Check one last time + return (_fw_regmap->clock_status_reg.read(which) == 1); +} + +bool x300_mb_controller::is_pps_present() +{ + // The ZPU_RB_CLK_STATUS_PPS_DETECT bit toggles with each rising edge of the PPS. + // We monitor it for up to 1.5 seconds looking for it to toggle. + uint32_t pps_detect = + _fw_regmap->clock_status_reg.read(fw_regmap_t::clk_status_reg_t::PPS_DETECT); + const auto timeout_time = std::chrono::steady_clock::now() + 1500ms; + while (std::chrono::steady_clock::now() < timeout_time) { + std::this_thread::sleep_for(100ms); + if (pps_detect + != _fw_regmap->clock_status_reg.read( + fw_regmap_t::clk_status_reg_t::PPS_DETECT)) + return true; + } + return false; +} + +bool x300_mb_controller::get_ref_locked() +{ + _fw_regmap->clock_status_reg.refresh(); + return (_fw_regmap->clock_status_reg.get(fw_regmap_t::clk_status_reg_t::LMK_LOCK) + == 1) + && (_fw_regmap->clock_status_reg.get( + fw_regmap_t::clk_status_reg_t::RADIO_CLK_LOCK) + == 1) + && (_fw_regmap->clock_status_reg.get( + fw_regmap_t::clk_status_reg_t::IDELAYCTRL_LOCK) + == 1); +} + +void x300_mb_controller::self_cal_adc_xfer_delay(bool apply_delay) +{ + UHD_LOG_INFO("X300", "Running ADC transfer delay self-cal: "); + + // Effective resolution of the self-cal. + constexpr size_t NUM_DELAY_STEPS = 100; + + double master_clk_period = (1.0e9 / _clock_ctrl->get_master_clock_rate()); // in ns + double delay_start = 0.0; + double delay_range = 2 * master_clk_period; + double delay_incr = delay_range / NUM_DELAY_STEPS; + + double cached_clk_delay = _clock_ctrl->get_clock_delay(X300_CLOCK_WHICH_ADC0); + double fpga_clk_delay = _clock_ctrl->get_clock_delay(X300_CLOCK_WHICH_FPGA); + + // Iterate through several values of delays and measure ADC data integrity + std::vector<std::pair<double, bool>> results; + for (size_t i = 0; i < NUM_DELAY_STEPS; i++) { + // Delay the ADC clock (will set both Ch0 and Ch1 delays) + double delay = _clock_ctrl->set_clock_delay( + X300_CLOCK_WHICH_ADC0, delay_incr * i + delay_start); + wait_for_clk_locked(fw_regmap_t::clk_status_reg_t::LMK_LOCK, 0.1); + + uint32_t err_code = 0; + for (auto& radio : _radio_refs) { + // Test each channel (I and Q) individually so as to not accidentally + // trigger on the data from the other channel if there is a swap + + // -- Test I Channel -- + // Put ADC in ramp test mode. Tie the other channel to all ones. + radio->set_adc_test_word("ramp", "ones"); + // Turn on the pattern checker in the FPGA. It will lock when it sees a + // zero and count deviations from the expected value + radio->set_adc_checker_enabled(false); + radio->set_adc_checker_enabled(true); + // 50ms @ 200MHz = 10 million samples + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + if (radio->get_adc_checker_locked(true /* I */)) { + err_code += radio->get_adc_checker_error_code(true /* I */); + } else { + err_code += 100; // Increment error code by 100 to indicate no lock + } + + // -- Test Q Channel -- + // Put ADC in ramp test mode. Tie the other channel to all ones. + radio->set_adc_test_word("ones", "ramp"); + // Turn on the pattern checker in the FPGA. It will lock when it sees a + // zero and count deviations from the expected value + radio->set_adc_checker_enabled(false); + radio->set_adc_checker_enabled(true); + // 50ms @ 200MHz = 10 million samples + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + if (radio->get_adc_checker_locked(false /* Q */)) { + err_code += radio->get_adc_checker_error_code(false /* Q */); + } else { + err_code += 100; // Increment error code by 100 to indicate no lock + } + } + UHD_LOG_TRACE( + LOG_ID, boost::format("XferDelay=%fns, Error=%d") % delay % err_code); + results.push_back(std::pair<double, bool>(delay, err_code == 0)); + } + + // Calculate the valid window + // When done win_start_idx will have the first delay value index that caused + // no errors, and win_stop_idx will have the last valid delay value index + int win_start_idx = -1, win_stop_idx = -1, cur_start_idx = -1, cur_stop_idx = -1; + for (size_t i = 0; i < results.size(); i++) { + std::pair<double, bool>& item = results[i]; + if (item.second) { // If data is stable + if (cur_start_idx == -1) { // This is the first window + cur_start_idx = i; + cur_stop_idx = i; + } else { // We are extending the window + cur_stop_idx = i; + } + } else { + if (cur_start_idx == -1) { // We haven't yet seen valid data + // Do nothing + } else if (win_start_idx == -1) { // We passed the first valid window + win_start_idx = cur_start_idx; + win_stop_idx = cur_stop_idx; + } else { // Update cached window if current window is larger + double cur_win_len = + results[cur_stop_idx].first - results[cur_start_idx].first; + double cached_win_len = + results[win_stop_idx].first - results[win_start_idx].first; + if (cur_win_len > cached_win_len) { + win_start_idx = cur_start_idx; + win_stop_idx = cur_stop_idx; + } + } + // Reset current window + cur_start_idx = -1; + cur_stop_idx = -1; + } + } + if (win_start_idx == -1) { + throw uhd::runtime_error( + "self_cal_adc_xfer_delay: Self calibration failed. Convergence error."); + } + + double win_center = + (results[win_stop_idx].first + results[win_start_idx].first) / 2.0; + const double win_length = results[win_stop_idx].first - results[win_start_idx].first; + if (win_length < master_clk_period / 4) { + throw uhd::runtime_error("self_cal_adc_xfer_delay: Self calibration failed. " + "Valid window too narrow."); + } + + // Cycle slip the relative delay by a clock cycle to prevent sample misalignment + // fpga_clk_delay > 0 and 0 < win_center < 2*(1/MCR) so one cycle slip is all we need + bool cycle_slip = (win_center - fpga_clk_delay >= master_clk_period); + if (cycle_slip) { + win_center -= master_clk_period; + } + + if (apply_delay) { + // Apply delay + win_center = _clock_ctrl->set_clock_delay( + X300_CLOCK_WHICH_ADC0, win_center); // Sets ADC0 and ADC1 + wait_for_clk_locked(fw_regmap_t::clk_status_reg_t::LMK_LOCK, 0.1); + // Validate + for (auto radio_ref : _radio_refs) { + radio_ref->self_test_adc(2000); + } + } else { + // Restore delay + _clock_ctrl->set_clock_delay( + X300_CLOCK_WHICH_ADC0, cached_clk_delay); // Sets ADC0 and ADC1 + } + + // Teardown + for (auto& radio : _radio_refs) { + radio->set_adc_test_word("normal", "normal"); + radio->set_adc_checker_enabled(false); + } + UHD_LOGGER_INFO(LOG_ID) + << (boost::format("ADC transfer delay self-cal done (FPGA->ADC=%.3fns%s, " + "Window=%.3fns)") + % (win_center - fpga_clk_delay) % (cycle_slip ? " +cyc" : "") + % win_length); +} + +void x300_mb_controller::extended_adc_test(double duration_s) +{ + static const size_t SECS_PER_ITER = 5; + RFNOC_LOG_INFO( + boost::format("Running Extended ADC Self-Test (Duration=%.0fs, %ds/iteration)...") + % duration_s % SECS_PER_ITER); + + size_t num_iters = static_cast<size_t>(ceil(duration_s / SECS_PER_ITER)); + size_t num_failures = 0; + for (size_t iter = 0; iter < num_iters; iter++) { + // Run self-test + RFNOC_LOG_INFO( + boost::format("Extended ADC Self-Test Iteration %06d... ") % (iter + 1)); + try { + for (auto& radio : _radio_refs) { + radio->self_test_adc(SECS_PER_ITER * 1000); + } + RFNOC_LOG_INFO(boost::format("Extended ADC Self-Test Iteration %06d passed ") + % (iter + 1)); + } catch (std::exception& e) { + num_failures++; + RFNOC_LOG_ERROR(e.what()); + } + } + if (num_failures == 0) { + RFNOC_LOG_INFO("Extended ADC Self-Test PASSED"); + } else { + const std::string err_msg = + (boost::format("Extended ADC Self-Test FAILED!!! (%d/%d failures)") + % num_failures % num_iters) + .str(); + RFNOC_LOG_ERROR(err_msg); + throw uhd::runtime_error(err_msg); + } +} diff --git a/host/lib/usrp/x300/x300_mb_controller.hpp b/host/lib/usrp/x300/x300_mb_controller.hpp index 9a220ab00..53f166a0e 100644 --- a/host/lib/usrp/x300/x300_mb_controller.hpp +++ b/host/lib/usrp/x300/x300_mb_controller.hpp @@ -8,26 +8,45 @@ #define INCLUDED_LIBUHD_X300_MB_CONTROLLER_HPP #include "x300_clock_ctrl.hpp" +#include "x300_device_args.hpp" +#include "x300_radio_mbc_iface.hpp" +#include "x300_regs.hpp" #include <uhd/rfnoc/mb_controller.hpp> +#include <uhd/types/sensors.hpp> #include <uhd/types/wb_iface.hpp> +#include <uhd/usrp/gps_ctrl.hpp> +#include <unordered_set> namespace uhd { namespace rfnoc { /*! X300-Specific version of the mb_controller * * Reminder: There is one of these per motherboard. + * + * The X300 motherboard controller is responsible for: + * - Controlling the timekeeper + * - Controlling all time- and clock-related settings + * - Initialize and hold the GPS control */ class x300_mb_controller : public mb_controller { public: - x300_mb_controller(uhd::i2c_iface::sptr zpu_i2c, + /************************************************************************** + * Structors + *************************************************************************/ + x300_mb_controller(const size_t hw_rev, + const std::string product_name, + uhd::i2c_iface::sptr zpu_i2c, uhd::wb_iface::sptr zpu_ctrl, - x300_clock_ctrl::sptr clock_ctrl) - : _zpu_i2c(zpu_i2c), _zpu_ctrl(zpu_ctrl), _clock_ctrl(clock_ctrl) - { - // nop - } + x300_clock_ctrl::sptr clock_ctrl, + uhd::usrp::mboard_eeprom_t mb_eeprom, + uhd::usrp::x300::x300_device_args_t args); + + ~x300_mb_controller(); + /************************************************************************** + * X300-Specific APIs + *************************************************************************/ //! Return reference to the ZPU-owned I2C controller uhd::i2c_iface::sptr get_zpu_i2c() { @@ -40,30 +59,107 @@ public: //! Return reference to LMK clock controller x300_clock_ctrl::sptr get_clock_ctrl() { return _clock_ctrl; } + void register_reset_codec_cb(std::function<void(void)>&& reset_cb) + { + _reset_cbs.push_back(std::move(reset_cb)); + } + + void set_initialization_done() { _initialization_done = true; } + + void register_radio(uhd::usrp::x300::x300_radio_mbc_iface* radio) + { + _radio_refs.push_back(radio); + } + + /************************************************************************** + * Timekeeper API + *************************************************************************/ //! X300-specific version of the timekeeper controls + // + // The X300 controls timekeepers via the ZPU class x300_timekeeper : public mb_controller::timekeeper { public: - x300_timekeeper(uhd::wb_iface::sptr zpu_ctrl) : _zpu_ctrl(zpu_ctrl) {} - + x300_timekeeper(const size_t tk_idx, uhd::wb_iface::sptr zpu_ctrl, const double tick_rate) + : _tk_idx(tk_idx), _zpu_ctrl(zpu_ctrl) + { + set_tick_rate(tick_rate); + } uint64_t get_ticks_now(); - uint64_t get_ticks_last_pps(); - void set_ticks_now(const uint64_t ticks); - void set_ticks_next_pps(const uint64_t ticks); - void set_period(const uint64_t period_ns); private: + uint32_t get_tk_addr(const uint32_t tk_addr); + const size_t _tk_idx; uhd::wb_iface::sptr _zpu_ctrl; - }; + }; /* x300_timekeeper */ + + /************************************************************************** + * Motherboard Control API (see mb_controller.hpp) + *************************************************************************/ + void init(); + std::string get_mboard_name() const; + void set_time_source(const std::string& source); + std::string get_time_source() const; + std::vector<std::string> get_time_sources() const; + void set_clock_source(const std::string& source); + std::string get_clock_source() const; + std::vector<std::string> get_clock_sources() const; + void set_sync_source(const std::string& clock_source, const std::string& time_source); + void set_sync_source(const device_addr_t& sync_source); + device_addr_t get_sync_source() const; + std::vector<device_addr_t> get_sync_sources(); + void set_clock_source_out(const bool enb); + void set_time_source_out(const bool enb); + sensor_value_t get_sensor(const std::string& name); + std::vector<std::string> get_sensor_names(); + uhd::usrp::mboard_eeprom_t get_eeprom(); + bool synchronize(std::vector<mb_controller::sptr>& mb_controllers, + const uhd::time_spec_t& time_spec = uhd::time_spec_t(0.0), + const bool quiet = false); private: + //! Return a string X300::MB_CTRL#N + std::string get_unique_id(); + + //! Init GPS + void init_gps(); + + //! Reset all registered DACs and ADCs + void reset_codecs(); + + //! Wait until reference clock locks, or a timeout occurs + // + // \returns lock status + bool wait_for_clk_locked(uint32_t which, double timeout); + + //! Returns true if a PPS signal is detected + bool is_pps_present(); + + //! Return LMK lock status + bool get_ref_locked(); + + /*! Calibrate the ADC transfer delay + * + * This will try various clock delay settings to the ADC, and pick the one + * with the best BER performance. + */ + void self_cal_adc_xfer_delay(const bool apply_delay); + + void extended_adc_test(double duration_s); + /************************************************************************** * Attributes *************************************************************************/ + //! Hardware revision + const size_t _hw_rev; + + //! Product name (X310, X300) + const std::string _product_name; + //! Reference to the ZPU-owned I2C controller uhd::i2c_iface::sptr _zpu_i2c; @@ -72,6 +168,37 @@ private: //! Reference to LMK clock controller x300_clock_ctrl::sptr _clock_ctrl; + + //! State of the MB EEPROM + uhd::usrp::mboard_eeprom_t _mb_eeprom; + + //! Copy of the device args + uhd::usrp::x300::x300_device_args_t _args; + + //! Reference to clock control register + uhd::usrp::x300::fw_regmap_t::sptr _fw_regmap; + + //! Reference to GPS control + uhd::gps_ctrl::sptr _gps; + + //! Reference to all callbacks to reset the ADCs/DACs + std::vector<std::function<void(void)>> _reset_cbs; + + //! Current clock source (external, internal, gpsdo) + std::string _current_refclk_src; + + //! Current time source (external, internal, gpsdo) + std::string _current_time_src; + + //! Reference to radios on this motherboard + std::vector<uhd::usrp::x300::x300_radio_mbc_iface*> _radio_refs; + + //! List of available sensors + std::unordered_set<std::string> _sensors{"ref_locked"}; + + //! Flag to tell us if initialization is complete. Some functions behave + // differently after initialization. + bool _initialization_done = false; }; }} // namespace uhd::rfnoc diff --git a/host/lib/usrp/x300/x300_mb_iface.cpp b/host/lib/usrp/x300/x300_mb_iface.cpp new file mode 100644 index 000000000..2c053080d --- /dev/null +++ b/host/lib/usrp/x300/x300_mb_iface.cpp @@ -0,0 +1,226 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "x300_impl.hpp" +#include <uhdlib/rfnoc/device_id.hpp> + +using namespace uhd::rfnoc; +using uhd::transport::link_type_t; + + +x300_impl::x300_mb_iface::x300_mb_iface(uhd::usrp::x300::conn_manager::sptr conn_mgr, + const double radio_clk_freq, + const uhd::rfnoc::device_id_t remote_dev_id) + : _remote_dev_id(remote_dev_id) + , _bus_clk(std::make_shared<uhd::rfnoc::clock_iface>( + "bus_clk", uhd::usrp::x300::BUS_CLOCK_RATE, false)) + , _radio_clk( + std::make_shared<uhd::rfnoc::clock_iface>("radio_clk", radio_clk_freq, false)) + , _conn_mgr(conn_mgr) +{ + UHD_ASSERT_THROW(_conn_mgr); + _bus_clk->set_running(true); + _radio_clk->set_running(true); +} + +x300_impl::x300_mb_iface::~x300_mb_iface() = default; + +uint16_t x300_impl::x300_mb_iface::get_proto_ver() +{ + // TODO: Get from from a hardware register + return 0x0100; +} + +uhd::rfnoc::chdr_w_t x300_impl::x300_mb_iface::get_chdr_w() +{ + // TODO: Get from from a hardware register + return uhd::rfnoc::CHDR_W_64; +} + +uhd::endianness_t x300_impl::x300_mb_iface::get_endianness( + const uhd::rfnoc::device_id_t /*local_device_id*/) +{ + // FIXME + return uhd::ENDIANNESS_BIG; +} + +uhd::rfnoc::device_id_t x300_impl::x300_mb_iface::get_remote_device_id() +{ + return _remote_dev_id; +} + +std::vector<uhd::rfnoc::device_id_t> x300_impl::x300_mb_iface::get_local_device_ids() +{ + return _conn_mgr->get_local_device_ids(); +} + +uhd::transport::adapter_id_t x300_impl::x300_mb_iface::get_adapter_id( + const uhd::rfnoc::device_id_t local_device_id) +{ + return _adapter_map[local_device_id]; +} + +void x300_impl::x300_mb_iface::reset_network() +{ + // FIXME +} + +uhd::rfnoc::clock_iface::sptr x300_impl::x300_mb_iface::get_clock_iface( + const std::string& clock_name) +{ + if (clock_name == "radio_clk") { + return _radio_clk; + } + if (clock_name == "bus_clk") { + return _bus_clk; + } + UHD_LOG_ERROR("X300", "Invalid timebase clock name: " + clock_name); + throw uhd::key_error("[X300] Invalid timebase clock name: " + clock_name); +} + +uhd::rfnoc::chdr_ctrl_xport::sptr x300_impl::x300_mb_iface::make_ctrl_transport( + uhd::rfnoc::device_id_t local_device_id, const uhd::rfnoc::sep_id_t& local_epid) +{ + uhd::transport::io_service::sptr io_srv; + uhd::transport::send_link_if::sptr send_link; + uhd::transport::recv_link_if::sptr recv_link; + std::tie(io_srv, send_link, std::ignore, recv_link, std::ignore, std::ignore) = + _conn_mgr->get_links(link_type_t::CTRL, + local_device_id, + local_epid, + uhd::rfnoc::sep_id_t(), + uhd::device_addr_t()); + + /* Associate local device ID with the adapter */ + _adapter_map[local_device_id] = send_link->get_send_adapter_id(); + + auto xport = uhd::rfnoc::chdr_ctrl_xport::make(io_srv, + send_link, + recv_link, + _pkt_factory, + local_epid, + send_link->get_num_send_frames(), + recv_link->get_num_recv_frames()); + return xport; +} + +uhd::rfnoc::chdr_rx_data_xport::uptr x300_impl::x300_mb_iface::make_rx_data_transport( + uhd::rfnoc::mgmt::mgmt_portal& mgmt_portal, + const uhd::rfnoc::sep_addr_pair_t& addrs, + const uhd::rfnoc::sep_id_pair_t& epids, + const uhd::rfnoc::sw_buff_t pyld_buff_fmt, + const uhd::rfnoc::sw_buff_t mdata_buff_fmt, + const uhd::device_addr_t& xport_args) +{ + const uhd::rfnoc::sep_addr_t local_sep_addr = addrs.second; + const uhd::rfnoc::sep_id_t remote_epid = epids.first; + const uhd::rfnoc::sep_id_t local_epid = epids.second; + + uhd::transport::io_service::sptr io_srv; + uhd::transport::send_link_if::sptr send_link; + uhd::transport::recv_link_if::sptr recv_link; + size_t recv_buff_size; + bool lossy_xport; + std::tie(io_srv, send_link, std::ignore, recv_link, recv_buff_size, lossy_xport) = + _conn_mgr->get_links(link_type_t::RX_DATA, + local_sep_addr.first, + local_epid, + remote_epid, + xport_args); + + /* Associate local device ID with the adapter */ + _adapter_map[local_sep_addr.first] = send_link->get_send_adapter_id(); + + // TODO: configure this based on the transport type + const uhd::rfnoc::stream_buff_params_t recv_capacity = { + recv_buff_size, uhd::rfnoc::MAX_FC_CAPACITY_PKTS}; + + const double ratio = 1.0 / 32; + + // Configure flow control frequency to use bytes only for UDP + uhd::rfnoc::stream_buff_params_t fc_freq = { + static_cast<uint64_t>(std::ceil(double(recv_buff_size) * ratio)), + uhd::rfnoc::MAX_FC_FREQ_PKTS}; + + uhd::rfnoc::stream_buff_params_t fc_headroom = {0, 0}; + + // Create the data transport + auto fc_params = uhd::rfnoc::chdr_rx_data_xport::configure_sep(io_srv, + recv_link, + send_link, + _pkt_factory, + mgmt_portal, + epids, + pyld_buff_fmt, + mdata_buff_fmt, + recv_capacity, + fc_freq, + fc_headroom, + lossy_xport); + + auto rx_xport = std::make_unique<uhd::rfnoc::chdr_rx_data_xport>(io_srv, + recv_link, + send_link, + _pkt_factory, + epids, + recv_link->get_num_recv_frames(), + fc_params); + + return rx_xport; +} + +uhd::rfnoc::chdr_tx_data_xport::uptr x300_impl::x300_mb_iface::make_tx_data_transport( + uhd::rfnoc::mgmt::mgmt_portal& mgmt_portal, + const uhd::rfnoc::sep_addr_pair_t& addrs, + const uhd::rfnoc::sep_id_pair_t& epids, + const uhd::rfnoc::sw_buff_t pyld_buff_fmt, + const uhd::rfnoc::sw_buff_t mdata_buff_fmt, + const uhd::device_addr_t& xport_args) +{ + const uhd::rfnoc::sep_addr_t local_sep_addr = addrs.first; + const uhd::rfnoc::sep_id_t remote_epid = epids.second; + const uhd::rfnoc::sep_id_t local_epid = epids.first; + + uhd::transport::io_service::sptr io_srv; + uhd::transport::send_link_if::sptr send_link; + uhd::transport::recv_link_if::sptr recv_link; + bool lossy_xport; + std::tie(io_srv, send_link, std::ignore, recv_link, std::ignore, lossy_xport) = + _conn_mgr->get_links(link_type_t::TX_DATA, + local_sep_addr.first, + local_epid, + remote_epid, + xport_args); + + /* Associate local device ID with the adapter */ + _adapter_map[local_sep_addr.first] = send_link->get_send_adapter_id(); + + // TODO: configure this based on the transport type + const double fc_freq_ratio = 1.0 / 8; + const double fc_headroom_ratio = 0; + + const auto buff_capacity = chdr_tx_data_xport::configure_sep(io_srv, + recv_link, + send_link, + _pkt_factory, + mgmt_portal, + epids, + pyld_buff_fmt, + mdata_buff_fmt, + fc_freq_ratio, + fc_headroom_ratio); + + // Create the data transport + auto tx_xport = std::make_unique<chdr_tx_data_xport>(io_srv, + recv_link, + send_link, + _pkt_factory, + epids, + send_link->get_num_send_frames(), + buff_capacity); + + return tx_xport; +} diff --git a/host/lib/usrp/x300/x300_pcie_mgr.cpp b/host/lib/usrp/x300/x300_pcie_mgr.cpp index 47095b370..220a96530 100644 --- a/host/lib/usrp/x300/x300_pcie_mgr.cpp +++ b/host/lib/usrp/x300/x300_pcie_mgr.cpp @@ -12,22 +12,21 @@ #include "x300_mboard_type.hpp" #include "x300_regs.hpp" #include "x310_lvbitx.hpp" -#include <uhd/transport/nirio_zero_copy.hpp> -#include <uhd/transport/zero_copy.hpp> #include <uhd/types/device_addr.hpp> -#include <uhd/utils/byteswap.hpp> #include <uhd/utils/log.hpp> #include <uhd/utils/static.hpp> +#include <uhdlib/rfnoc/device_id.hpp> +#include <uhdlib/transport/nirio_link.hpp> #include <uhdlib/usrp/cores/i2c_core_100_wb32.hpp> #include <unordered_map> #include <mutex> namespace { -uint32_t extract_sid_from_pkt(void* pkt, size_t) -{ - return uhd::sid_t(uhd::wtohx(static_cast<const uint32_t*>(pkt)[1])).get_dst(); -} +//uint32_t extract_sid_from_pkt(void* pkt, size_t) +//{ + //return uhd::sid_t(uhd::wtohx(static_cast<const uint32_t*>(pkt)[1])).get_dst(); +//} constexpr uint32_t RADIO_DEST_PREFIX_TX = 0; @@ -198,9 +197,8 @@ device_addrs_t pcie_manager::find(const device_addr_t& hint, bool explicit_query /****************************************************************************** * Structors *****************************************************************************/ -pcie_manager::pcie_manager(const x300_device_args_t& args, - uhd::property_tree::sptr tree, - const uhd::fs_path& root_path) +pcie_manager::pcie_manager( + const x300_device_args_t& args, uhd::property_tree::sptr, const uhd::fs_path&) : _args(args), _resource(args.get_resource()) { nirio_status status = 0; @@ -237,9 +235,7 @@ pcie_manager::pcie_manager(const x300_device_args_t& args, _rio_fpga_interface->get_kernel_proxy()->get_rio_quirks().register_tx_streams( tx_data_fifos, 2); - tree->create<size_t>(root_path / "mtu/recv").set(PCIE_RX_DATA_FRAME_SIZE); - tree->create<size_t>(root_path / "mtu/send").set(PCIE_TX_DATA_FRAME_SIZE); - tree->create<double>(root_path / "link_max_rate").set(MAX_RATE_PCIE); + _local_device_id = rfnoc::allocate_device_id(); } /****************************************************************************** @@ -276,115 +272,124 @@ void pcie_manager::release_ctrl_iface(std::function<void(void)>&& release_fn) } uint32_t pcie_manager::allocate_pcie_dma_chan( - const uhd::sid_t& tx_sid, const uhd::usrp::device3_impl::xport_type_t xport_type) + const rfnoc::sep_id_t& /*remote_epid*/, const link_type_t /*link_type*/) { - constexpr uint32_t CTRL_CHANNEL = 0; - constexpr uint32_t ASYNC_MSG_CHANNEL = 1; - constexpr uint32_t FIRST_DATA_CHANNEL = 2; - if (xport_type == uhd::usrp::device3_impl::CTRL) { - return CTRL_CHANNEL; - } else if (xport_type == uhd::usrp::device3_impl::ASYNC_MSG) { - return ASYNC_MSG_CHANNEL; - } else { - // sid_t has no comparison defined, so we need to convert it uint32_t - uint32_t raw_sid = tx_sid.get(); - - if (_dma_chan_pool.count(raw_sid) == 0) { - size_t channel = _dma_chan_pool.size() + FIRST_DATA_CHANNEL; - if (channel > PCIE_MAX_CHANNELS) { - throw uhd::runtime_error( - "Trying to allocate more DMA channels than are available"); - } - _dma_chan_pool[raw_sid] = channel; - UHD_LOGGER_DEBUG("X300") - << "Assigning PCIe DMA channel " << _dma_chan_pool[raw_sid] << " to SID " - << tx_sid.to_pp_string_hex(); - } - - return _dma_chan_pool[raw_sid]; - } + throw uhd::not_implemented_error("allocate_pcie_dma_chan()"); + //constexpr uint32_t CTRL_CHANNEL = 0; + //constexpr uint32_t ASYNC_MSG_CHANNEL = 1; + //constexpr uint32_t FIRST_DATA_CHANNEL = 2; + //if (link_type == uhd::usrp::device3_impl::CTRL) { + //return CTRL_CHANNEL; + //} else if (link_type == uhd::usrp::device3_impl::ASYNC_MSG) { + //return ASYNC_MSG_CHANNEL; + //} else { + //// sid_t has no comparison defined, so we need to convert it uint32_t + //uint32_t raw_sid = tx_sid.get(); + + //if (_dma_chan_pool.count(raw_sid) == 0) { + //size_t channel = _dma_chan_pool.size() + FIRST_DATA_CHANNEL; + //if (channel > PCIE_MAX_CHANNELS) { + //throw uhd::runtime_error( + //"Trying to allocate more DMA channels than are available"); + //} + //_dma_chan_pool[raw_sid] = channel; + //UHD_LOGGER_DEBUG("X300") + //<< "Assigning PCIe DMA channel " << _dma_chan_pool[raw_sid] << " to SID " + //<< tx_sid.to_pp_string_hex(); + //} + + //return _dma_chan_pool[raw_sid]; + //} } muxed_zero_copy_if::sptr pcie_manager::make_muxed_pcie_msg_xport( uint32_t dma_channel_num, size_t max_muxed_ports) { - zero_copy_xport_params buff_args; - buff_args.send_frame_size = PCIE_MSG_FRAME_SIZE; - buff_args.recv_frame_size = PCIE_MSG_FRAME_SIZE; - buff_args.num_send_frames = PCIE_MSG_NUM_FRAMES; - buff_args.num_recv_frames = PCIE_MSG_NUM_FRAMES; - - zero_copy_if::sptr base_xport = nirio_zero_copy::make( - _rio_fpga_interface, dma_channel_num, buff_args, uhd::device_addr_t()); - return muxed_zero_copy_if::make(base_xport, extract_sid_from_pkt, max_muxed_ports); + throw uhd::not_implemented_error("NI-RIO links not yet implemented!"); + //zero_copy_xport_params buff_args; + //buff_args.send_frame_size = PCIE_MSG_FRAME_SIZE; + //buff_args.recv_frame_size = PCIE_MSG_FRAME_SIZE; + //buff_args.num_send_frames = PCIE_MSG_NUM_FRAMES; + //buff_args.num_recv_frames = PCIE_MSG_NUM_FRAMES; + + //zero_copy_if::sptr base_xport = nirio_zero_copy::make( + //_rio_fpga_interface, dma_channel_num, buff_args, uhd::device_addr_t()); + //return muxed_zero_copy_if::make(base_xport, extract_sid_from_pkt, max_muxed_ports); } -both_xports_t pcie_manager::make_transport(both_xports_t xports, - const uhd::usrp::device3_impl::xport_type_t xport_type, - const uhd::device_addr_t& args, - const size_t send_mtu, - const size_t recv_mtu) +both_links_t pcie_manager::get_links(link_type_t /*link_type*/, + const rfnoc::device_id_t local_device_id, + const rfnoc::sep_id_t& /*local_epid*/, + const rfnoc::sep_id_t& /*remote_epid*/, + const device_addr_t& /*link_args*/) { - zero_copy_xport_params default_buff_args; - xports.endianness = ENDIANNESS_LITTLE; - xports.lossless = true; - const uint32_t dma_channel_num = allocate_pcie_dma_chan(xports.send_sid, xport_type); - if (xport_type == uhd::usrp::device3_impl::CTRL) { - // Transport for control stream - if (not _ctrl_dma_xport) { - // One underlying DMA channel will handle - // all control traffic - _ctrl_dma_xport = - make_muxed_pcie_msg_xport(dma_channel_num, PCIE_MAX_MUXED_CTRL_XPORTS); - } - // Create a virtual control transport - xports.recv = _ctrl_dma_xport->make_stream(xports.recv_sid.get_dst()); - } else if (xport_type == uhd::usrp::device3_impl::ASYNC_MSG) { - // Transport for async message stream - if (not _async_msg_dma_xport) { - // One underlying DMA channel will handle - // all async message traffic - _async_msg_dma_xport = - make_muxed_pcie_msg_xport(dma_channel_num, PCIE_MAX_MUXED_ASYNC_XPORTS); - } - // Create a virtual async message transport - xports.recv = _async_msg_dma_xport->make_stream(xports.recv_sid.get_dst()); - } else if (xport_type == uhd::usrp::device3_impl::TX_DATA) { - default_buff_args.send_frame_size = args.cast<size_t>( - "send_frame_size", std::min(send_mtu, PCIE_TX_DATA_FRAME_SIZE)); - default_buff_args.num_send_frames = - args.cast<size_t>("num_send_frames", PCIE_TX_DATA_NUM_FRAMES); - default_buff_args.send_buff_size = args.cast<size_t>("send_buff_size", 0); - default_buff_args.recv_frame_size = PCIE_MSG_FRAME_SIZE; - default_buff_args.num_recv_frames = PCIE_MSG_NUM_FRAMES; - xports.recv = nirio_zero_copy::make( - _rio_fpga_interface, dma_channel_num, default_buff_args); - } else if (xport_type == uhd::usrp::device3_impl::RX_DATA) { - default_buff_args.send_frame_size = PCIE_MSG_FRAME_SIZE; - default_buff_args.num_send_frames = PCIE_MSG_NUM_FRAMES; - default_buff_args.recv_frame_size = args.cast<size_t>( - "recv_frame_size", std::min(recv_mtu, PCIE_RX_DATA_FRAME_SIZE)); - default_buff_args.num_recv_frames = - args.cast<size_t>("num_recv_frames", PCIE_RX_DATA_NUM_FRAMES); - default_buff_args.recv_buff_size = args.cast<size_t>("recv_buff_size", 0); - xports.recv = nirio_zero_copy::make( - _rio_fpga_interface, dma_channel_num, default_buff_args); + throw uhd::not_implemented_error("NI-RIO links not yet implemented!"); + if (local_device_id != _local_device_id) { + throw uhd::runtime_error( + std::string("[X300] Cannot create NI-RIO link through local device ID ") + + std::to_string(local_device_id) + + ", no such device associated with this motherboard!"); } - - xports.send = xports.recv; - - // Router config word is: - // - Upper 16 bits: Destination address (e.g. 0.0) - // - Lower 16 bits: DMA channel - uint32_t router_config_word = (xports.recv_sid.get_dst() << 16) | dma_channel_num; - _rio_fpga_interface->get_kernel_proxy()->poke(PCIE_ROUTER_REG(0), router_config_word); - - // For the nirio transport, buffer size is depends on the frame size and num - // frames - xports.recv_buff_size = - xports.recv->get_num_recv_frames() * xports.recv->get_recv_frame_size(); - xports.send_buff_size = - xports.send->get_num_send_frames() * xports.send->get_send_frame_size(); - - return xports; + //zero_copy_xport_params default_buff_args; + //xports.endianness = ENDIANNESS_LITTLE; + //xports.lossless = true; + //const uint32_t dma_channel_num = allocate_pcie_dma_chan(xports.send_sid, xport_type); + //if (xport_type == uhd::usrp::device3_impl::CTRL) { + //// Transport for control stream + //if (not _ctrl_dma_xport) { + //// One underlying DMA channel will handle + //// all control traffic + //_ctrl_dma_xport = + //make_muxed_pcie_msg_xport(dma_channel_num, PCIE_MAX_MUXED_CTRL_XPORTS); + //} + //// Create a virtual control transport + //xports.recv = _ctrl_dma_xport->make_stream(xports.recv_sid.get_dst()); + //} else if (xport_type == uhd::usrp::device3_impl::ASYNC_MSG) { + //// Transport for async message stream + //if (not _async_msg_dma_xport) { + //// One underlying DMA channel will handle + //// all async message traffic + //_async_msg_dma_xport = + //make_muxed_pcie_msg_xport(dma_channel_num, PCIE_MAX_MUXED_ASYNC_XPORTS); + //} + //// Create a virtual async message transport + //xports.recv = _async_msg_dma_xport->make_stream(xports.recv_sid.get_dst()); + //} else if (xport_type == uhd::usrp::device3_impl::TX_DATA) { + //default_buff_args.send_frame_size = args.cast<size_t>( + //"send_frame_size", std::min(send_mtu, PCIE_TX_DATA_FRAME_SIZE)); + //default_buff_args.num_send_frames = + //args.cast<size_t>("num_send_frames", PCIE_TX_DATA_NUM_FRAMES); + //default_buff_args.send_buff_size = args.cast<size_t>("send_buff_size", 0); + //default_buff_args.recv_frame_size = PCIE_MSG_FRAME_SIZE; + //default_buff_args.num_recv_frames = PCIE_MSG_NUM_FRAMES; + //xports.recv = nirio_zero_copy::make( + //_rio_fpga_interface, dma_channel_num, default_buff_args); + //} else if (xport_type == uhd::usrp::device3_impl::RX_DATA) { + //default_buff_args.send_frame_size = PCIE_MSG_FRAME_SIZE; + //default_buff_args.num_send_frames = PCIE_MSG_NUM_FRAMES; + //default_buff_args.recv_frame_size = args.cast<size_t>( + //"recv_frame_size", std::min(recv_mtu, PCIE_RX_DATA_FRAME_SIZE)); + //default_buff_args.num_recv_frames = + //args.cast<size_t>("num_recv_frames", PCIE_RX_DATA_NUM_FRAMES); + //default_buff_args.recv_buff_size = args.cast<size_t>("recv_buff_size", 0); + //xports.recv = nirio_zero_copy::make( + //_rio_fpga_interface, dma_channel_num, default_buff_args); + //} + + //xports.send = xports.recv; + + //// Router config word is: + //// - Upper 16 bits: Destination address (e.g. 0.0) + //// - Lower 16 bits: DMA channel + //uint32_t router_config_word = (xports.recv_sid.get_dst() << 16) | dma_channel_num; + //_rio_fpga_interface->get_kernel_proxy()->poke(PCIE_ROUTER_REG(0), router_config_word); + + //// For the nirio transport, buffer size is depends on the frame size and num + //// frames + //xports.recv_buff_size = + //xports.recv->get_num_recv_frames() * xports.recv->get_recv_frame_size(); + //xports.send_buff_size = + //xports.send->get_num_send_frames() * xports.send->get_send_frame_size(); + + //return xports; } diff --git a/host/lib/usrp/x300/x300_pcie_mgr.hpp b/host/lib/usrp/x300/x300_pcie_mgr.hpp index c884c8b5f..146a2ff93 100644 --- a/host/lib/usrp/x300/x300_pcie_mgr.hpp +++ b/host/lib/usrp/x300/x300_pcie_mgr.hpp @@ -7,13 +7,15 @@ #ifndef INCLUDED_X300_PCI_MGR_HPP #define INCLUDED_X300_PCI_MGR_HPP -#include "../device3/device3_impl.hpp" #include "x300_conn_mgr.hpp" #include "x300_device_args.hpp" #include "x300_mboard_type.hpp" +#include <uhd/property_tree.hpp> #include <uhd/transport/muxed_zero_copy_if.hpp> #include <uhd/transport/nirio/niusrprio_session.h> -#include <uhdlib/rfnoc/xports.hpp> +#include <uhd/types/direction.hpp> +#include <uhdlib/rfnoc/rfnoc_common.hpp> +#include <uhdlib/transport/links.hpp> namespace uhd { namespace usrp { namespace x300 { @@ -47,11 +49,18 @@ public: */ void release_ctrl_iface(std::function<void(void)>&& release_fn); - both_xports_t make_transport(both_xports_t xports, - const uhd::usrp::device3_impl::xport_type_t xport_type, - const uhd::device_addr_t& args, - const size_t send_mtu, - const size_t recv_mtu); + /*! Return list of local device IDs associated with this link + */ + std::vector<uhd::rfnoc::device_id_t> get_local_device_ids() + { + return {_local_device_id}; + } + + uhd::transport::both_links_t get_links(uhd::transport::link_type_t link_type, + const uhd::rfnoc::device_id_t local_device_id, + const uhd::rfnoc::sep_id_t& local_epid, + const uhd::rfnoc::sep_id_t& remote_epid, + const uhd::device_addr_t& link_args); private: /*! Allocate or return a previously allocated PCIe channel pair @@ -59,7 +68,7 @@ private: * Note the SID is always the transmit SID (i.e. from host to device). */ uint32_t allocate_pcie_dma_chan( - const uhd::sid_t& tx_sid, const uhd::usrp::device3_impl::xport_type_t xport_type); + const uhd::rfnoc::sep_id_t& remote_epid, const uhd::transport::link_type_t link_type); uhd::transport::muxed_zero_copy_if::sptr make_muxed_pcie_msg_xport( uint32_t dma_channel_num, size_t max_muxed_ports); @@ -69,13 +78,15 @@ private: uhd::niusrprio::niusrprio_session::sptr _rio_fpga_interface; - //! Maps SID -> DMA channel - std::map<uint32_t, uint32_t> _dma_chan_pool; + //! Maps Remote EPID -> DMA channel + std::map<uhd::rfnoc::sep_id_t, uint32_t> _dma_chan_pool; //! Control transport for one PCIe connection uhd::transport::muxed_zero_copy_if::sptr _ctrl_dma_xport; //! Async message transport uhd::transport::muxed_zero_copy_if::sptr _async_msg_dma_xport; + + uhd::rfnoc::device_id_t _local_device_id; }; }}} // namespace uhd::usrp::x300 diff --git a/host/lib/usrp/x300/x300_prop_tree.cpp b/host/lib/usrp/x300/x300_prop_tree.cpp new file mode 100644 index 000000000..21597beea --- /dev/null +++ b/host/lib/usrp/x300/x300_prop_tree.cpp @@ -0,0 +1,117 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "x300_defaults.hpp" +#include "x300_mb_controller.hpp" +#include <uhd/property_tree.hpp> +#include <uhd/utils/math.hpp> + +using namespace uhd; +using namespace uhd::rfnoc; + +namespace uhd { namespace usrp { namespace x300 { + +void init_prop_tree( + const size_t mb_idx, x300_mb_controller* mbc, property_tree::sptr pt) +{ + const fs_path mb_path = fs_path("/mboards") / mb_idx; + try { + pt->create<std::string>("/name").set("X-Series Device"); + } catch (const uhd::runtime_error&) { + // property_tree lacks an atomic check to only create a new node if it + // doesn't exist, so we simply try and create it and when it fails, we + // assume that another device has already created this node and we move + // on. If we did "if exists" before creating, there's a non-zero chance + // that a concurrent device init would still throw. + } + pt->create<std::string>(mb_path / "name").set(mbc->get_mboard_name()); + pt->create<std::string>(mb_path / "codename").set("Yetti"); + + //////////////////////////////////////////////////////////////////// + // create clock properties + //////////////////////////////////////////////////////////////////// + pt->create<double>(mb_path / "master_clock_rate").set_publisher([mbc]() { + return mbc->get_clock_ctrl()->get_master_clock_rate(); + }); + + //////////////////////////////////////////////////////////////////// + // setup time sources and properties + //////////////////////////////////////////////////////////////////// + pt->create<std::string>(mb_path / "time_source" / "value") + .set(mbc->get_time_source()) + .add_coerced_subscriber([mbc](const std::string& time_source) { + mbc->set_time_source(time_source); + }); + pt->create<std::vector<std::string>>(mb_path / "time_source" / "options") + .set(mbc->get_time_sources()); + + // setup the time output, default to ON + pt->create<bool>(mb_path / "time_source" / "output") + .add_coerced_subscriber([mbc](const bool time_output) { + mbc->set_time_source_out(time_output); + }) + .set(true); + + //////////////////////////////////////////////////////////////////// + // setup clock sources and properties + //////////////////////////////////////////////////////////////////// + pt->create<std::string>(mb_path / "clock_source" / "value") + .set(mbc->get_clock_source()) + .add_coerced_subscriber([mbc](const std::string& clock_source) { + mbc->set_clock_source(clock_source); + }) + .set_publisher([mbc]() { return mbc->get_clock_source(); }); + pt->create<std::vector<std::string>>(mb_path / "clock_source" / "options") + .set(mbc->get_clock_sources()); + + // setup external reference options. default to 10 MHz input reference + pt->create<std::string>(mb_path / "clock_source" / "external"); + pt + ->create<std::vector<double>>( + mb_path / "clock_source" / "external" / "freq" / "options") + .set(EXTERNAL_FREQ_OPTIONS); + pt->create<double>(mb_path / "clock_source" / "external" / "value") + .set(mbc->get_clock_ctrl()->get_sysref_clock_rate()) + .set_coercer([current_rate = mbc->get_clock_ctrl()->get_sysref_clock_rate()]( + const double clock_rate) { + if (!uhd::math::frequencies_are_equal(clock_rate, current_rate)) { + UHD_LOG_WARNING( + "X300", "Cannot change the sysref clock rate at runtime!"); + } + return clock_rate; + }); + + // setup the clock output, default to ON + pt->create<bool>(mb_path / "clock_source" / "output") + .add_coerced_subscriber( + [mbc](const bool clock_output) { mbc->set_clock_source_out(clock_output); }); + + // Initialize tick rate (must be done before setting time) + // Note: The master tick rate can't be changed at runtime! + const double master_clock_rate = mbc->get_clock_ctrl()->get_master_clock_rate(); + pt->create<double>(mb_path / "tick_rate") + .set_coercer([master_clock_rate](const double rate) { + // The contract of multi_usrp::set_master_clock_rate() is to coerce + // and not throw, so we'll follow that behaviour here. + if (!uhd::math::frequencies_are_equal(rate, master_clock_rate)) { + UHD_LOGGER_WARNING("X300") + << "Cannot update master clock rate! X300 Series does not " + "allow changing the clock rate during runtime."; + } + return master_clock_rate; + }) + .set(master_clock_rate); + + //////////////////////////////////////////////////////////////////// + // and do the misc mboard sensors + //////////////////////////////////////////////////////////////////// + for (const std::string& sensor_name : mbc->get_sensor_names()) { + pt->create<sensor_value_t>(mb_path / "sensors" / sensor_name) + .set_publisher([mbc, sensor_name]() { return mbc->get_sensor(sensor_name); }); + } +} + +}}} // namespace uhd::usrp::x300 diff --git a/host/lib/usrp/x300/x300_radio_control.cpp b/host/lib/usrp/x300/x300_radio_control.cpp new file mode 100644 index 000000000..6cee57827 --- /dev/null +++ b/host/lib/usrp/x300/x300_radio_control.cpp @@ -0,0 +1,1906 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "x300_adc_ctrl.hpp" +#include "x300_dac_ctrl.hpp" +#include "x300_dboard_iface.hpp" +#include "x300_device_args.hpp" +#include "x300_mb_controller.hpp" +#include "x300_radio_mbc_iface.hpp" +#include "x300_regs.hpp" +#include <uhd/rfnoc/registry.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/types/wb_iface.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/utils/gain_group.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/math.hpp> +#include <uhd/utils/soft_register.hpp> +#include <uhdlib/rfnoc/radio_control_impl.hpp> +#include <uhdlib/rfnoc/reg_iface_adapter.hpp> +#include <uhdlib/usrp/common/apply_corrections.hpp> +#include <uhdlib/usrp/cores/gpio_atr_3000.hpp> +#include <uhdlib/usrp/cores/rx_frontend_core_3000.hpp> +#include <uhdlib/usrp/cores/spi_core_3000.hpp> +#include <uhdlib/usrp/cores/tx_frontend_core_200.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/make_shared.hpp> +#include <algorithm> +#include <chrono> +#include <functional> +#include <iostream> +#include <thread> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; + +namespace { + +std::vector<uint8_t> str_to_bytes(std::string str) +{ + return std::vector<uint8_t>(str.cbegin(), str.cend()); +} + +std::string bytes_to_str(std::vector<uint8_t> str_b) +{ + return std::string(str_b.cbegin(), str_b.cend()); +} + +gain_fcns_t make_gain_fcns_from_subtree(property_tree::sptr subtree) +{ + gain_fcns_t gain_fcns; + gain_fcns.get_range = [subtree]() { + return subtree->access<meta_range_t>("range").get(); + }; + gain_fcns.get_value = [subtree]() { return subtree->access<double>("value").get(); }; + gain_fcns.set_value = [subtree](const double gain) { + subtree->access<double>("value").set(gain); + }; + return gain_fcns; +} + +template <typename map_type> +size_t _get_chan_from_map(std::unordered_map<size_t, map_type> map, const std::string& fe) +{ + for (auto it = map.begin(); it != map.end(); ++it) { + if (it->second.db_fe_name == fe) { + return it->first; + } + } + throw uhd::lookup_error( + str(boost::format("Invalid daughterboard frontend name: %s") % fe)); +} + +constexpr double DEFAULT_RATE = 200e6; + +} // namespace + +namespace x300_regs { + +static constexpr uint32_t PERIPH_BASE = 0x80000; +static constexpr uint32_t PERIPH_REG_OFFSET = 8; + +// db_control registers +static constexpr uint32_t SR_MISC_OUTS = PERIPH_BASE + 160 * PERIPH_REG_OFFSET; +static constexpr uint32_t SR_SPI = PERIPH_BASE + 168 * PERIPH_REG_OFFSET; +static constexpr uint32_t SR_LEDS = PERIPH_BASE + 176 * PERIPH_REG_OFFSET; +static constexpr uint32_t SR_FP_GPIO = PERIPH_BASE + 184 * PERIPH_REG_OFFSET; +static constexpr uint32_t SR_DB_GPIO = PERIPH_BASE + 192 * PERIPH_REG_OFFSET; + +static constexpr uint32_t RB_MISC_IO = PERIPH_BASE + 16 * PERIPH_REG_OFFSET; +static constexpr uint32_t RB_SPI = PERIPH_BASE + 17 * PERIPH_REG_OFFSET; +static constexpr uint32_t RB_LEDS = PERIPH_BASE + 18 * PERIPH_REG_OFFSET; +static constexpr uint32_t RB_DB_GPIO = PERIPH_BASE + 19 * PERIPH_REG_OFFSET; +static constexpr uint32_t RB_FP_GPIO = PERIPH_BASE + 20 * PERIPH_REG_OFFSET; + + +//! Delta between frontend offsets for channel 0 and 1 +constexpr uint32_t SR_FE_CHAN_OFFSET = 16 * PERIPH_REG_OFFSET; +constexpr uint32_t SR_TX_FE_BASE = PERIPH_BASE + 208 * PERIPH_REG_OFFSET; +constexpr uint32_t SR_RX_FE_BASE = PERIPH_BASE + 224 * PERIPH_REG_OFFSET; + +} // namespace x300_regs + +class x300_radio_control_impl : public radio_control_impl, + public uhd::usrp::x300::x300_radio_mbc_iface +{ +public: + RFNOC_RADIO_CONSTRUCTOR(x300_radio_control) + , _radio_type(get_block_id().get_block_count() == 0 ? PRIMARY : SECONDARY) + { + RFNOC_LOG_TRACE("Initializing x300_radio_control, slot " + << x300_radio_control_impl::get_slot_name()); + UHD_ASSERT_THROW(get_mb_controller()); + _x300_mb_control = + std::dynamic_pointer_cast<x300_mb_controller>(get_mb_controller()); + UHD_ASSERT_THROW(_x300_mb_control); + _x300_mb_control->register_radio(this); + // MCR is locked for this session + _master_clock_rate = _x300_mb_control->get_clock_ctrl()->get_master_clock_rate(); + UHD_ASSERT_THROW(get_tick_rate() == _master_clock_rate); + radio_control_impl::set_rate(_master_clock_rate); + + //////////////////////////////////////////////////////////////// + // Setup peripherals + //////////////////////////////////////////////////////////////// + // The X300 only requires a single timed_wb_iface, even for TwinRX + _wb_iface = RFNOC_MAKE_WB_IFACE(0, 0); + + RFNOC_LOG_TRACE("Creating SPI interface..."); + _spi = spi_core_3000::make( + [this](const uint32_t addr, const uint32_t data) { + regs().poke32(addr, data, get_command_time(0)); + }, + [this]( + const uint32_t addr) { return regs().peek32(addr, get_command_time(0)); }, + x300_regs::SR_SPI, + 8, + x300_regs::RB_SPI); + // DAC/ADC + RFNOC_LOG_TRACE("Running init_codec..."); + // Note: ADC calibration and DAC sync happen in x300_mb_controller + _init_codecs(); + _x300_mb_control->register_reset_codec_cb([this]() { this->reset_codec(); }); + // FP-GPIO + if (_radio_type == PRIMARY) { + RFNOC_LOG_TRACE("Creating FP-GPIO interface..."); + _fp_gpio = gpio_atr::gpio_atr_3000::make(_wb_iface, + x300_regs::SR_FP_GPIO, + x300_regs::RB_FP_GPIO, + x300_regs::PERIPH_REG_OFFSET); + // Create the GPIO banks and attributes, and populate them with some default + // values + // TODO: Do we need this section? Since the _fp_gpio handles state now, we + // don't need to stash values here. We only need this if we want to set + // anything to a default value. + for (const gpio_atr::gpio_attr_map_t::value_type attr : + gpio_atr::gpio_attr_map) { + // TODO: Default values? + if (attr.first == usrp::gpio_atr::GPIO_SRC) { + // Don't set the SRC + // TODO: Remove from the map?? + continue; + } + set_gpio_attr("FP0", usrp::gpio_atr::gpio_attr_map.at(attr.first), 0); + } + } + // DB Initialization + _init_db(); // This does not init the dboards themselves! + + // LEDs are technically valid for both RX and TX, but let's put them + // here + _leds = gpio_atr::gpio_atr_3000::make_write_only( + _wb_iface, x300_regs::SR_LEDS, x300_regs::PERIPH_REG_OFFSET); + _leds->set_atr_mode( + usrp::gpio_atr::MODE_ATR, usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL); + // We always want to initialize at least one frontend core for both TX and RX + // RX periphs + for (size_t i = 0; i < std::max<size_t>(get_num_output_ports(), 1); i++) { + _rx_fe_map[i].core = rx_frontend_core_3000::make(_wb_iface, + x300_regs::SR_RX_FE_BASE + i * x300_regs::SR_FE_CHAN_OFFSET, + x300_regs::PERIPH_REG_OFFSET); + _rx_fe_map[i].core->set_adc_rate( + _x300_mb_control->get_clock_ctrl()->get_master_clock_rate()); + _rx_fe_map[i].core->set_dc_offset( + rx_frontend_core_3000::DEFAULT_DC_OFFSET_VALUE); + _rx_fe_map[i].core->set_dc_offset_auto( + rx_frontend_core_3000::DEFAULT_DC_OFFSET_ENABLE); + _rx_fe_map[i].core->populate_subtree( + get_tree()->subtree(FE_PATH / "rx_fe_corrections" / i)); + } + // TX Periphs + for (size_t i = 0; i < std::max<size_t>(get_num_input_ports(), 1); i++) { + _tx_fe_map[i].core = tx_frontend_core_200::make(_wb_iface, + x300_regs::SR_TX_FE_BASE + i * x300_regs::SR_FE_CHAN_OFFSET, + x300_regs::PERIPH_REG_OFFSET); + _tx_fe_map[i].core->set_dc_offset( + tx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); + _tx_fe_map[i].core->set_iq_balance( + tx_frontend_core_200::DEFAULT_IQ_BALANCE_VALUE); + _tx_fe_map[i].core->populate_subtree( + get_tree()->subtree(FE_PATH / "tx_fe_corrections" / i)); + } + + // Dboards + _init_dboards(); + + // Properties + for (auto& samp_rate_prop : _samp_rate_in) { + samp_rate_prop.set(get_rate()); + } + for (auto& samp_rate_prop : _samp_rate_out) { + samp_rate_prop.set(get_rate()); + } + } /* ctor */ + + ~x300_radio_control_impl() + { + // nop + } + + /************************************************************************** + * Radio API calls + *************************************************************************/ + double set_rate(double rate) + { + // On X3x0, tick rate can't actually be changed at runtime + const double actual_rate = get_rate(); + if (not uhd::math::frequencies_are_equal(rate, actual_rate)) { + RFNOC_LOG_WARNING("Requesting invalid sampling rate from device: " + << (rate / 1e6) << " MHz. Actual rate is: " + << (actual_rate / 1e6) << " MHz."); + } + return actual_rate; + } + + void set_tx_antenna(const std::string& ant, const size_t chan) + { + get_tree() + ->access<std::string>(get_db_path("tx", chan) / "antenna" / "value") + .set(ant); + } + + std::string get_tx_antenna(const size_t chan) const + { + return get_tree() + ->access<std::string>(get_db_path("tx", chan) / "antenna" / "value") + .get(); + } + + std::vector<std::string> get_tx_antennas(size_t chan) const + { + return get_tree() + ->access<std::vector<std::string>>( + get_db_path("tx", chan) / "antenna" / "options") + .get(); + } + + void set_rx_antenna(const std::string& ant, const size_t chan) + { + get_tree() + ->access<std::string>(get_db_path("rx", chan) / "antenna" / "value") + .set(ant); + } + + std::string get_rx_antenna(const size_t chan) const + { + return get_tree() + ->access<std::string>(get_db_path("rx", chan) / "antenna" / "value") + .get(); + } + + std::vector<std::string> get_rx_antennas(size_t chan) const + { + return get_tree() + ->access<std::vector<std::string>>( + get_db_path("rx", chan) / "antenna" / "options") + .get(); + } + + double set_tx_frequency(const double freq, const size_t chan) + { + return get_tree() + ->access<double>(get_db_path("tx", chan) / "freq" / "value") + .set(freq) + .get(); + } + + void set_tx_tune_args(const uhd::device_addr_t& tune_args, const size_t chan) + { + if (get_tree()->exists(get_db_path("tx", chan) / "tune_args")) { + get_tree() + ->access<uhd::device_addr_t>(get_db_path("tx", chan) / "tune_args") + .set(tune_args); + } + } + + double get_tx_frequency(const size_t chan) + { + return get_tree() + ->access<double>(get_db_path("tx", chan) / "freq" / "value") + .get(); + } + + double set_rx_frequency(const double freq, const size_t chan) + { + RFNOC_LOG_TRACE( + "set_rx_frequency(freq=" << (freq / 1e6) << " MHz, chan=" << chan << ")"); + return get_tree() + ->access<double>(get_db_path("rx", chan) / "freq" / "value") + .set(freq) + .get(); + } + + void set_rx_tune_args(const uhd::device_addr_t& tune_args, const size_t chan) + { + if (get_tree()->exists(get_db_path("rx", chan) / "tune_args")) { + get_tree() + ->access<uhd::device_addr_t>(get_db_path("rx", chan) / "tune_args") + .set(tune_args); + } + } + + double get_rx_frequency(const size_t chan) + { + return get_tree() + ->access<double>(get_db_path("rx", chan) / "freq" / "value") + .get(); + } + + uhd::freq_range_t get_tx_frequency_range(const size_t chan) const + { + return get_tree() + ->access<uhd::freq_range_t>(get_db_path("tx", chan) / "freq" / "range") + .get(); + } + + uhd::freq_range_t get_rx_frequency_range(const size_t chan) const + { + return get_tree() + ->access<uhd::meta_range_t>(get_db_path("rx", chan) / "freq" / "range") + .get(); + } + + /*** Bandwidth-Related APIs************************************************/ + double set_rx_bandwidth(const double bandwidth, const size_t chan) + { + return get_tree() + ->access<double>(get_db_path("rx", chan) / "bandwidth" / "value") + .set(bandwidth) + .get(); + } + + double get_rx_bandwidth(const size_t chan) + { + return get_tree() + ->access<double>(get_db_path("rx", chan) / "bandwidth" / "value") + .get(); + } + + uhd::meta_range_t get_rx_bandwidth_range(size_t chan) const + { + return get_tree() + ->access<uhd::meta_range_t>(get_db_path("rx", chan) / "bandwidth" / "range") + .get(); + } + + double set_tx_bandwidth(const double bandwidth, const size_t chan) + { + return get_tree() + ->access<double>(get_db_path("tx", chan) / "bandwidth" / "value") + .set(bandwidth) + .get(); + } + + double get_tx_bandwidth(const size_t chan) + { + return get_tree() + ->access<double>(get_db_path("tx", chan) / "bandwidth" / "value") + .get(); + } + + uhd::meta_range_t get_tx_bandwidth_range(size_t chan) const + { + return get_tree() + ->access<uhd::meta_range_t>(get_db_path("tx", chan) / "bandwidth" / "range") + .get(); + } + + /*** Gain-Related APIs ***************************************************/ + double set_tx_gain(const double gain, const size_t chan) + { + return set_tx_gain(gain, ALL_GAINS, chan); + } + + double set_tx_gain(const double gain, const std::string& name, const size_t chan) + { + if (_tx_gain_groups.count(chan)) { + auto& gg = _tx_gain_groups.at(chan); + gg->set_value(gain, name); + return radio_control_impl::set_tx_gain(gg->get_value(name), chan); + } + return radio_control_impl::set_tx_gain(0.0, chan); + } + + double set_rx_gain(const double gain, const size_t chan) + { + return set_rx_gain(gain, ALL_GAINS, chan); + } + + double set_rx_gain(const double gain, const std::string& name, const size_t chan) + { + auto& gg = _rx_gain_groups.at(chan); + gg->set_value(gain, name); + return radio_control_impl::set_rx_gain(gg->get_value(name), chan); + } + + double get_rx_gain(const size_t chan) + { + return get_rx_gain(ALL_GAINS, chan); + } + + double get_rx_gain(const std::string& name, const size_t chan) + { + return _rx_gain_groups.at(chan)->get_value(name); + } + + double get_tx_gain(const size_t chan) + { + return get_tx_gain(ALL_GAINS, chan); + } + + double get_tx_gain(const std::string& name, const size_t chan) + { + return _tx_gain_groups.at(chan)->get_value(name); + } + + std::vector<std::string> get_tx_gain_names(const size_t chan) const + { + return _tx_gain_groups.at(chan)->get_names(); + } + + std::vector<std::string> get_rx_gain_names(const size_t chan) const + { + return _rx_gain_groups.at(chan)->get_names(); + } + + uhd::gain_range_t get_tx_gain_range(const size_t chan) const + { + return get_tx_gain_range(ALL_GAINS, chan); + } + + uhd::gain_range_t get_tx_gain_range(const std::string& name, const size_t chan) const + { + if (!_tx_gain_groups.count(chan)) { + throw uhd::index_error( + "Trying to access invalid TX gain group: " + std::to_string(chan)); + } + return _tx_gain_groups.at(chan)->get_range(name); + } + + uhd::gain_range_t get_rx_gain_range(const size_t chan) const + { + return get_rx_gain_range(ALL_GAINS, chan); + } + + uhd::gain_range_t get_rx_gain_range(const std::string& name, const size_t chan) const + { + if (!_rx_gain_groups.count(chan)) { + throw uhd::index_error( + "Trying to access invalid RX gain group: " + std::to_string(chan)); + } + return _rx_gain_groups.at(chan)->get_range(name); + } + + std::vector<std::string> get_tx_gain_profile_names(const size_t chan) const + { + return get_tree() + ->access<std::vector<std::string>>( + get_db_path("tx", chan) / "gains/all/profile/options") + .get(); + } + + std::vector<std::string> get_rx_gain_profile_names(const size_t chan) const + { + return get_tree() + ->access<std::vector<std::string>>( + get_db_path("rx", chan) / "gains/all/profile/options") + .get(); + } + + + void set_tx_gain_profile(const std::string& profile, const size_t chan) + { + get_tree() + ->access<std::string>(get_db_path("tx", chan) / "gains/all/profile/value") + .set(profile); + } + + void set_rx_gain_profile(const std::string& profile, const size_t chan) + { + get_tree() + ->access<std::string>(get_db_path("rx", chan) / "gains/all/profile/value") + .set(profile); + } + + + std::string get_tx_gain_profile(const size_t chan) const + { + return get_tree() + ->access<std::string>(get_db_path("tx", chan) / "gains/all/profile/value") + .get(); + } + + std::string get_rx_gain_profile(const size_t chan) const + { + return get_tree() + ->access<std::string>(get_db_path("rx", chan) / "gains/all/profile/value") + .get(); + } + + /************************************************************************** + * LO controls + *************************************************************************/ + std::vector<std::string> get_rx_lo_names(const size_t chan) const + { + fs_path rx_fe_fe_root = get_db_path("rx", chan); + std::vector<std::string> lo_names; + if (get_tree()->exists(rx_fe_fe_root / "los")) { + for (const std::string& name : get_tree()->list(rx_fe_fe_root / "los")) { + lo_names.push_back(name); + } + } + return lo_names; + } + + std::vector<std::string> get_rx_lo_sources( + const std::string& name, const size_t chan) const + { + fs_path rx_fe_fe_root = get_db_path("rx", chan); + + if (get_tree()->exists(rx_fe_fe_root / "los")) { + if (name == ALL_LOS) { + if (get_tree()->exists(rx_fe_fe_root / "los" / ALL_LOS)) { + // Special value ALL_LOS support atomically sets the source for all + // LOs + return get_tree() + ->access<std::vector<std::string>>( + rx_fe_fe_root / "los" / ALL_LOS / "source" / "options") + .get(); + } else { + return std::vector<std::string>(); + } + } else { + if (get_tree()->exists(rx_fe_fe_root / "los")) { + return get_tree() + ->access<std::vector<std::string>>( + rx_fe_fe_root / "los" / name / "source" / "options") + .get(); + } else { + throw uhd::runtime_error("Could not find LO stage " + name); + } + } + } else { + // If the daughterboard doesn't expose it's LO(s) then it can only be internal + return std::vector<std::string>(1, "internal"); + } + } + + void set_rx_lo_source( + const std::string& src, const std::string& name, const size_t chan) + { + fs_path rx_fe_fe_root = get_db_path("rx", chan); + + if (get_tree()->exists(rx_fe_fe_root / "los")) { + if (name == ALL_LOS) { + if (get_tree()->exists(rx_fe_fe_root / "los" / ALL_LOS)) { + // Special value ALL_LOS support atomically sets the source for all + // LOs + get_tree() + ->access<std::string>( + rx_fe_fe_root / "los" / ALL_LOS / "source" / "value") + .set(src); + } else { + for (const std::string& n : get_tree()->list(rx_fe_fe_root / "los")) { + this->set_rx_lo_source(src, n, chan); + } + } + } else { + if (get_tree()->exists(rx_fe_fe_root / "los")) { + get_tree() + ->access<std::string>( + rx_fe_fe_root / "los" / name / "source" / "value") + .set(src); + } else { + throw uhd::runtime_error("Could not find LO stage " + name); + } + } + } else { + throw uhd::runtime_error( + "This device does not support manual configuration of LOs"); + } + } + + const std::string get_rx_lo_source(const std::string& name, const size_t chan) + { + fs_path rx_fe_fe_root = get_db_path("rx", chan); + + if (get_tree()->exists(rx_fe_fe_root / "los")) { + if (name == ALL_LOS) { + // Special value ALL_LOS support atomically sets the source for all LOs + return get_tree() + ->access<std::string>( + rx_fe_fe_root / "los" / ALL_LOS / "source" / "value") + .get(); + } else { + if (get_tree()->exists(rx_fe_fe_root / "los")) { + return get_tree() + ->access<std::string>( + rx_fe_fe_root / "los" / name / "source" / "value") + .get(); + } else { + throw uhd::runtime_error("Could not find LO stage " + name); + } + } + } else { + // If the daughterboard doesn't expose it's LO(s) then it can only be internal + return "internal"; + } + } + + void set_rx_lo_export_enabled( + bool enabled, const std::string& name, const size_t chan) + { + fs_path rx_fe_fe_root = get_db_path("rx", chan); + + if (get_tree()->exists(rx_fe_fe_root / "los")) { + if (name == ALL_LOS) { + if (get_tree()->exists(rx_fe_fe_root / "los" / ALL_LOS)) { + // Special value ALL_LOS support atomically sets the source for all + // LOs + get_tree() + ->access<bool>(rx_fe_fe_root / "los" / ALL_LOS / "export") + .set(enabled); + } else { + for (const std::string& n : get_tree()->list(rx_fe_fe_root / "los")) { + this->set_rx_lo_export_enabled(enabled, n, chan); + } + } + } else { + if (get_tree()->exists(rx_fe_fe_root / "los")) { + get_tree() + ->access<bool>(rx_fe_fe_root / "los" / name / "export") + .set(enabled); + } else { + throw uhd::runtime_error("Could not find LO stage " + name); + } + } + } else { + throw uhd::runtime_error( + "This device does not support manual configuration of LOs"); + } + } + + bool get_rx_lo_export_enabled(const std::string& name, const size_t chan) const + { + fs_path rx_fe_fe_root = get_db_path("rx", chan); + + if (get_tree()->exists(rx_fe_fe_root / "los")) { + if (name == ALL_LOS) { + // Special value ALL_LOS support atomically sets the source for all LOs + return get_tree() + ->access<bool>(rx_fe_fe_root / "los" / ALL_LOS / "export") + .get(); + } else { + if (get_tree()->exists(rx_fe_fe_root / "los")) { + return get_tree() + ->access<bool>(rx_fe_fe_root / "los" / name / "export") + .get(); + } else { + throw uhd::runtime_error("Could not find LO stage " + name); + } + } + } else { + // If the daughterboard doesn't expose it's LO(s), assume it cannot export + return false; + } + } + + double set_rx_lo_freq(double freq, const std::string& name, const size_t chan) + { + fs_path rx_fe_fe_root = get_db_path("rx", chan); + + if (get_tree()->exists(rx_fe_fe_root / "los")) { + if (name == ALL_LOS) { + throw uhd::runtime_error( + "LO frequency must be set for each stage individually"); + } else { + if (get_tree()->exists(rx_fe_fe_root / "los")) { + get_tree() + ->access<double>(rx_fe_fe_root / "los" / name / "freq" / "value") + .set(freq); + return get_tree() + ->access<double>(rx_fe_fe_root / "los" / name / "freq" / "value") + .get(); + } else { + throw uhd::runtime_error("Could not find LO stage " + name); + } + } + } else { + throw uhd::runtime_error( + "This device does not support manual configuration of LOs"); + } + } + + double get_rx_lo_freq(const std::string& name, const size_t chan) + { + fs_path rx_fe_fe_root = get_db_path("rx", chan); + + if (get_tree()->exists(rx_fe_fe_root / "los")) { + if (name == ALL_LOS) { + throw uhd::runtime_error( + "LO frequency must be retrieved for each stage individually"); + } else { + if (get_tree()->exists(rx_fe_fe_root / "los")) { + return get_tree() + ->access<double>(rx_fe_fe_root / "los" / name / "freq" / "value") + .get(); + } else { + throw uhd::runtime_error("Could not find LO stage " + name); + } + } + } else { + // Return actual RF frequency if the daughterboard doesn't expose its LO(s) + return get_tree()->access<double>(rx_fe_fe_root / "freq" / " value").get(); + } + } + + freq_range_t get_rx_lo_freq_range(const std::string& name, const size_t chan) const + { + fs_path rx_fe_fe_root = get_db_path("rx", chan); + + if (get_tree()->exists(rx_fe_fe_root / "los")) { + if (name == ALL_LOS) { + throw uhd::runtime_error( + "LO frequency range must be retrieved for each stage individually"); + } else { + if (get_tree()->exists(rx_fe_fe_root / "los")) { + return get_tree() + ->access<freq_range_t>( + rx_fe_fe_root / "los" / name / "freq" / "range") + .get(); + } else { + throw uhd::runtime_error("Could not find LO stage " + name); + } + } + } else { + // Return the actual RF range if the daughterboard doesn't expose its LO(s) + return get_tree() + ->access<meta_range_t>(rx_fe_fe_root / "freq" / "range") + .get(); + } + } + + /*** Calibration API *****************************************************/ + void set_tx_dc_offset(const std::complex<double>& offset, size_t chan) + { + const fs_path dc_offset_path = get_fe_path("tx", chan) / "dc_offset" / "value"; + if (get_tree()->exists(dc_offset_path)) { + get_tree()->access<std::complex<double>>(dc_offset_path).set(offset); + } else { + RFNOC_LOG_WARNING("Setting TX DC offset is not possible on this device."); + } + } + + meta_range_t get_tx_dc_offset_range(size_t chan) const + { + const fs_path range_path = get_fe_path("tx", chan) / "dc_offset" / "range"; + if (get_tree()->exists(range_path)) { + return get_tree()->access<uhd::meta_range_t>(range_path).get(); + } else { + RFNOC_LOG_WARNING( + "This device does not support querying the TX DC offset range."); + return meta_range_t(0, 0); + } + } + + void set_tx_iq_balance(const std::complex<double>& correction, size_t chan) + { + const fs_path iq_balance_path = get_fe_path("tx", chan) / "iq_balance" / "value"; + if (get_tree()->exists(iq_balance_path)) { + get_tree()->access<std::complex<double>>(iq_balance_path).set(correction); + } else { + RFNOC_LOG_WARNING("Setting TX IQ Balance is not possible on this device."); + } + } + + void set_rx_dc_offset(const bool enb, size_t chan) + { + const fs_path dc_offset_path = get_fe_path("rx", chan) / "dc_offset" / "enable"; + if (get_tree()->exists(dc_offset_path)) { + get_tree()->access<bool>(dc_offset_path).set(enb); + } else { + RFNOC_LOG_WARNING( + "Setting DC offset compensation is not possible on this device."); + } + } + + void set_rx_dc_offset(const std::complex<double>& offset, size_t chan) + { + const fs_path dc_offset_path = get_fe_path("rx", chan) / "dc_offset" / "value"; + if (get_tree()->exists(dc_offset_path)) { + get_tree()->access<std::complex<double>>(dc_offset_path).set(offset); + } else { + RFNOC_LOG_WARNING("Setting RX DC offset is not possible on this device."); + } + } + + meta_range_t get_rx_dc_offset_range(size_t chan) const + { + const fs_path range_path = get_fe_path("rx", chan) / "dc_offset" / "range"; + if (get_tree()->exists(range_path)) { + return get_tree()->access<uhd::meta_range_t>(range_path).get(); + } else { + RFNOC_LOG_WARNING( + "This device does not support querying the rx DC offset range."); + return meta_range_t(0, 0); + } + } + + void set_rx_iq_balance(const bool enb, size_t chan) + { + const fs_path iq_balance_path = get_fe_path("rx", chan) / "iq_balance" / "enable"; + if (get_tree()->exists(iq_balance_path)) { + get_tree()->access<bool>(iq_balance_path).set(enb); + } else { + RFNOC_LOG_WARNING("Setting RX IQ Balance is not possible on this device."); + } + } + + void set_rx_iq_balance(const std::complex<double>& correction, size_t chan) + { + const fs_path iq_balance_path = get_fe_path("rx", chan) / "iq_balance" / "value"; + if (get_tree()->exists(iq_balance_path)) { + get_tree()->access<std::complex<double>>(iq_balance_path).set(correction); + } else { + RFNOC_LOG_WARNING("Setting RX IQ Balance is not possible on this device."); + } + } + + /*** GPIO API ************************************************************/ + std::vector<std::string> get_gpio_banks() const + { + std::vector<std::string> banks{"RX", "TX"}; + if (_fp_gpio) { + banks.push_back("FP0"); + } + return banks; + } + + void set_gpio_attr( + const std::string& bank, const std::string& attr, const uint32_t value) + { + if (bank == "FP0" and _fp_gpio) { + _fp_gpio->set_gpio_attr(usrp::gpio_atr::gpio_attr_rev_map.at(attr), value); + return; + } + if (bank.size() >= 2 and bank[1] == 'X') { + const std::string name = bank.substr(2); + const dboard_iface::unit_t unit = (bank[0] == 'R') ? dboard_iface::UNIT_RX + : dboard_iface::UNIT_TX; + constexpr uint16_t mask = 0xFFFF; + if (attr == "CTRL") { + _db_iface->set_pin_ctrl(unit, value, mask); + } + else if (attr == "DDR") { + _db_iface->set_gpio_ddr(unit, value, mask); + } + else if (attr == "OUT") { + _db_iface->set_gpio_out(unit, value, mask); + } + else if (attr == "ATR_0X") { + _db_iface->set_atr_reg(unit, gpio_atr::ATR_REG_IDLE, value, mask); + } + else if (attr == "ATR_RX") { + _db_iface->set_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY, value, mask); + } + else if (attr == "ATR_TX") { + _db_iface->set_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY, value, mask); + } + else if (attr == "ATR_XX") { + _db_iface->set_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX, value, mask); + } + else { + RFNOC_LOG_ERROR("Invalid GPIO attribute name: " << attr); + throw uhd::key_error(std::string("Invalid GPIO attribute name: ") + attr); + } + return; + } + RFNOC_LOG_WARNING( + "Invalid GPIO bank name: `" + << bank + << "'. Ignoring call to set_gpio_attr() to retain backward compatibility."); + } + + uint32_t get_gpio_attr(const std::string& bank, const std::string& attr) + { + if (bank == "FP0" and _fp_gpio) { + return _fp_gpio->get_attr_reg(usrp::gpio_atr::gpio_attr_rev_map.at(attr)); + } + if (bank.size() >= 2 and bank[1] == 'X') { + const std::string name = bank.substr(2); + const dboard_iface::unit_t unit = (bank[0] == 'R') ? dboard_iface::UNIT_RX + : dboard_iface::UNIT_TX; + if (attr == "CTRL") + return _db_iface->get_pin_ctrl(unit); + if (attr == "DDR") + return _db_iface->get_gpio_ddr(unit); + if (attr == "OUT") + return _db_iface->get_gpio_out(unit); + if (attr == "ATR_0X") + return _db_iface->get_atr_reg(unit, gpio_atr::ATR_REG_IDLE); + if (attr == "ATR_RX") + return _db_iface->get_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY); + if (attr == "ATR_TX") + return _db_iface->get_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY); + if (attr == "ATR_XX") + return _db_iface->get_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX); + if (attr == "READBACK") + return _db_iface->read_gpio(unit); + RFNOC_LOG_ERROR("Invalid GPIO attribute name: " << attr); + throw uhd::key_error(std::string("Invalid GPIO attribute name: ") + attr); + } + RFNOC_LOG_WARNING( + "Invalid GPIO bank name: `" + << bank + << "'. get_gpio_attr() will return 0 to retain backward compatibility."); + return 0; + } + + /************************************************************************** + * Sensor API + *************************************************************************/ + std::vector<std::string> get_rx_sensor_names(size_t chan) const + { + const fs_path sensor_path = get_db_path("rx", chan) / "sensors"; + if (get_tree()->exists(sensor_path)) { + return get_tree()->list(sensor_path); + } + return {}; + } + + uhd::sensor_value_t get_rx_sensor(const std::string& name, size_t chan) + { + return get_tree() + ->access<uhd::sensor_value_t>(get_db_path("rx", chan) / "sensors" / name) + .get(); + } + + std::vector<std::string> get_tx_sensor_names(size_t chan) const + { + const fs_path sensor_path = get_db_path("tx", chan) / "sensors"; + if (get_tree()->exists(sensor_path)) { + return get_tree()->list(sensor_path); + } + return {}; + } + + uhd::sensor_value_t get_tx_sensor(const std::string& name, size_t chan) + { + return get_tree() + ->access<uhd::sensor_value_t>(get_db_path("tx", chan) / "sensors" / name) + .get(); + } + + /************************************************************************** + * EEPROM API + *************************************************************************/ + void set_db_eeprom(const uhd::eeprom_map_t& db_eeprom) + { + const std::string key_prefix = db_eeprom.count("rx_id") ? "rx_" : "tx_"; + const std::string id_key = key_prefix + "id"; + const std::string serial_key = key_prefix + "serial"; + const std::string rev_key = key_prefix + "rev"; + if (!(db_eeprom.count(id_key) && db_eeprom.count(serial_key) + && db_eeprom.count(rev_key))) { + RFNOC_LOG_ERROR("set_db_eeprom() requires id, serial, and rev keys!"); + throw uhd::key_error( + "[X300] set_db_eeprom() requires id, serial, and rev keys!"); + } + + dboard_eeprom_t eeprom; + eeprom.id.from_string(bytes_to_str(db_eeprom.at(id_key))); + eeprom.serial = bytes_to_str(db_eeprom.at(serial_key)); + eeprom.revision = bytes_to_str(db_eeprom.at(rev_key)); + if (get_tree()->exists(DB_PATH / (key_prefix + "eeprom"))) { + get_tree() + ->access<dboard_eeprom_t>(DB_PATH / (key_prefix + "eeprom")) + .set(eeprom); + } else { + RFNOC_LOG_WARNING("Cannot set EEPROM, tree path does not exist."); + } + } + + + uhd::eeprom_map_t get_db_eeprom() + { + uhd::eeprom_map_t result; + if (get_tree()->exists(DB_PATH / "rx_eeprom")) { + const auto rx_eeprom = + get_tree()->access<dboard_eeprom_t>(DB_PATH / "rx_eeprom").get(); + result["rx_id"] = str_to_bytes(rx_eeprom.id.to_pp_string()); + result["rx_serial"] = str_to_bytes(rx_eeprom.serial); + result["rx_rev"] = str_to_bytes(rx_eeprom.revision); + } + if (get_tree()->exists(DB_PATH / "tx_eeprom")) { + const auto rx_eeprom = + get_tree()->access<dboard_eeprom_t>(DB_PATH / "rx_eeprom").get(); + result["tx_id"] = str_to_bytes(rx_eeprom.id.to_pp_string()); + result["tx_serial"] = str_to_bytes(rx_eeprom.serial); + result["tx_rev"] = str_to_bytes(rx_eeprom.revision); + } + return result; + } + + /************************************************************************** + * Radio Identification API Calls + *************************************************************************/ + std::string get_slot_name() const + { + return _radio_type == PRIMARY ? "A" : "B"; + } + + size_t get_chan_from_dboard_fe( + const std::string& fe, const direction_t direction) const + { + switch (direction) { + case uhd::TX_DIRECTION: + return _get_chan_from_map(_tx_fe_map, fe); + case uhd::RX_DIRECTION: + return _get_chan_from_map(_rx_fe_map, fe); + default: + UHD_THROW_INVALID_CODE_PATH(); + } + } + + std::string get_dboard_fe_from_chan( + const size_t chan, const uhd::direction_t direction) const + { + switch (direction) { + case uhd::TX_DIRECTION: + return _tx_fe_map.at(chan).db_fe_name; + case uhd::RX_DIRECTION: + return _rx_fe_map.at(chan).db_fe_name; + default: + UHD_THROW_INVALID_CODE_PATH(); + } + } + + std::string get_fe_name(const size_t chan, const uhd::direction_t direction) const + { + fs_path name_path = + get_db_path(direction == uhd::RX_DIRECTION ? "rx" : "tx", chan) / "name"; + if (!get_tree()->exists(name_path)) { + return get_dboard_fe_from_chan(chan, direction); + } + + return get_tree()->access<std::string>(name_path).get(); + } + + + virtual void set_command_time(uhd::time_spec_t time, const size_t chan) + { + node_t::set_command_time(time, chan); + // This is for TwinRX only: + fs_path cmd_time_path = get_db_path("rx", chan) / "time" / "cmd"; + if (get_tree()->exists(cmd_time_path)) { + get_tree()->access<time_spec_t>(cmd_time_path).set(time); + } + } + + /************************************************************************** + * MB Interface API Calls + *************************************************************************/ + uint32_t get_adc_rx_word() + { + return regs().peek32(regmap::RADIO_BASE_ADDR + regmap::REG_RX_DATA); + } + + void set_adc_test_word(const std::string& patterna, const std::string& patternb) + { + _adc->set_test_word(patterna, patternb); + } + + void set_adc_checker_enabled(const bool enb) + { + _regs->misc_outs_reg.write( + radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, enb ? 1 : 0); + } + + bool get_adc_checker_locked(const bool I) + { + return bool(_regs->misc_ins_reg.read( + I ? radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_LOCKED + : radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_LOCKED)); + } + + uint32_t get_adc_checker_error_code(const bool I) + { + return _regs->misc_ins_reg.get( + I ? radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_ERROR + : radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_ERROR); + } + + // Documented in x300_radio_mbc_iface.hpp + void self_test_adc(const uint32_t ramp_time_ms) + { + RFNOC_LOG_DEBUG("Running ADC self-cal..."); + // Bypass all front-end corrections + for (size_t i = 0; i < get_num_output_ports(); i++) { + _rx_fe_map[i].core->bypass_all(true); + } + + // Test basic patterns + _adc->set_test_word("ones", "ones"); + _check_adc(0xfffcfffc); + _adc->set_test_word("zeros", "zeros"); + _check_adc(0x00000000); + _adc->set_test_word("ones", "zeros"); + _check_adc(0xfffc0000); + _adc->set_test_word("zeros", "ones"); + _check_adc(0x0000fffc); + for (size_t k = 0; k < 14; k++) { + _adc->set_test_word("zeros", "custom", 1 << k); + _check_adc(1 << (k + 2)); + } + for (size_t k = 0; k < 14; k++) { + _adc->set_test_word("custom", "zeros", 1 << k); + _check_adc(1 << (k + 18)); + } + + // Turn on ramp pattern test + _adc->set_test_word("ramp", "ramp"); + _regs->misc_outs_reg.write( + radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + // Sleep added for SPI transactions to finish and ramp to start before checker is + // enabled. + std::this_thread::sleep_for(std::chrono::microseconds(1000)); + _regs->misc_outs_reg.write( + radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1); + + std::this_thread::sleep_for(std::chrono::milliseconds(ramp_time_ms)); + _regs->misc_ins_reg.refresh(); + + std::string i_status, q_status; + if (_regs->misc_ins_reg.get( + radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_LOCKED)) + if (_regs->misc_ins_reg.get( + radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_ERROR)) + i_status = "Bit Errors!"; + else + i_status = "Good"; + else + i_status = "Not Locked!"; + + if (_regs->misc_ins_reg.get( + radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_LOCKED)) + if (_regs->misc_ins_reg.get( + radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_ERROR)) + q_status = "Bit Errors!"; + else + q_status = "Good"; + else + q_status = "Not Locked!"; + + // Return to normal mode + _adc->set_test_word("normal", "normal"); + + if ((i_status != "Good") or (q_status != "Good")) { + throw uhd::runtime_error( + (boost::format("ADC self-test failed for %s. Ramp checker status: " + "{ADC_A=%s, ADC_B=%s}") + % get_unique_id() % i_status % q_status) + .str()); + } + + // Restore front-end corrections + for (size_t i = 0; i < get_num_output_ports(); i++) { + _rx_fe_map[i].core->bypass_all(false); + } + } + + void sync_dac() + { + _dac->sync(); + } + + void set_dac_sync(const bool enb, const uhd::time_spec_t& time) + { + if (time != uhd::time_spec_t(0.0)) { + set_command_time(time, 0); + } + _regs->misc_outs_reg.write( + radio_regmap_t::misc_outs_reg_t::DAC_SYNC, enb ? 1 : 0); + if (!enb && time != uhd::time_spec_t(0.0)) { + set_command_time(uhd::time_spec_t(0.0), 0); + } + } + + void dac_verify_sync() + { + _dac->verify_sync(); + } + +private: + /************************************************************************** + * ADC Control + *************************************************************************/ + //! Create the ADC/DAC objects, reset them, run ADC cal + void _init_codecs() + { + _regs = std::make_unique<radio_regmap_t>(get_block_id().get_block_count()); + _regs->initialize(*_wb_iface, true); + // Only Radio0 has the ADC/DAC reset bits + if (_radio_type == PRIMARY) { + RFNOC_LOG_TRACE("Resetting DAC and ADCs..."); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0); + _regs->misc_outs_reg.flush(); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 0); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 1); + _regs->misc_outs_reg.flush(); + } + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 1); + + RFNOC_LOG_TRACE("Creating ADC interface..."); + _adc = x300_adc_ctrl::make(_spi, DB_ADC_SEN); + RFNOC_LOG_TRACE("Creating DAC interface..."); + _dac = x300_dac_ctrl::make(_spi, DB_DAC_SEN, _master_clock_rate); + _self_cal_adc_capture_delay(); + + //////////////////////////////////////////////////////////////// + // create legacy codec control objects + //////////////////////////////////////////////////////////////// + // DAC has no gains + get_tree()->create<int>("tx_codec/gains"); + get_tree()->create<std::string>("tx_codec/name").set("ad9146"); + get_tree()->create<std::string>("rx_codec/name").set("ads62p48"); + get_tree() + ->create<meta_range_t>("rx_codec/gains/digital/range") + .set(meta_range_t(0, 6.0, 0.5)); + get_tree() + ->create<double>("rx_codec/gains/digital/value") + .add_coerced_subscriber([this](const double gain) { _adc->set_gain(gain); }) + .set(0); + } + + //! Calibrate delays on the ADC. This needs to happen before every session. + void _self_cal_adc_capture_delay() + { + RFNOC_LOG_TRACE("Running ADC capture delay self-cal..."); + constexpr uint32_t NUM_DELAY_STEPS = 32; // The IDELAYE2 element has 32 steps + // Retry self-cal if it fails in warmup situations + constexpr uint32_t NUM_RETRIES = 2; + constexpr int32_t MIN_WINDOW_LEN = 4; + + int32_t win_start = -1, win_stop = -1; + uint32_t iter = 0; + while (iter++ < NUM_RETRIES) { + for (uint32_t dly_tap = 0; dly_tap < NUM_DELAY_STEPS; dly_tap++) { + // Apply delay + _regs->misc_outs_reg.write( + radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_VAL, dly_tap); + _regs->misc_outs_reg.write( + radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_STB, 1); + _regs->misc_outs_reg.write( + radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_STB, 0); + + uint32_t err_code = 0; + + // -- Test I Channel -- + // Put ADC in ramp test mode. Tie the other channel to all ones. + _adc->set_test_word("ramp", "ones"); + // Turn on the pattern checker in the FPGA. It will lock when it sees a + // zero and count deviations from the expected value + _regs->misc_outs_reg.write( + radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + _regs->misc_outs_reg.write( + radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1); + // 5ms @ 200MHz = 1 million samples + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + if (_regs->misc_ins_reg.read( + radio_regmap_t::misc_ins_reg_t::ADC_CHECKER0_I_LOCKED)) { + err_code += _regs->misc_ins_reg.get( + radio_regmap_t::misc_ins_reg_t::ADC_CHECKER0_I_ERROR); + } else { + err_code += 100; // Increment error code by 100 to indicate no lock + } + + // -- Test Q Channel -- + // Put ADC in ramp test mode. Tie the other channel to all ones. + _adc->set_test_word("ones", "ramp"); + // Turn on the pattern checker in the FPGA. It will lock when it sees a + // zero and count deviations from the expected value + _regs->misc_outs_reg.write( + radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + _regs->misc_outs_reg.write( + radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1); + // 5ms @ 200MHz = 1 million samples + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + if (_regs->misc_ins_reg.read( + radio_regmap_t::misc_ins_reg_t::ADC_CHECKER0_Q_LOCKED)) { + err_code += _regs->misc_ins_reg.get( + radio_regmap_t::misc_ins_reg_t::ADC_CHECKER0_Q_ERROR); + } else { + err_code += 100; // Increment error code by 100 to indicate no lock + } + + if (err_code == 0) { + if (win_start == -1) { // This is the first window + win_start = dly_tap; + win_stop = dly_tap; + } else { // We are extending the window + win_stop = dly_tap; + } + } else { + if (win_start != -1) { // A valid window turned invalid + if (win_stop - win_start >= MIN_WINDOW_LEN) { + break; // Valid window found + } else { + win_start = -1; // Reset window + } + } + } + // UHD_LOGGER_INFO("X300 RADIO") << (boost::format("CapTap=%d, Error=%d") + // % dly_tap % err_code); + } + + // Retry the self-cal if it fails + if ((win_start == -1 || (win_stop - win_start) < MIN_WINDOW_LEN) + && iter < NUM_RETRIES /*not last iteration*/) { + win_start = -1; + win_stop = -1; + std::this_thread::sleep_for(std::chrono::milliseconds(2000)); + } else { + break; + } + } + _adc->set_test_word("normal", "normal"); + _regs->misc_outs_reg.write( + radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + + if (win_start == -1) { + throw uhd::runtime_error("self_cal_adc_capture_delay: Self calibration " + "failed. Convergence error."); + } + + if (win_stop - win_start < MIN_WINDOW_LEN) { + throw uhd::runtime_error( + "self_cal_adc_capture_delay: Self calibration failed. " + "Valid window too narrow."); + } + + uint32_t ideal_tap = (win_stop + win_start) / 2; + _regs->misc_outs_reg.write( + radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_VAL, ideal_tap); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_STB, 1); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_STB, 0); + + double tap_delay = (1.0e12 / 200e6) / (2 * 32); // in ps + RFNOC_LOG_DEBUG( + boost::format("ADC capture delay self-cal done (Tap=%d, Window=%d, " + "TapDelay=%.3fps, Iter=%d)") + % ideal_tap % (win_stop - win_start) % tap_delay % iter); + } + + //! Verify that the output of the ADC matches an expected \p val + void _check_adc(const uint32_t val) + { + // Wait for previous control transaction to flush + get_adc_rx_word(); + // Wait for ADC test pattern to propagate + std::this_thread::sleep_for(std::chrono::microseconds(5)); + // Read value of RX readback register and verify, adapt for I inversion + // in FPGA + const uint32_t adc_rb = get_adc_rx_word() ^ 0xfffc0000; + if (val != adc_rb) { + RFNOC_LOG_ERROR(boost::format("ADC self-test failed! (Exp=0x%x, Got=0x%x)") + % val % adc_rb); + throw uhd::runtime_error("ADC self-test failed!"); + } + } + + void reset_codec() + { + RFNOC_LOG_TRACE("Start reset_codec"); + if (_radio_type == PRIMARY) { // ADC/DAC reset lines only exist in Radio0 + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0); + _regs->misc_outs_reg.flush(); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 0); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 1); + _regs->misc_outs_reg.flush(); + } + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 1); + UHD_ASSERT_THROW(bool(_adc)); + UHD_ASSERT_THROW(bool(_dac)); + _adc->reset(); + _dac->reset(); + RFNOC_LOG_TRACE("Done reset_codec"); + } + + /************************************************************************** + * DBoard + *************************************************************************/ + fs_path get_db_path(const std::string& dir, const size_t chan) const + { + UHD_ASSERT_THROW(dir == "rx" || dir == "tx"); + if (dir == "rx" && chan >= get_num_output_ports()) { + throw uhd::key_error("Invalid RX channel: " + std::to_string(chan)); + } + if (dir == "tx" && chan >= get_num_input_ports()) { + throw uhd::key_error("Invalid TX channel: " + std::to_string(chan)); + } + return DB_PATH / (dir + "_frontends") + / ((dir == "rx") ? _rx_fe_map.at(chan).db_fe_name + : _tx_fe_map.at(chan).db_fe_name); + } + + fs_path get_fe_path(const std::string& dir, const size_t chan) const + { + UHD_ASSERT_THROW(dir == "rx" || dir == "tx"); + if (dir == "rx" && chan >= get_num_output_ports()) { + throw uhd::key_error("Invalid RX channel: " + std::to_string(chan)); + } + if (dir == "tx" && chan >= get_num_input_ports()) { + throw uhd::key_error("Invalid TX channel: " + std::to_string(chan)); + } + return FE_PATH / (dir + "_fe_corrections") + / ((dir == "rx") ? _rx_fe_map.at(chan).db_fe_name + : _tx_fe_map.at(chan).db_fe_name); + } + + void _init_db() + { + constexpr size_t BASE_ADDR = 0x50; + constexpr size_t RX_EEPROM_ADDR = 0x5; + constexpr size_t TX_EEPROM_ADDR = 0x4; + constexpr size_t GDB_EEPROM_ADDR = 0x1; + static const std::vector<size_t> EEPROM_ADDRS{ + RX_EEPROM_ADDR, TX_EEPROM_ADDR, GDB_EEPROM_ADDR}; + static const std::vector<std::string> EEPROM_PATHS{ + "rx_eeprom", "tx_eeprom", "gdb_eeprom"}; + const size_t DB_OFFSET = (_radio_type == PRIMARY) ? 0x0 : 0x2; + auto zpu_i2c = _x300_mb_control->get_zpu_i2c(); + auto clock = _x300_mb_control->get_clock_ctrl(); + for (size_t i = 0; i < EEPROM_ADDRS.size(); i++) { + const size_t addr = EEPROM_ADDRS[i] + DB_OFFSET; + // Load EEPROM + _db_eeproms[addr].load(*zpu_i2c, BASE_ADDR | addr); + // Add to tree + get_tree() + ->create<dboard_eeprom_t>(DB_PATH / EEPROM_PATHS[i]) + .set(_db_eeproms[addr]) + .add_coerced_subscriber([this, zpu_i2c, BASE_ADDR, addr]( + const uhd::usrp::dboard_eeprom_t& db_eeprom) { + _set_db_eeprom(zpu_i2c, BASE_ADDR | addr, db_eeprom); + }); + } + + // create a new dboard interface + x300_dboard_iface_config_t db_config; + db_config.gpio = gpio_atr::db_gpio_atr_3000::make(_wb_iface, + x300_regs::SR_DB_GPIO, + x300_regs::RB_DB_GPIO, + x300_regs::PERIPH_REG_OFFSET); + db_config.spi = _spi; + db_config.rx_spi_slaveno = DB_RX_SEN; + db_config.tx_spi_slaveno = DB_TX_SEN; + db_config.i2c = zpu_i2c; + db_config.clock = clock; + db_config.which_rx_clk = (_radio_type == PRIMARY) ? X300_CLOCK_WHICH_DB0_RX + : X300_CLOCK_WHICH_DB1_RX; + db_config.which_tx_clk = (_radio_type == PRIMARY) ? X300_CLOCK_WHICH_DB0_TX + : X300_CLOCK_WHICH_DB1_TX; + db_config.dboard_slot = (_radio_type == PRIMARY) ? 0 : 1; + db_config.cmd_time_ctrl = _wb_iface; + + // create a new dboard manager + RFNOC_LOG_TRACE("Creating DB interface..."); + _db_iface = boost::make_shared<x300_dboard_iface>(db_config); + RFNOC_LOG_TRACE("Creating DB manager..."); + _db_manager = dboard_manager::make(_db_eeproms[RX_EEPROM_ADDR + DB_OFFSET], + _db_eeproms[TX_EEPROM_ADDR + DB_OFFSET], + _db_eeproms[GDB_EEPROM_ADDR + DB_OFFSET], + _db_iface, + get_tree()->subtree(DB_PATH), + true // defer daughterboard initialization + ); + RFNOC_LOG_TRACE("DB Manager Initialization complete."); + + // The X3x0 radio block defaults to two ports, but most daughterboards + // only have one frontend. So we now reduce the number of actual ports + // based on what is connected. + // Note: The Basic and LF boards pretend they have four frontends, + // which a hack from the past. However, they actually only have one + // frontend, and we select the AB/BA/A/B setting through the antenna. + // The easiest way to identify those boards is because they're the only + // ones with four frontends. + // For all other cases, we reduce the number of frontends to one. + const size_t num_tx_frontends = _db_manager->get_tx_frontends().size(); + const size_t num_rx_frontends = _db_manager->get_rx_frontends().size(); + if (num_tx_frontends == 4) { + RFNOC_LOG_TRACE("Found four frontends, inferring BasicTX or LFTX."); + set_num_input_ports(1); + } else if (num_tx_frontends == 2 || num_tx_frontends == 1) { + set_num_input_ports(num_tx_frontends); + } else { + throw uhd::runtime_error("Unexpected number of TX frontends!"); + } + if (num_rx_frontends == 4) { + RFNOC_LOG_TRACE("Found four frontends, inferring BasicRX or LFRX."); + set_num_output_ports(1); + } else if (num_rx_frontends == 2 || num_rx_frontends == 1) { + set_num_output_ports(num_rx_frontends); + } else { + throw uhd::runtime_error("Unexpected number of RX frontends!"); + } + // This is specific to TwinRX. Due to driver legacy, we think we have a + // Tx frontend even though we don't. We thus hard-code that knowledge + // here. + if (num_rx_frontends == 2 + && boost::starts_with( + get_tree()->access<std::string>(DB_PATH / "rx_frontends/0/name").get(), + "TwinRX")) { + set_num_input_ports(0); + } + RFNOC_LOG_TRACE("Num Active Frontends: RX: " << get_num_output_ports() + << " TX: " << get_num_input_ports()); + } + + void _init_dboards() + { + size_t rx_chan = 0; + size_t tx_chan = 0; + for (const std::string& fe : _db_manager->get_rx_frontends()) { + if (rx_chan >= get_num_output_ports()) { + break; + } + _rx_fe_map[rx_chan].db_fe_name = fe; + _db_iface->add_rx_fe(fe, _rx_fe_map[rx_chan].core); + const fs_path fe_path(DB_PATH / "rx_frontends" / fe); + const std::string conn = + get_tree()->access<std::string>(fe_path / "connection").get(); + const double if_freq = + (get_tree()->exists(fe_path / "if_freq/value")) + ? get_tree()->access<double>(fe_path / "if_freq/value").get() + : 0.0; + _rx_fe_map[rx_chan].core->set_fe_connection( + usrp::fe_connection_t(conn, if_freq)); + rx_chan++; + } + for (const std::string& fe : _db_manager->get_tx_frontends()) { + if (tx_chan >= get_num_input_ports()) { + break; + } + _tx_fe_map[tx_chan].db_fe_name = fe; + const fs_path fe_path(DB_PATH / "tx_frontends" / fe); + const std::string conn = + get_tree()->access<std::string>(fe_path / "connection").get(); + _tx_fe_map[tx_chan].core->set_mux(conn); + tx_chan++; + } + UHD_ASSERT_THROW(rx_chan or tx_chan); + const double actual_rate = rx_chan ? _rx_fe_map.at(0).core->get_output_rate() + : get_rate(); + RFNOC_LOG_DEBUG("Actual sample rate: " << (actual_rate / 1e6) << " Msps."); + radio_control_impl::set_rate(actual_rate); + + // Initialize the daughterboards now that frontend cores and connections exist + _db_manager->initialize_dboards(); + + // now that dboard is created -- register into rx antenna event + if (not _rx_fe_map.empty()) { + for (size_t i = 0; i < get_num_output_ports(); i++) { + if (get_tree()->exists(get_db_path("rx", i) / "antenna" / "value")) { + // We need a desired subscriber for antenna/value because the experts + // don't coerce that property. + get_tree() + ->access<std::string>(get_db_path("rx", i) / "antenna" / "value") + .add_desired_subscriber([this, i](const std::string& led) { + _update_atr_leds(led, i); + }) + .update(); + } else { + _update_atr_leds("", i); // init anyway, even if never called + } + } + } + + // bind frontend corrections to the dboard freq props + if (not _tx_fe_map.empty()) { + for (size_t i = 0; i < get_num_input_ports(); i++) { + if (get_tree()->exists(get_db_path("tx", i) / "freq" / "value")) { + get_tree() + ->access<double>(get_db_path("tx", i) / "freq" / "value") + .add_coerced_subscriber([this, i](const double freq) { + set_tx_fe_corrections(freq, i); + }); + } + } + } + if (not _rx_fe_map.empty()) { + for (size_t i = 0; i < get_num_output_ports(); i++) { + if (get_tree()->exists(get_db_path("rx", i) / "freq" / "value")) { + get_tree() + ->access<double>(get_db_path("rx", i) / "freq" / "value") + .add_coerced_subscriber([this, i](const double freq) { + set_rx_fe_corrections(freq, i); + }); + } + } + } + + //////////////////////////////////////////////////////////////// + // Set gain groups + // Note: The actual gain control comes from the daughterboard drivers, thus, + // we need to call into the prop tree at the appropriate location in order + // to modify the gains. + //////////////////////////////////////////////////////////////// + // TX + for (size_t chan = 0; chan < get_num_input_ports(); chan++) { + fs_path rf_gains_path(get_db_path("tx", chan) / "gains"); + if (!get_tree()->exists(rf_gains_path)) { + _tx_gain_groups[chan] = gain_group::make_zero(); + continue; + } + + std::vector<std::string> gain_stages = get_tree()->list(rf_gains_path); + if (gain_stages.empty()) { + _tx_gain_groups[chan] = gain_group::make_zero(); + continue; + } + + // DAC does not have a gain path + auto gg = gain_group::make(); + for (const auto& name : gain_stages) { + gg->register_fcns(name, + make_gain_fcns_from_subtree( + get_tree()->subtree(rf_gains_path / name)), + 1 /* high prio */); + } + _tx_gain_groups[chan] = gg; + } + // RX + for (size_t chan = 0; chan < get_num_output_ports(); chan++) { + fs_path rf_gains_path(get_db_path("rx", chan) / "gains"); + fs_path adc_gains_path("rx_codec/gains"); + + auto gg = gain_group::make(); + // ADC also has a gain path + for (const auto& name : get_tree()->list(adc_gains_path)) { + gg->register_fcns("ADC-" + name, + make_gain_fcns_from_subtree( + get_tree()->subtree(adc_gains_path / name)), + 0 /* low prio */); + } + if (get_tree()->exists(rf_gains_path)) { + for (const auto& name : get_tree()->list(rf_gains_path)) { + gg->register_fcns(name, + make_gain_fcns_from_subtree( + get_tree()->subtree(rf_gains_path / name)), + 1 /* high prio */); + } + } + _rx_gain_groups[chan] = gg; + } + } /* _init_dboards */ + + void _set_db_eeprom(i2c_iface::sptr i2c, + const size_t addr, + const uhd::usrp::dboard_eeprom_t& db_eeprom) + { + db_eeprom.store(*i2c, addr); + _db_eeproms[addr] = db_eeprom; + } + + void _update_atr_leds(const std::string& rx_ant, const size_t /*chan*/) + { + // The "RX1" port is used by TwinRX and the "TX/RX" port is used by all + // other full-duplex dboards. We need to handle both here. + const bool is_txrx = (rx_ant == "TX/RX" or rx_ant == "RX1"); + const int TXRX_RX = (1 << 0); + const int TXRX_TX = (1 << 1); + const int RX2_RX = (1 << 2); + _leds->set_atr_reg(gpio_atr::ATR_REG_IDLE, 0); + _leds->set_atr_reg(gpio_atr::ATR_REG_RX_ONLY, is_txrx ? TXRX_RX : RX2_RX); + _leds->set_atr_reg(gpio_atr::ATR_REG_TX_ONLY, TXRX_TX); + _leds->set_atr_reg(gpio_atr::ATR_REG_FULL_DUPLEX, RX2_RX | TXRX_TX); + } + + void set_rx_fe_corrections(const double lo_freq, const size_t chan) + { + if (not _ignore_cal_file) { + apply_rx_fe_corrections(get_tree(), + get_tree()->access<dboard_eeprom_t>(DB_PATH / "rx_eeprom").get().serial, + get_fe_path("rx", chan), + lo_freq); + } + } + + void set_tx_fe_corrections(const double lo_freq, const size_t chan) + { + if (not _ignore_cal_file) { + apply_tx_fe_corrections(get_tree(), + get_tree()->access<dboard_eeprom_t>(DB_PATH / "tx_eeprom").get().serial, + get_fe_path("tx", chan), + lo_freq); + } + } + + /************************************************************************** + * noc_block_base API + *************************************************************************/ + //! Safely shut down all peripherals + // + // Reminder: After this is called, no peeks and pokes are allowed! + void deinit() + { + RFNOC_LOG_TRACE("deinit()"); + // Reset daughterboard + _db_manager.reset(); + _db_iface.reset(); + // Reset codecs + if (_radio_type == PRIMARY) { + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0); + } + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 0); + _regs->misc_outs_reg.flush(); + _adc.reset(); + _dac.reset(); + // Destroy all other periph controls + _spi.reset(); + _fp_gpio.reset(); + _leds.reset(); + _rx_fe_map.clear(); + _tx_fe_map.clear(); + } + + bool check_topology(const std::vector<size_t>& connected_inputs, + const std::vector<size_t>& connected_outputs) + { + RFNOC_LOG_TRACE("check_topology()"); + if (!node_t::check_topology(connected_inputs, connected_outputs)) { + return false; + } + + for (size_t chan = 0; chan < get_num_input_ports(); chan++) { + const auto fe_enable_path = get_db_path("tx", chan) / "enabled"; + if (get_tree()->exists(fe_enable_path)) { + const bool chan_active = std::any_of(connected_inputs.cbegin(), + connected_inputs.cend(), + [chan](const size_t input) { return input == chan; }); + RFNOC_LOG_TRACE( + "Enabling TX chan " << chan << ": " << (chan_active ? "Yes" : "No")); + get_tree()->access<bool>(fe_enable_path).set(chan_active); + } + } + + for (size_t chan = 0; chan < get_num_output_ports(); chan++) { + const auto fe_enable_path = get_db_path("rx", chan) / "enabled"; + if (get_tree()->exists(fe_enable_path)) { + const bool chan_active = std::any_of(connected_outputs.cbegin(), + connected_outputs.cend(), + [chan](const size_t output) { return output == chan; }); + RFNOC_LOG_TRACE( + "Enabling RX chan " << chan << ": " << (chan_active ? "Yes" : "No")); + get_tree()->access<bool>(fe_enable_path).set(chan_active); + } + } + + return true; + } + + + /************************************************************************** + * Attributes + *************************************************************************/ + //! Register space for the ADC and DAC + class radio_regmap_t : public uhd::soft_regmap_t + { + public: + class misc_outs_reg_t : public uhd::soft_reg32_wo_t + { + public: + UHD_DEFINE_SOFT_REG_FIELD(DAC_ENABLED, /*width*/ 1, /*shift*/ 0); //[0] + UHD_DEFINE_SOFT_REG_FIELD(DAC_RESET_N, /*width*/ 1, /*shift*/ 1); //[1] + UHD_DEFINE_SOFT_REG_FIELD(ADC_RESET, /*width*/ 1, /*shift*/ 2); //[2] + UHD_DEFINE_SOFT_REG_FIELD(ADC_DATA_DLY_STB, /*width*/ 1, /*shift*/ 3); //[3] + UHD_DEFINE_SOFT_REG_FIELD(ADC_DATA_DLY_VAL, /*width*/ 5, /*shift*/ 4); //[8:4] + UHD_DEFINE_SOFT_REG_FIELD( + ADC_CHECKER_ENABLED, /*width*/ 1, /*shift*/ 9); //[9] + UHD_DEFINE_SOFT_REG_FIELD(DAC_SYNC, /*width*/ 1, /*shift*/ 10); //[10] + + misc_outs_reg_t() : uhd::soft_reg32_wo_t(x300_regs::SR_MISC_OUTS) + { + // Initial values + set(DAC_ENABLED, 0); + set(DAC_RESET_N, 0); + set(ADC_RESET, 0); + set(ADC_DATA_DLY_STB, 0); + set(ADC_DATA_DLY_VAL, 16); + set(ADC_CHECKER_ENABLED, 0); + set(DAC_SYNC, 0); + } + } misc_outs_reg; + + class misc_ins_reg_t : public uhd::soft_reg64_ro_t + { + public: + UHD_DEFINE_SOFT_REG_FIELD( + ADC_CHECKER0_Q_LOCKED, /*width*/ 1, /*shift*/ 32); //[0] + UHD_DEFINE_SOFT_REG_FIELD( + ADC_CHECKER0_I_LOCKED, /*width*/ 1, /*shift*/ 33); //[1] + UHD_DEFINE_SOFT_REG_FIELD( + ADC_CHECKER1_Q_LOCKED, /*width*/ 1, /*shift*/ 34); //[2] + UHD_DEFINE_SOFT_REG_FIELD( + ADC_CHECKER1_I_LOCKED, /*width*/ 1, /*shift*/ 35); //[3] + UHD_DEFINE_SOFT_REG_FIELD( + ADC_CHECKER0_Q_ERROR, /*width*/ 1, /*shift*/ 36); //[4] + UHD_DEFINE_SOFT_REG_FIELD( + ADC_CHECKER0_I_ERROR, /*width*/ 1, /*shift*/ 37); //[5] + UHD_DEFINE_SOFT_REG_FIELD( + ADC_CHECKER1_Q_ERROR, /*width*/ 1, /*shift*/ 38); //[6] + UHD_DEFINE_SOFT_REG_FIELD( + ADC_CHECKER1_I_ERROR, /*width*/ 1, /*shift*/ 39); //[7] + + misc_ins_reg_t() : uhd::soft_reg64_ro_t(x300_regs::RB_MISC_IO) {} + } misc_ins_reg; + + radio_regmap_t(int radio_num) + : soft_regmap_t("radio" + std::to_string(radio_num) + "_regmap") + { + add_to_map(misc_outs_reg, "misc_outs_reg", PRIVATE); + add_to_map(misc_ins_reg, "misc_ins_reg", PRIVATE); + } + }; /* class radio_regmap_t */ + //! wb_iface Instance for _regs + uhd::timed_wb_iface::sptr _wb_iface; + //! Instantiation of regs object for ADC and DAC (MISC_OUT register) + std::unique_ptr<radio_regmap_t> _regs; + //! Reference to the MB controller, typecast + std::shared_ptr<x300_mb_controller> _x300_mb_control; + + //! Reference to the DBoard SPI core (also controls ADC/DAC) + spi_core_3000::sptr _spi; + //! Reference to the ADC controller + x300_adc_ctrl::sptr _adc; + //! Reference to the DAC controller + x300_dac_ctrl::sptr _dac; + //! Front-panel GPIO + usrp::gpio_atr::gpio_atr_3000::sptr _fp_gpio; + //! LEDs + usrp::gpio_atr::gpio_atr_3000::sptr _leds; + + struct rx_fe_perif + { + std::string name; + std::string db_fe_name; + rx_frontend_core_3000::sptr core; + }; + struct tx_fe_perif + { + std::string name; + std::string db_fe_name; + tx_frontend_core_200::sptr core; + }; + + std::unordered_map<size_t, rx_fe_perif> _rx_fe_map; + std::unordered_map<size_t, tx_fe_perif> _tx_fe_map; + + //! Cache of EEPROM info (one per channel) + std::unordered_map<size_t, usrp::dboard_eeprom_t> _db_eeproms; + //! Reference to DB manager + usrp::dboard_manager::sptr _db_manager; + //! Reference to DB iface + boost::shared_ptr<x300_dboard_iface> _db_iface; + + enum radio_connection_t { PRIMARY, SECONDARY }; + radio_connection_t _radio_type; + + bool _ignore_cal_file = false; + + std::unordered_map<size_t, uhd::gain_group::sptr> _tx_gain_groups; + std::unordered_map<size_t, uhd::gain_group::sptr> _rx_gain_groups; + + double _master_clock_rate = DEFAULT_RATE; +}; + +UHD_RFNOC_BLOCK_REGISTER_FOR_DEVICE_DIRECT( + x300_radio_control, RADIO_BLOCK, X300, "Radio", true, "radio_clk", "radio_clk") diff --git a/host/lib/usrp/x300/x300_radio_mbc_iface.hpp b/host/lib/usrp/x300/x300_radio_mbc_iface.hpp new file mode 100644 index 000000000..cb0d99306 --- /dev/null +++ b/host/lib/usrp/x300/x300_radio_mbc_iface.hpp @@ -0,0 +1,64 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_X300_MBC_IFACE_HPP +#define INCLUDED_LIBUHD_X300_MBC_IFACE_HPP + +#include <uhd/types/time_spec.hpp> +#include <cstddef> +#include <string> + +namespace uhd { namespace usrp { namespace x300 { + +class x300_radio_mbc_iface +{ +public: + virtual ~x300_radio_mbc_iface() {} + + //! Return the current output of the ADC via a register + virtual uint32_t get_adc_rx_word() = 0; + + //! Set ADC test word (see x300_adc_ctrl::set_test_word()) + virtual void set_adc_test_word( + const std::string& patterna, const std::string& patternb) = 0; + + //! Enable or disable the ADC checker + virtual void set_adc_checker_enabled(const bool enb) = 0; + + //! Query ADC checker lock bit + virtual bool get_adc_checker_locked(const bool I) = 0; + + //! Return current ADC error status + virtual uint32_t get_adc_checker_error_code(const bool I) = 0; + + /*! Runs some ADC self tests + * + * - First, the ADC gets set to produce a constant value and we see if it + * reaches the FPGA + * - Then, the ADC is put into ramp mode, and we see if we read the ramp + * with no errors + * + * \param ramp_time_ms The duration of the ramp test. Increasing the test + * will increase the probability of a bit error. + * \throws uhd::runtime_error if one or more bit errors occurred + */ + virtual void self_test_adc(const uint32_t ramp_time_ms) = 0; + + //! Call sync() on the DAC object + virtual void sync_dac() = 0; + + //! Set the FRAMEP/N sync pulse. If time is not zero, it will do so at the + // given time. + virtual void set_dac_sync( + const bool enb, const uhd::time_spec_t& time = uhd::time_spec_t(0.0)) = 0; + + //! Call verify_sync() on the DAC object + virtual void dac_verify_sync() = 0; +}; + +}}} // namespace uhd::usrp::x300 + +#endif /* INCLUDED_LIBUHD_X300_MBC_IFACE_HPP */ diff --git a/host/lib/usrp/x300/x300_regs.hpp b/host/lib/usrp/x300/x300_regs.hpp index d2677c05e..64ff63d77 100644 --- a/host/lib/usrp/x300/x300_regs.hpp +++ b/host/lib/usrp/x300/x300_regs.hpp @@ -11,6 +11,7 @@ #include <uhd/config.hpp> #include <uhd/utils/soft_register.hpp> #include <stdint.h> +#include <memory> static const int BL_ADDRESS = 0; static const int BL_DATA = 1; @@ -171,7 +172,7 @@ namespace uhd { namespace usrp { namespace x300 { class fw_regmap_t : public uhd::soft_regmap_t { public: - typedef boost::shared_ptr<fw_regmap_t> sptr; + using sptr = std::shared_ptr<fw_regmap_t>; class clk_ctrl_reg_t : public uhd::soft_reg32_wo_t { diff --git a/host/tests/devtest/devtest_e3xx.py b/host/tests/devtest/devtest_e3xx.py index a93c15ea5..9d21f8849 100755 --- a/host/tests/devtest/devtest_e3xx.py +++ b/host/tests/devtest/devtest_e3xx.py @@ -1,12 +1,16 @@ # # Copyright 2015 Ettus Research LLC # Copyright 2018 Ettus Research, a National Instruments Company +# Copyright 2019 Ettus Research, a National Instruments Brand # # SPDX-License-Identifier: GPL-3.0-or-later # """ -Run device tests for the E3XX series. +Run device tests for the E31X series. """ + +# pylint: disable=wrong-import-position +# pylint: disable=unused-import from usrp_probe_test import uhd_usrp_probe_test from benchmark_rate_test import uhd_benchmark_rate_test uhd_benchmark_rate_test.tests = { @@ -35,10 +39,15 @@ uhd_benchmark_rate_test.tests = { from rx_samples_to_file_test import rx_samples_to_file_test rx_samples_to_file_test.tests = { - 'default': { + 'chan0': { 'duration': 1, 'subdev': 'A:0', - 'rate': 5e6, + 'rate': 1e6, + }, + 'chan1': { + 'duration': 1, + 'subdev': 'A:1', + 'rate': 1e6, }, } @@ -48,7 +57,7 @@ uhd_tx_waveforms_test.tests = { 'chan': '0', }, 'chan1': { - 'chan': '0', + 'chan': '1', }, 'both_chans': { 'chan': '0,1', @@ -57,4 +66,3 @@ uhd_tx_waveforms_test.tests = { from tx_bursts_test import uhd_tx_bursts_test from test_pps_test import uhd_test_pps_test - |