From 0ac85e4d0186cb63cb98da90363f34947675b3e5 Mon Sep 17 00:00:00 2001
From: mattprost <matt.prost@ni.com>
Date: Thu, 5 Mar 2020 10:47:59 -0600
Subject: x300: lf/basic antenna API implementation

This results in a change of operation for LF/Basic Boards on
X300/X310 devices. The RX streaming mode will now be specified
by the antenna rather than the subdev: (AB or BA for complex
streaming, and A or B for real-mode streaming, with AB being
the default antenna value). For real-mode streaming, data is
collected as complex data with zeroed-out values in the
quadrature domain. The subdevs for these boards have been
changed to 0 and 1 for the RX channels, and 0 for the TX
channel, in order to align with subdev specs of other RFNoC
devices.

Note: the old streaming mode paradigm is still in place for
the N210.
---
 host/lib/usrp/cores/rx_frontend_core_3000.cpp |   1 +
 host/lib/usrp/dboard/db_basic_and_lf.cpp      | 189 ++++++++++++++++----------
 host/lib/usrp/dboard/db_basic_and_lf.hpp      |  35 +++++
 host/lib/usrp/x300/x300_radio_control.cpp     | 119 +++++++++++-----
 4 files changed, 234 insertions(+), 110 deletions(-)
 create mode 100644 host/lib/usrp/dboard/db_basic_and_lf.hpp

(limited to 'host/lib')

