aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib/usrp/x300
diff options
context:
space:
mode:
authorMartin Braun <martin.braun@ettus.com>2019-07-03 20:15:35 -0700
committerMartin Braun <martin.braun@ettus.com>2019-11-26 12:16:25 -0800
commitc256b9df6502536c2e451e690f1ad5962c664d1a (patch)
treea83ad13e6f5978bbe14bb3ecf8294ba1e3d28db4 /host/lib/usrp/x300
parent9a8435ed998fc5c65257f4c55768750b227ab19e (diff)
downloaduhd-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/lib/usrp/x300')
-rw-r--r--host/lib/usrp/x300/CMakeLists.txt8
-rw-r--r--host/lib/usrp/x300/x300_conn_mgr.hpp11
-rw-r--r--host/lib/usrp/x300/x300_defaults.hpp2
-rw-r--r--host/lib/usrp/x300/x300_eth_mgr.cpp370
-rw-r--r--host/lib/usrp/x300/x300_eth_mgr.hpp34
-rw-r--r--host/lib/usrp/x300/x300_fw_common.h2
-rw-r--r--host/lib/usrp/x300/x300_impl.cpp524
-rw-r--r--host/lib/usrp/x300/x300_impl.hpp130
-rw-r--r--host/lib/usrp/x300/x300_io_impl.cpp59
-rw-r--r--host/lib/usrp/x300/x300_mb_controller.cpp731
-rw-r--r--host/lib/usrp/x300/x300_mb_controller.hpp153
-rw-r--r--host/lib/usrp/x300/x300_mb_iface.cpp226
-rw-r--r--host/lib/usrp/x300/x300_pcie_mgr.cpp233
-rw-r--r--host/lib/usrp/x300/x300_pcie_mgr.hpp31
-rw-r--r--host/lib/usrp/x300/x300_prop_tree.cpp117
-rw-r--r--host/lib/usrp/x300/x300_radio_control.cpp1906
-rw-r--r--host/lib/usrp/x300/x300_radio_mbc_iface.hpp64
-rw-r--r--host/lib/usrp/x300/x300_regs.hpp3
18 files changed, 3665 insertions, 939 deletions
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
{