diff options
author | Martin Braun <martin.braun@ettus.com> | 2019-07-03 20:15:35 -0700 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2019-11-26 12:16:25 -0800 |
commit | c256b9df6502536c2e451e690f1ad5962c664d1a (patch) | |
tree | a83ad13e6f5978bbe14bb3ecf8294ba1e3d28db4 /host/lib/usrp/x300 | |
parent | 9a8435ed998fc5c65257f4c55768750b227ab19e (diff) | |
download | uhd-c256b9df6502536c2e451e690f1ad5962c664d1a.tar.gz uhd-c256b9df6502536c2e451e690f1ad5962c664d1a.tar.bz2 uhd-c256b9df6502536c2e451e690f1ad5962c664d1a.zip |
x300/mpmd: Port all RFNoC devices to the new RFNoC framework
Co-Authored-By: Alex Williams <alex.williams@ni.com>
Co-Authored-By: Sugandha Gupta <sugandha.gupta@ettus.com>
Co-Authored-By: Brent Stapleton <brent.stapleton@ettus.com>
Co-Authored-By: Ciro Nishiguchi <ciro.nishiguchi@ni.com>
Diffstat (limited to 'host/lib/usrp/x300')
-rw-r--r-- | host/lib/usrp/x300/CMakeLists.txt | 8 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_conn_mgr.hpp | 11 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_defaults.hpp | 2 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_eth_mgr.cpp | 370 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_eth_mgr.hpp | 34 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_fw_common.h | 2 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_impl.cpp | 524 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_impl.hpp | 130 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_io_impl.cpp | 59 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_mb_controller.cpp | 731 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_mb_controller.hpp | 153 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_mb_iface.cpp | 226 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_pcie_mgr.cpp | 233 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_pcie_mgr.hpp | 31 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_prop_tree.cpp | 117 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_radio_control.cpp | 1906 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_radio_mbc_iface.hpp | 64 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_regs.hpp | 3 |
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 { |