diff --git a/host/lib/usrp/cores/rx_frontend_core_3000.cpp b/host/lib/usrp/cores/rx_frontend_core_3000.cpp
index 788e222ed..1dc8be745 100644
--- a/host/lib/usrp/cores/rx_frontend_core_3000.cpp
+++ b/host/lib/usrp/cores/rx_frontend_core_3000.cpp
@@ -193,6 +193,7 @@ public:
     {
         switch (_fe_conn.get_sampling_mode()) {
             case fe_connection_t::REAL:
+                return _adc_rate;
             case fe_connection_t::HETERODYNE:
                 return _adc_rate / 2;
             default:
diff --git a/host/lib/usrp/dboard/db_basic_and_lf.cpp b/host/lib/usrp/dboard/db_basic_and_lf.cpp
index 5e8ac310e..aaeaff2a6 100644
--- a/host/lib/usrp/dboard/db_basic_and_lf.cpp
+++ b/host/lib/usrp/dboard/db_basic_and_lf.cpp
@@ -5,6 +5,7 @@
 // SPDX-License-Identifier: GPL-3.0-or-later
 //
 
+#include "db_basic_and_lf.hpp"
 #include <uhd/types/dict.hpp>
 #include <uhd/types/ranges.hpp>
 #include <uhd/usrp/dboard_base.hpp>
@@ -17,27 +18,7 @@
 
 using namespace uhd;
 using namespace uhd::usrp;
-
-/***********************************************************************
- * Constants
- **********************************************************************/
-namespace {
-constexpr uint32_t BASIC_TX_PID = 0x0000;
-constexpr uint32_t BASIC_RX_PID = 0x0001;
-constexpr uint32_t LF_TX_PID    = 0x000E;
-constexpr uint32_t LF_RX_PID    = 0x000F;
-
-constexpr double BASIC_MAX_BANDWIDTH = 250e6; // Hz
-constexpr double LF_MAX_BANDWIDTH    = 32e6; // Hz
-
-
-const std::map<std::string, double> subdev_bandwidth_scalar{
-    {"A", 1.0}, {"B", 1.0}, {"AB", 2.0}, {"BA", 2.0}};
-
-const uhd::dict<std::string, std::string> sd_name_to_conn =
-    boost::assign::map_list_of("AB", "IQ")("BA", "QI")("A", "I")("B", "Q");
-} // namespace
-
+using namespace uhd::usrp::dboard::basic_and_lf;
 
 /***********************************************************************
  * The basic and lf boards:
@@ -50,8 +31,6 @@ public:
     virtual ~basic_rx(void);
 
 private:
-    void set_rx_ant(const std::string& ant);
-
     double _max_freq;
 };
 
@@ -91,13 +70,21 @@ static dboard_base::sptr make_lf_tx(dboard_base::ctor_args_t args)
 UHD_STATIC_BLOCK(reg_basic_and_lf_dboards)
 {
     dboard_manager::register_dboard(
-        BASIC_TX_PID, &make_basic_tx, "Basic TX", sd_name_to_conn.keys());
+        BASIC_TX_PID, &make_basic_tx, "Basic TX", antenna_mode_to_conn.keys());
     dboard_manager::register_dboard(
-        BASIC_RX_PID, &make_basic_rx, "Basic RX", sd_name_to_conn.keys());
+        BASIC_RX_PID, &make_basic_rx, "Basic RX", antenna_mode_to_conn.keys());
     dboard_manager::register_dboard(
-        LF_TX_PID, &make_lf_tx, "LF TX", sd_name_to_conn.keys());
+        LF_TX_PID, &make_lf_tx, "LF TX", antenna_mode_to_conn.keys());
     dboard_manager::register_dboard(
-        LF_RX_PID, &make_lf_rx, "LF RX", sd_name_to_conn.keys());
+        LF_RX_PID, &make_lf_rx, "LF RX", antenna_mode_to_conn.keys());
+    dboard_manager::register_dboard(
+        BASIC_TX_RFNOC_PID, &make_basic_tx, "Basic TX", tx_frontends);
+    dboard_manager::register_dboard(
+        BASIC_RX_RFNOC_PID, &make_basic_rx, "Basic RX", rx_frontends);
+    dboard_manager::register_dboard(
+        LF_TX_RFNOC_PID, &make_lf_tx, "LF TX", tx_frontends);
+    dboard_manager::register_dboard(
+        LF_RX_RFNOC_PID, &make_lf_rx, "LF RX", rx_frontends);
 }
 
 /***********************************************************************
@@ -106,20 +93,30 @@ UHD_STATIC_BLOCK(reg_basic_and_lf_dboards)
 basic_rx::basic_rx(ctor_args_t args, double max_freq)
     : rx_dboard_base(args), _max_freq(max_freq)
 {
+    // Examine the frontend to use the RFNoC or the N210 implementation
+    // (preserves legacy behavior)
     const std::string fe_name(get_subdev_name());
-    const std::string fe_conn(sd_name_to_conn[fe_name]);
-    const std::string db_name(
-        str(boost::format("%s (%s)")
-            % ((get_rx_id() == BASIC_RX_PID) ? "BasicRX" : "LFRX") % fe_name));
+    const bool is_rfnoc_dev = std::find(rx_frontends.begin(), rx_frontends.end(), fe_name)
+                              != rx_frontends.end();
+    const std::string ant_mode(is_rfnoc_dev ? "AB" : fe_name);
+    const std::string fe_conn(antenna_mode_to_conn[ant_mode]);
+    const std::string db_name([this]() {
+        switch (get_rx_id().to_uint16()) {
+            case BASIC_RX_PID:
+            case BASIC_RX_RFNOC_PID:
+                return str(boost::format("%s (%s)") % "BasicRX" % get_subdev_name());
+            case LF_RX_PID:
+            case LF_RX_RFNOC_PID:
+                return str(boost::format("%s (%s)") % "LFRX" % get_subdev_name());
+            default:
+                UHD_THROW_INVALID_CODE_PATH();
+        }
+    }());
     UHD_LOG_TRACE("BASICRX",
-        "Initializing driver for: " << db_name << " IQ connection type: " << fe_conn);
-    const bool has_fe_conn_settings =
-        get_iface()->has_set_fe_connection(dboard_iface::UNIT_RX);
-    UHD_LOG_TRACE("BASICRX",
-        "Access to FE connection settings: " << (has_fe_conn_settings ? "Yes" : "No"));
+        "Initializing driver for: " << db_name << " IQ connection type: " << ant_mode);
+    UHD_LOG_TRACE("BASICRX", "Is RFNoC Device: " << (is_rfnoc_dev ? "Yes" : "No"));
 
-    std::vector<std::string> antenna_options =
-        has_fe_conn_settings ? sd_name_to_conn.keys() : std::vector<std::string>(1, "");
+    std::vector<std::string> antenna_options = antenna_mode_to_conn.keys();
 
     ////////////////////////////////////////////////////////////////////
     // Register properties
@@ -134,29 +131,43 @@ basic_rx::basic_rx(ctor_args_t args, double max_freq)
         .set(freq_range_t(-_max_freq, +_max_freq));
     this->get_rx_subtree()
         ->create<std::string>("antenna/value")
-        .set(has_fe_conn_settings ? fe_name : "");
-    if (has_fe_conn_settings) {
-        this->get_rx_subtree()
-            ->access<std::string>("antenna/value")
-            .add_coerced_subscriber(
-                [this](const std::string& ant) { this->set_rx_ant(ant); });
-    }
+        .set(is_rfnoc_dev ? ant_mode : "");
     this->get_rx_subtree()
         ->create<std::vector<std::string>>("antenna/options")
-        .set(antenna_options);
+        .set(is_rfnoc_dev ? antenna_options : std::vector<std::string>(1, ""));
     this->get_rx_subtree()->create<int>("sensors"); // phony property so this dir exists
     this->get_rx_subtree()
         ->create<std::string>("connection")
-        .set(sd_name_to_conn[get_subdev_name()]);
+        .set(antenna_mode_to_conn[ant_mode]);
     this->get_rx_subtree()->create<bool>("enabled").set(true); // always enabled
     this->get_rx_subtree()->create<bool>("use_lo_offset").set(false);
     this->get_rx_subtree()
         ->create<double>("bandwidth/value")
-        .set(subdev_bandwidth_scalar.at(get_subdev_name()) * _max_freq);
+        .set(antenna_mode_bandwidth_scalar.at(ant_mode) * _max_freq);
     this->get_rx_subtree()
         ->create<meta_range_t>("bandwidth/range")
-        .set(freq_range_t(subdev_bandwidth_scalar.at(get_subdev_name()) * _max_freq,
-            subdev_bandwidth_scalar.at(get_subdev_name()) * _max_freq));
+        .set(freq_range_t(antenna_mode_bandwidth_scalar.at(ant_mode) * _max_freq,
+            antenna_mode_bandwidth_scalar.at(ant_mode) * _max_freq));
+    if (is_rfnoc_dev) {
+        this->get_rx_subtree()
+            ->access<std::string>("antenna/value")
+            .add_coerced_subscriber([this](const std::string& ant) {
+                this->get_rx_subtree()
+                    ->access<std::string>("connection")
+                    .set(antenna_mode_to_conn[ant]);
+                this->get_rx_subtree()
+                    ->access<double>("bandwidth/value")
+                    .set(antenna_mode_bandwidth_scalar.at(ant) * _max_freq);
+                this->get_rx_subtree()
+                    ->access<meta_range_t>("bandwidth/range")
+                    .set(freq_range_t(antenna_mode_bandwidth_scalar.at(ant) * _max_freq,
+                        antenna_mode_bandwidth_scalar.at(ant) * _max_freq));
+                UHD_LOG_TRACE("BASICRX",
+                    "Changing antenna mode on channel: " << get_subdev_name()
+                                                         << " IQ connection type: "
+                                                         << antenna_mode_to_conn[ant]);
+            });
+    }
 
     // disable RX dboard clock by default
     this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, false);
@@ -172,34 +183,40 @@ basic_rx::~basic_rx(void)
     /* NOP */
 }
 
-void basic_rx::set_rx_ant(const std::string& ant)
-{
-    UHD_ASSERT_THROW(get_iface()->has_set_fe_connection(dboard_iface::UNIT_RX));
-    UHD_LOG_TRACE("BASICRX", "Setting antenna value to: " << ant);
-    get_iface()->set_fe_connection(dboard_iface::UNIT_RX,
-        get_subdev_name(),
-        usrp::fe_connection_t(sd_name_to_conn[ant], 0.0 /* IF */));
-}
-
 /***********************************************************************
  * Basic and LF TX dboard
  **********************************************************************/
 basic_tx::basic_tx(ctor_args_t args, double max_freq) : tx_dboard_base(args)
 {
+    // Examine the frontend to use the RFNoC or the N210 implementation
+    // (preserves legacy behavior)
+    const std::string fe_name(get_subdev_name());
+    const bool is_rfnoc_dev = std::find(tx_frontends.begin(), tx_frontends.end(), fe_name)
+                              != tx_frontends.end();
+    const std::string ant_mode(is_rfnoc_dev ? "AB" : fe_name);
     _max_freq = max_freq;
-    // this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true);
+    const std::string db_name([this]() {
+        switch (get_tx_id().to_uint16()) {
+            case BASIC_TX_PID:
+            case BASIC_TX_RFNOC_PID:
+                return str(boost::format("%s (%s)") % "BasicTX" % get_subdev_name());
+            case LF_TX_PID:
+            case LF_TX_RFNOC_PID:
+                return str(boost::format("%s (%s)") % "LFTX" % get_subdev_name());
+            default:
+                UHD_THROW_INVALID_CODE_PATH();
+        }
+    }());
+    UHD_LOG_TRACE("BASICTX",
+        "Initializing driver for: " << db_name << " IQ connection type: " << ant_mode);
+    UHD_LOG_TRACE("BASICTX", "Is RFNoC Device: " << (is_rfnoc_dev ? "Yes" : "No"));
+
+    std::vector<std::string> antenna_options = antenna_mode_to_conn.keys();
 
     ////////////////////////////////////////////////////////////////////
     // Register properties
     ////////////////////////////////////////////////////////////////////
-    if (get_tx_id() == BASIC_TX_PID) {
-        this->get_tx_subtree()->create<std::string>("name").set(
-            std::string(str(boost::format("BasicTX (%s)") % get_subdev_name())));
-    } else {
-        this->get_tx_subtree()->create<std::string>("name").set(
-            std::string(str(boost::format("LFTX (%s)") % get_subdev_name())));
-    }
-
+    this->get_tx_subtree()->create<std::string>("name").set(db_name);
     this->get_tx_subtree()->create<int>("gains"); // phony property so this dir exists
     this->get_tx_subtree()->create<double>("freq/value").set_publisher([]() {
         return 0.0;
@@ -207,21 +224,45 @@ basic_tx::basic_tx(ctor_args_t args, double max_freq) : tx_dboard_base(args)
     this->get_tx_subtree()
         ->create<meta_range_t>("freq/range")
         .set(freq_range_t(-_max_freq, +_max_freq));
-    this->get_tx_subtree()->create<std::string>("antenna/value").set("");
-    this->get_tx_subtree()->create<std::vector<std::string>>("antenna/options").set({""});
+    this->get_tx_subtree()
+        ->create<std::string>("antenna/value")
+        .set(is_rfnoc_dev ? ant_mode : "");
+    this->get_tx_subtree()
+        ->create<std::vector<std::string>>("antenna/options")
+        .set(is_rfnoc_dev ? antenna_options : std::vector<std::string>(1, ""));
     this->get_tx_subtree()->create<int>("sensors"); // phony property so this dir exists
     this->get_tx_subtree()
         ->create<std::string>("connection")
-        .set(sd_name_to_conn[get_subdev_name()]);
+        .set(antenna_mode_to_conn[ant_mode]);
     this->get_tx_subtree()->create<bool>("enabled").set(true); // always enabled
     this->get_tx_subtree()->create<bool>("use_lo_offset").set(false);
     this->get_tx_subtree()
         ->create<double>("bandwidth/value")
-        .set(subdev_bandwidth_scalar.at(get_subdev_name()) * _max_freq);
+        .set(antenna_mode_bandwidth_scalar.at(ant_mode) * _max_freq);
     this->get_tx_subtree()
         ->create<meta_range_t>("bandwidth/range")
-        .set(freq_range_t(subdev_bandwidth_scalar.at(get_subdev_name()) * _max_freq,
-            subdev_bandwidth_scalar.at(get_subdev_name()) * _max_freq));
+        .set(freq_range_t(antenna_mode_bandwidth_scalar.at(ant_mode) * _max_freq,
+            antenna_mode_bandwidth_scalar.at(ant_mode) * _max_freq));
+    if (is_rfnoc_dev) {
+        this->get_tx_subtree()
+            ->access<std::string>("antenna/value")
+            .add_coerced_subscriber([this](const std::string& ant) {
+                this->get_tx_subtree()
+                    ->access<std::string>("connection")
+                    .set(antenna_mode_to_conn[ant]);
+                this->get_tx_subtree()
+                    ->access<double>("bandwidth/value")
+                    .set(antenna_mode_bandwidth_scalar.at(ant) * _max_freq);
+                this->get_tx_subtree()
+                    ->access<meta_range_t>("bandwidth/range")
+                    .set(freq_range_t(antenna_mode_bandwidth_scalar.at(ant) * _max_freq,
+                        antenna_mode_bandwidth_scalar.at(ant) * _max_freq));
+                UHD_LOG_TRACE("BASICTX",
+                    "Changing antenna mode for channel: " << get_subdev_name()
+                                                          << " IQ connection type: "
+                                                          << antenna_mode_to_conn[ant]);
+            });
+    }
 
     // disable TX dboard clock by default
     this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, false);
diff --git a/host/lib/usrp/dboard/db_basic_and_lf.hpp b/host/lib/usrp/dboard/db_basic_and_lf.hpp
new file mode 100644
index 000000000..c306926eb
--- /dev/null
+++ b/host/lib/usrp/dboard/db_basic_and_lf.hpp
@@ -0,0 +1,35 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/types/dict.hpp>
+#include <boost/assign/list_of.hpp>
+
+namespace uhd { namespace usrp { namespace dboard { namespace basic_and_lf {
+constexpr uint32_t BASIC_TX_PID = 0x0000;
+constexpr uint32_t BASIC_RX_PID = 0x0001;
+constexpr uint32_t LF_TX_PID    = 0x000E;
+constexpr uint32_t LF_RX_PID    = 0x000F;
+// The PIDs of these dboards are duplicated to support a new operating mode
+// on RFNoC devices, while maintaining legacy for the USRP2
+constexpr uint32_t RFNOC_PID_FLAG     = 0x6300;
+constexpr uint32_t BASIC_TX_RFNOC_PID = BASIC_TX_PID | RFNOC_PID_FLAG; // 0x6300
+constexpr uint32_t BASIC_RX_RFNOC_PID = BASIC_RX_PID | RFNOC_PID_FLAG; // 0x6301
+constexpr uint32_t LF_TX_RFNOC_PID    = LF_TX_PID | RFNOC_PID_FLAG; // 0x630E
+constexpr uint32_t LF_RX_RFNOC_PID    = LF_RX_PID | RFNOC_PID_FLAG; // 0x630F
+
+constexpr double BASIC_MAX_BANDWIDTH = 250e6; // Hz
+constexpr double LF_MAX_BANDWIDTH    = 32e6; // Hz
+
+
+static const std::vector<std::string> rx_frontends{"0", "1"};
+static const std::vector<std::string> tx_frontends{"0"};
+
+static const std::map<std::string, double> antenna_mode_bandwidth_scalar{
+    {"A", 1.0}, {"B", 1.0}, {"AB", 2.0}, {"BA", 2.0}};
+
+static const uhd::dict<std::string, std::string> antenna_mode_to_conn =
+    boost::assign::map_list_of("AB", "IQ")("BA", "QI")("A", "I")("B", "Q");
+}}}}; // namespace uhd::usrp::dboard::basic_and_lf
diff --git a/host/lib/usrp/x300/x300_radio_control.cpp b/host/lib/usrp/x300/x300_radio_control.cpp
index 1bd88164a..02a46f0e0 100644
--- a/host/lib/usrp/x300/x300_radio_control.cpp
+++ b/host/lib/usrp/x300/x300_radio_control.cpp
@@ -4,6 +4,7 @@
 // SPDX-License-Identifier: GPL-3.0-or-later
 //
 
+#include "../dboard/db_basic_and_lf.hpp"
 #include "x300_adc_ctrl.hpp"
 #include "x300_dac_ctrl.hpp"
 #include "x300_dboard_iface.hpp"
@@ -208,10 +209,12 @@ public:
 
         // Properties
         for (auto& samp_rate_prop : _samp_rate_in) {
-            samp_rate_prop.set(get_rate());
+            set_property(
+                samp_rate_prop.get_id(), get_rate(), samp_rate_prop.get_src_info());
         }
         for (auto& samp_rate_prop : _samp_rate_out) {
-            samp_rate_prop.set(get_rate());
+            set_property(
+                samp_rate_prop.get_id(), get_rate(), samp_rate_prop.get_src_info());
         }
     } /* ctor */
 
@@ -237,6 +240,16 @@ public:
 
     void set_tx_antenna(const std::string& ant, const size_t chan)
     {
+        // Antenna changes may result in a change of streaming mode for LF/Basic dboards
+        if (_basic_lf_tx) {
+            if (not uhd::usrp::dboard::basic_and_lf::antenna_mode_to_conn.has_key(ant)) {
+                throw uhd::lookup_error(
+                    str(boost::format("Invalid antenna mode: %s") % ant));
+            }
+            const std::string connection =
+                uhd::usrp::dboard::basic_and_lf::antenna_mode_to_conn[ant];
+            _tx_fe_map[chan].core->set_mux(connection);
+        }
         get_tree()
             ->access<std::string>(get_db_path("tx", chan) / "antenna" / "value")
             .set(ant);
@@ -259,6 +272,18 @@ public:
 
     void set_rx_antenna(const std::string& ant, const size_t chan)
     {
+        // Antenna changes may result in a change of streaming mode for LF/Basic dboards
+        if (_basic_lf_rx) {
+            if (not uhd::usrp::dboard::basic_and_lf::antenna_mode_to_conn.has_key(ant)) {
+                throw uhd::lookup_error(
+                    str(boost::format("Invalid antenna mode: %s") % ant));
+            }
+            const std::string connection =
+                uhd::usrp::dboard::basic_and_lf::antenna_mode_to_conn[ant];
+            const double if_freq = 0.0;
+            _rx_fe_map[chan].core->set_fe_connection(
+                usrp::fe_connection_t(connection, if_freq));
+        }
         get_tree()
             ->access<std::string>(get_db_path("rx", chan) / "antenna" / "value")
             .set(ant);
@@ -1022,7 +1047,7 @@ public:
     }
 
     size_t get_chan_from_dboard_fe(
-        const std::string& fe, const direction_t direction) const
+        const std::string& fe, const uhd::direction_t direction) const
     {
         switch (direction) {
             case uhd::TX_DIRECTION:
@@ -1448,6 +1473,22 @@ private:
             const size_t addr = EEPROM_ADDRS[i] + DB_OFFSET;
             // Load EEPROM
             _db_eeproms[addr].load(*zpu_i2c, BASE_ADDR | addr);
+            // Use the RFNoC implementation for Basic/LF dboards
+            uint16_t dboard_pid = _db_eeproms[addr].id.to_uint16();
+            switch (dboard_pid) {
+                case uhd::usrp::dboard::basic_and_lf::BASIC_RX_PID:
+                case uhd::usrp::dboard::basic_and_lf::LF_RX_PID:
+                    dboard_pid |= uhd::usrp::dboard::basic_and_lf::RFNOC_PID_FLAG;
+                    _db_eeproms[addr].id = dboard_pid;
+                    _basic_lf_rx         = true;
+                    break;
+                case uhd::usrp::dboard::basic_and_lf::BASIC_TX_PID:
+                case uhd::usrp::dboard::basic_and_lf::LF_TX_PID:
+                    dboard_pid |= uhd::usrp::dboard::basic_and_lf::RFNOC_PID_FLAG;
+                    _db_eeproms[addr].id = dboard_pid;
+                    _basic_lf_tx         = true;
+                    break;
+            }
             // Add to tree
             get_tree()
                 ->create<dboard_eeprom_t>(DB_PATH / EEPROM_PATHS[i])
@@ -1489,29 +1530,22 @@ private:
         );
         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.
+        // The X3x0 radio block defaults to a maximum of two ports, but
+        // many daughterboards have fewer possible frontends. So we now
+        // reduce the number of actual ports based on what is connected.
+        // Note: The Basic and LF boards have two possible frontends on
+        // the rx side, and one on the tx side. TwinRX boards have two
+        // possible rx frontends, and require up to two ports on the rx side.
+        // For all other cases, the number of possible frontends is one for
+        // rx and tx.
         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) {
+        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) {
+        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!");
@@ -1537,28 +1571,14 @@ private:
             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));
+            _set_rx_fe(fe, rx_chan);
             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);
+            _set_tx_fe(fe, tx_chan);
             tx_chan++;
         }
         UHD_ASSERT_THROW(rx_chan or tx_chan);
@@ -1689,6 +1709,30 @@ private:
         _leds->set_atr_reg(gpio_atr::ATR_REG_FULL_DUPLEX, RX2_RX | TXRX_TX);
     }
 
+    void _set_rx_fe(const std::string& fe, const size_t chan)
+    {
+        _rx_fe_map[chan].db_fe_name = fe;
+        _db_iface->add_rx_fe(fe, _rx_fe_map[chan].core);
+        const std::string connection =
+            get_tree()->access<std::string>(get_db_path("rx", chan) / "connection").get();
+        const double if_freq =
+            (get_tree()->exists(get_db_path("rx", chan) / "if_freq" / "value"))
+                ? get_tree()
+                      ->access<double>(get_db_path("rx", chan) / "if_freq" / "value")
+                      .get()
+                : 0.0;
+        _rx_fe_map[chan].core->set_fe_connection(
+            usrp::fe_connection_t(connection, if_freq));
+    }
+
+    void _set_tx_fe(const std::string& fe, const size_t chan)
+    {
+        _tx_fe_map[chan].db_fe_name = fe;
+        const std::string connection =
+            get_tree()->access<std::string>(get_db_path("tx", chan) / "connection").get();
+        _tx_fe_map[chan].core->set_mux(connection);
+    }
+
     void set_rx_fe_corrections(const double lo_freq, const size_t chan)
     {
         if (not _ignore_cal_file) {
@@ -1867,6 +1911,9 @@ private:
         tx_frontend_core_200::sptr core;
     };
 
+    bool _basic_lf_rx;
+    bool _basic_lf_tx;
+
     std::unordered_map<size_t, rx_fe_perif> _rx_fe_map;
     std::unordered_map<size_t, tx_fe_perif> _tx_fe_map;
 
-- 
cgit v1.2.3