diff options
Diffstat (limited to 'host/lib/usrp')
23 files changed, 3109 insertions, 1468 deletions
diff --git a/host/lib/usrp/CMakeLists.txt b/host/lib/usrp/CMakeLists.txt index d20cc966b..f769417d9 100644 --- a/host/lib/usrp/CMakeLists.txt +++ b/host/lib/usrp/CMakeLists.txt @@ -52,6 +52,7 @@ ENDIF(ENABLE_GPSD) INCLUDE_SUBDIRECTORY(cores) INCLUDE_SUBDIRECTORY(dboard) INCLUDE_SUBDIRECTORY(common) +INCLUDE_SUBDIRECTORY(device3) INCLUDE_SUBDIRECTORY(usrp1) INCLUDE_SUBDIRECTORY(usrp2) INCLUDE_SUBDIRECTORY(b100) diff --git a/host/lib/usrp/cores/CMakeLists.txt b/host/lib/usrp/cores/CMakeLists.txt index 404fc6137..1e16dd39e 100644 --- a/host/lib/usrp/cores/CMakeLists.txt +++ b/host/lib/usrp/cores/CMakeLists.txt @@ -30,6 +30,7 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/rx_dsp_core_200.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tx_dsp_core_200.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rx_frontend_core_200.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rx_frontend_core_3000.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tx_frontend_core_200.cpp ${CMAKE_CURRENT_SOURCE_DIR}/user_settings_core_200.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rx_vita_core_3000.cpp diff --git a/host/lib/usrp/cores/dma_fifo_core_3000.cpp b/host/lib/usrp/cores/dma_fifo_core_3000.cpp index 1a9d5dd5c..5df28f7c2 100644 --- a/host/lib/usrp/cores/dma_fifo_core_3000.cpp +++ b/host/lib/usrp/cores/dma_fifo_core_3000.cpp @@ -240,6 +240,10 @@ public: flush(); } + virtual ~dma_fifo_core_3000_impl() + { + } + virtual void flush() { //Clear the FIFO and hold it in that state _fifo_ctrl_reg.write(fifo_ctrl_reg_t::CLEAR_FIFO, 1); diff --git a/host/lib/usrp/cores/rx_frontend_core_3000.cpp b/host/lib/usrp/cores/rx_frontend_core_3000.cpp new file mode 100644 index 000000000..23197cf5a --- /dev/null +++ b/host/lib/usrp/cores/rx_frontend_core_3000.cpp @@ -0,0 +1,186 @@ +// +// Copyright 2011-2012,2014-2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "rx_frontend_core_3000.hpp" +#include "dsp_core_utils.hpp" +#include <boost/math/special_functions/round.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/bind.hpp> +#include <uhd/types/dict.hpp> + +using namespace uhd; + +#define REG_RX_FE_MAG_CORRECTION (_base + 0 ) //18 bits +#define REG_RX_FE_PHASE_CORRECTION (_base + 4 ) //18 bits +#define REG_RX_FE_OFFSET_I (_base + 8 ) //18 bits +#define REG_RX_FE_OFFSET_Q (_base + 12) //18 bits +#define REG_RX_FE_MAPPING (_base + 16) +#define REG_RX_FE_HET_CORDIC_PHASE (_base + 20) + +#define FLAG_DSP_RX_MAPPING_SWAP_IQ (1 << 0) +#define FLAG_DSP_RX_MAPPING_REAL_MODE (1 << 1) +#define FLAG_DSP_RX_MAPPING_INVERT_Q (1 << 2) +#define FLAG_DSP_RX_MAPPING_INVERT_I (1 << 3) +#define FLAG_DSP_RX_MAPPING_REAL_DECIM (1 << 4) +//#define FLAG_DSP_RX_MAPPING_RESERVED (1 << 5) +//#define FLAG_DSP_RX_MAPPING_RESERVED (1 << 6) +#define FLAG_DSP_RX_MAPPING_BYPASS_ALL (1 << 7) + +#define OFFSET_FIXED (1ul << 31) +#define OFFSET_SET (1ul << 30) +#define FLAG_MASK (OFFSET_FIXED | OFFSET_SET) + +using namespace uhd::usrp; + +static boost::uint32_t fs_to_bits(const double num, const size_t bits){ + return boost::int32_t(boost::math::round(num * (1 << (bits-1)))); +} + +rx_frontend_core_3000::~rx_frontend_core_3000(void){ + /* NOP */ +} + +const std::complex<double> rx_frontend_core_3000::DEFAULT_DC_OFFSET_VALUE = std::complex<double>(0.0, 0.0); +const bool rx_frontend_core_3000::DEFAULT_DC_OFFSET_ENABLE = true; +const std::complex<double> rx_frontend_core_3000::DEFAULT_IQ_BALANCE_VALUE = std::complex<double>(0.0, 0.0); + +class rx_frontend_core_3000_impl : public rx_frontend_core_3000{ +public: + rx_frontend_core_3000_impl(wb_iface::sptr iface, const size_t base): + _i_dc_off(0), _q_dc_off(0), + _adc_rate(0.0), + _fe_conn(fe_connection_t("IQ")), + _iface(iface), _base(base) + { + //NOP + } + + void set_adc_rate(const double rate) { + _adc_rate = rate; + } + + void bypass_all(bool bypass_en) { + if (bypass_en) { + _iface->poke32(REG_RX_FE_MAPPING, FLAG_DSP_RX_MAPPING_BYPASS_ALL); + } else { + set_fe_connection(_fe_conn); + } + } + + void set_fe_connection(const fe_connection_t& fe_conn) { + boost::uint32_t mapping_reg_val = 0; + switch (fe_conn.get_sampling_mode()) { + case fe_connection_t::REAL: + case fe_connection_t::HETERODYNE: + mapping_reg_val = FLAG_DSP_RX_MAPPING_REAL_MODE|FLAG_DSP_RX_MAPPING_REAL_DECIM; + break; + default: + mapping_reg_val = 0; + break; + } + + if (fe_conn.is_iq_swapped()) mapping_reg_val |= FLAG_DSP_RX_MAPPING_SWAP_IQ; + if (fe_conn.is_i_inverted()) mapping_reg_val |= FLAG_DSP_RX_MAPPING_INVERT_I; + if (fe_conn.is_q_inverted()) mapping_reg_val |= FLAG_DSP_RX_MAPPING_INVERT_Q; + + _iface->poke32(REG_RX_FE_MAPPING, mapping_reg_val); + + UHD_ASSERT_THROW(_adc_rate!=0.0) + double cordic_freq = 0.0, actual_cordic_freq = 0.0; + if (fe_conn.get_sampling_mode() == fe_connection_t::HETERODYNE) { + //1. Remember the sign of the IF frequency. + // It will be discarded in the next step + int if_freq_sign = boost::math::sign(fe_conn.get_if_freq()); + //2. Map IF frequency to the range [0, _adc_rate) + double if_freq = std::abs(std::fmod(fe_conn.get_if_freq(), _adc_rate)); + //3. Map IF frequency to the range [-_adc_rate/2, _adc_rate/2) + // This is the aliased frequency + if (if_freq > (_adc_rate / 2.0)) { + if_freq -= _adc_rate; + } + //4. Set DSP offset to spin the signal in the opposite + // direction as the aliased frequency + cordic_freq = if_freq * (-if_freq_sign); + } + int32_t freq_word; + get_freq_and_freq_word(cordic_freq, _adc_rate, actual_cordic_freq, freq_word); + _iface->poke32(REG_RX_FE_HET_CORDIC_PHASE, boost::uint32_t(freq_word)); + + _fe_conn = fe_conn; + } + + void set_dc_offset_auto(const bool enb) { + _set_dc_offset(enb ? 0 : OFFSET_FIXED); + } + + std::complex<double> set_dc_offset(const std::complex<double> &off) { + static const double scaler = double(1ul << 29); + _i_dc_off = boost::math::iround(off.real()*scaler); + _q_dc_off = boost::math::iround(off.imag()*scaler); + + _set_dc_offset(OFFSET_SET | OFFSET_FIXED); + + return std::complex<double>(_i_dc_off/scaler, _q_dc_off/scaler); + } + + void _set_dc_offset(const boost::uint32_t flags) { + _iface->poke32(REG_RX_FE_OFFSET_I, flags | (_i_dc_off & ~FLAG_MASK)); + _iface->poke32(REG_RX_FE_OFFSET_Q, flags | (_q_dc_off & ~FLAG_MASK)); + } + + void set_iq_balance(const std::complex<double> &cor) { + _iface->poke32(REG_RX_FE_MAG_CORRECTION, fs_to_bits(cor.real(), 18)); + _iface->poke32(REG_RX_FE_PHASE_CORRECTION, fs_to_bits(cor.imag(), 18)); + } + + void populate_subtree(uhd::property_tree::sptr subtree) { + subtree->create<std::complex<double> >("dc_offset/value") + .set(DEFAULT_DC_OFFSET_VALUE) + .set_coercer(boost::bind(&rx_frontend_core_3000::set_dc_offset, this, _1)) + ; + subtree->create<bool>("dc_offset/enable") + .set(DEFAULT_DC_OFFSET_ENABLE) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_3000::set_dc_offset_auto, this, _1)) + ; + subtree->create<std::complex<double> >("iq_balance/value") + .set(DEFAULT_IQ_BALANCE_VALUE) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_3000::set_iq_balance, this, _1)) + ; + } + + double get_output_rate() { + switch (_fe_conn.get_sampling_mode()) { + case fe_connection_t::REAL: + case fe_connection_t::HETERODYNE: + return _adc_rate / 2; + default: + return _adc_rate; + } + return _adc_rate; + } + +private: + boost::int32_t _i_dc_off, _q_dc_off; + double _adc_rate; + fe_connection_t _fe_conn; + wb_iface::sptr _iface; + const size_t _base; +}; + +rx_frontend_core_3000::sptr rx_frontend_core_3000::make(wb_iface::sptr iface, const size_t base){ + return sptr(new rx_frontend_core_3000_impl(iface, base)); +} diff --git a/host/lib/usrp/cores/rx_frontend_core_3000.hpp b/host/lib/usrp/cores/rx_frontend_core_3000.hpp new file mode 100644 index 000000000..baa58331e --- /dev/null +++ b/host/lib/usrp/cores/rx_frontend_core_3000.hpp @@ -0,0 +1,69 @@ +// +// Copyright 2011,2014-2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_TX_FRONTEND_CORE_3000_HPP +#define INCLUDED_LIBUHD_USRP_TX_FRONTEND_CORE_3000_HPP + +#include <uhd/config.hpp> +#include <uhd/types/wb_iface.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/usrp/fe_connection.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include <complex> +#include <string> + +class rx_frontend_core_3000 : boost::noncopyable{ +public: + static const std::complex<double> DEFAULT_DC_OFFSET_VALUE; + static const bool DEFAULT_DC_OFFSET_ENABLE; + static const std::complex<double> DEFAULT_IQ_BALANCE_VALUE; + + typedef boost::shared_ptr<rx_frontend_core_3000> sptr; + + virtual ~rx_frontend_core_3000(void) = 0; + + static sptr make(uhd::wb_iface::sptr iface, const size_t base); + + /*! Set the input sampling rate (i.e. ADC rate) + */ + virtual void set_adc_rate(const double rate) = 0; + + virtual void bypass_all(bool bypass_en) = 0; + + virtual void set_fe_connection(const uhd::usrp::fe_connection_t& fe_conn) = 0; + + virtual void set_dc_offset_auto(const bool enb) = 0; + + virtual std::complex<double> set_dc_offset(const std::complex<double> &off) = 0; + + virtual void set_iq_balance(const std::complex<double> &cor) = 0; + + virtual void populate_subtree(uhd::property_tree::sptr subtree) = 0; + + /*! Return the sampling rate at the output + * + * In real mode, the frontend core will decimate the sampling rate by a + * factor of 2. + * + * \returns RX sampling rate + */ + virtual double get_output_rate(void) = 0; + +}; + +#endif /* INCLUDED_LIBUHD_USRP_TX_FRONTEND_CORE_3000_HPP */ diff --git a/host/lib/usrp/cores/rx_vita_core_3000.cpp b/host/lib/usrp/cores/rx_vita_core_3000.cpp index 52121837e..54c57c2d5 100644 --- a/host/lib/usrp/cores/rx_vita_core_3000.cpp +++ b/host/lib/usrp/cores/rx_vita_core_3000.cpp @@ -82,7 +82,9 @@ struct rx_vita_core_3000_impl : rx_vita_core_3000 void clear(void) { - this->configure_flow_control(0); //disable fc + // FC should never be disabled, this will actually become + // impossible in the future + //this->configure_flow_control(0); //disable fc } void set_nsamps_per_packet(const size_t nsamps) diff --git a/host/lib/usrp/device3/CMakeLists.txt b/host/lib/usrp/device3/CMakeLists.txt new file mode 100644 index 000000000..83f01a2e7 --- /dev/null +++ b/host/lib/usrp/device3/CMakeLists.txt @@ -0,0 +1,25 @@ +# +# Copyright 2014 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/device3_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/device3_io_impl.cpp +) diff --git a/host/lib/usrp/device3/device3_impl.cpp b/host/lib/usrp/device3/device3_impl.cpp new file mode 100644 index 000000000..7fcbc01b2 --- /dev/null +++ b/host/lib/usrp/device3/device3_impl.cpp @@ -0,0 +1,188 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "device3_impl.hpp" +#include "graph_impl.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/rfnoc/block_ctrl_base.hpp> +#include <boost/make_shared.hpp> +#include <algorithm> + +#define UHD_DEVICE3_LOG() UHD_LOGV(never) + +using namespace uhd::usrp; + +device3_impl::device3_impl() + : _sid_framer(0) +{ + _type = uhd::device::USRP; + _async_md.reset(new async_md_type(1000/*messages deep*/)); + _tree = uhd::property_tree::make(); +}; + +//! Returns true if the integer value stored in lhs is smaller than that in rhs +bool _compare_string_indexes(const std::string &lhs, const std::string &rhs) +{ + return boost::lexical_cast<size_t>(lhs) < boost::lexical_cast<size_t>(rhs); +} + +void device3_impl::merge_channel_defs( + const std::vector<uhd::rfnoc::block_id_t> &chan_ids, + const std::vector<uhd::device_addr_t> &chan_args, + const uhd::direction_t dir +) { + UHD_ASSERT_THROW(chan_ids.size() == chan_args.size()); + if (dir == uhd::DX_DIRECTION) { + merge_channel_defs(chan_ids, chan_args, RX_DIRECTION); + merge_channel_defs(chan_ids, chan_args, TX_DIRECTION); + return; + } + + uhd::fs_path chans_root = uhd::fs_path("/channels/") / (dir == RX_DIRECTION ? "rx" : "tx"); + // Store the new positions of the channels: + std::vector<size_t> chan_idxs; + + // 1. Get sorted list of currently defined channels + std::vector<std::string> curr_channels; + if (_tree->exists(chans_root)) { + curr_channels = _tree->list(chans_root); + std::sort(curr_channels.begin(), curr_channels.end(), _compare_string_indexes); + } + + // 2. Cycle through existing channels to find out where to merge + // the new channels. Rules are: + // - The order of chan_ids must be preserved + // - All block indices that are in chan_ids may be overwritten in the channel definition + // - If the channels in chan_ids are not yet in the property tree channel list, + // they are appended. + BOOST_FOREACH(const std::string &chan_idx, curr_channels) { + if (_tree->exists(chans_root / chan_idx)) { + rfnoc::block_id_t chan_block_id = _tree->access<rfnoc::block_id_t>(chans_root / chan_idx).get(); + if (std::find(chan_ids.begin(), chan_ids.end(), chan_block_id) != chan_ids.end()) { + chan_idxs.push_back(boost::lexical_cast<size_t>(chan_idx)); + } + } + } + size_t last_chan_idx = curr_channels.empty() ? 0 : (boost::lexical_cast<size_t>(curr_channels.back()) + 1); + while (chan_idxs.size() < chan_ids.size()) { + chan_idxs.push_back(last_chan_idx); + last_chan_idx++; + } + + // 3. Write the new channels + for (size_t i = 0; i < chan_ids.size(); i++) { + if (not _tree->exists(chans_root / chan_idxs[i])) { + _tree->create<rfnoc::block_id_t>(chans_root / chan_idxs[i]); + } + _tree->access<rfnoc::block_id_t>(chans_root / chan_idxs[i]).set(chan_ids[i]); + if (not _tree->exists(chans_root / chan_idxs[i] / "args")) { + _tree->create<uhd::device_addr_t>(chans_root / chan_idxs[i] / "args"); + } + _tree->access<uhd::device_addr_t>(chans_root / chan_idxs[i] / "args").set(chan_args[i]); + } +} + +/*********************************************************************** + * RFNoC-Specific + **********************************************************************/ +void device3_impl::enumerate_rfnoc_blocks( + size_t device_index, + size_t n_blocks, + size_t base_port, + const uhd::sid_t &base_sid, + uhd::device_addr_t transport_args, + uhd::endianness_t endianness +) { + // entries that are already connected to this block + uhd::sid_t ctrl_sid = base_sid; + uhd::property_tree::sptr subtree = _tree->subtree(uhd::fs_path("/mboards") / device_index); + // 1) Clean property tree entries + // TODO put this back once radios are actual rfnoc blocks!!!!!! + //if (subtree->exists("xbar")) { + //subtree->remove("xbar"); + //} + // 2) Destroy existing block controllers + // TODO: Clear out all the old block control classes + // 3) Create new block controllers + for (size_t i = 0; i < n_blocks; i++) { + UHD_DEVICE3_LOG() << "[RFNOC] ------- Block Setup -----------" << std::endl; + // First, make a transport for port number zero, because we always need that: + ctrl_sid.set_dst_xbarport(base_port + i); + ctrl_sid.set_dst_blockport(0); + both_xports_t xport = this->make_transport( + ctrl_sid, + CTRL, + transport_args + ); + UHD_DEVICE3_LOG() << str(boost::format("Setting up NoC-Shell Control for port #0 (SID: %s)...") % xport.send_sid.to_pp_string_hex()); + uhd::rfnoc::ctrl_iface::sptr ctrl = uhd::rfnoc::ctrl_iface::make( + endianness == ENDIANNESS_BIG, + xport.send, + xport.recv, + xport.send_sid, + str(boost::format("CE_%02d_Port_%02X") % i % ctrl_sid.get_dst_endpoint()) + ); + UHD_DEVICE3_LOG() << "OK" << std::endl; + uint64_t noc_id = ctrl->peek64(uhd::rfnoc::SR_READBACK_REG_ID); + UHD_DEVICE3_LOG() << str(boost::format("Port %d: Found NoC-Block with ID %016X.") % int(ctrl_sid.get_dst_endpoint()) % noc_id) << std::endl; + uhd::rfnoc::make_args_t make_args; + uhd::rfnoc::blockdef::sptr block_def = uhd::rfnoc::blockdef::make_from_noc_id(noc_id); + if (not block_def) { + UHD_DEVICE3_LOG() << "Using default block configuration." << std::endl; + block_def = uhd::rfnoc::blockdef::make_from_noc_id(uhd::rfnoc::DEFAULT_NOC_ID); + } + UHD_ASSERT_THROW(block_def); + make_args.ctrl_ifaces[0] = ctrl; + BOOST_FOREACH(const size_t port_number, block_def->get_all_port_numbers()) { + if (port_number == 0) { // We've already set this up + continue; + } + ctrl_sid.set_dst_blockport(port_number); + both_xports_t xport1 = this->make_transport( + ctrl_sid, + CTRL, + transport_args + ); + UHD_DEVICE3_LOG() << str(boost::format("Setting up NoC-Shell Control for port #%d (SID: %s)...") % port_number % xport1.send_sid.to_pp_string_hex()); + uhd::rfnoc::ctrl_iface::sptr ctrl1 = uhd::rfnoc::ctrl_iface::make( + endianness == ENDIANNESS_BIG, + xport1.send, + xport1.recv, + xport1.send_sid, + str(boost::format("CE_%02d_Port_%02d") % i % ctrl_sid.get_dst_endpoint()) + ); + UHD_DEVICE3_LOG() << "OK" << std::endl; + make_args.ctrl_ifaces[port_number] = ctrl1; + } + + make_args.base_address = xport.send_sid.get_dst(); + make_args.device_index = device_index; + make_args.tree = subtree; + make_args.is_big_endian = (endianness == ENDIANNESS_BIG); + _rfnoc_block_ctrl.push_back(uhd::rfnoc::block_ctrl_base::make(make_args, noc_id)); + } +} + + +uhd::rfnoc::graph::sptr device3_impl::create_graph(const std::string &name) +{ + return boost::make_shared<uhd::rfnoc::graph_impl>( + name, + shared_from_this() + ); +} + diff --git a/host/lib/usrp/device3/device3_impl.hpp b/host/lib/usrp/device3/device3_impl.hpp new file mode 100644 index 000000000..0d94ae21c --- /dev/null +++ b/host/lib/usrp/device3/device3_impl.hpp @@ -0,0 +1,210 @@ +// +// Copyright 2014-2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +// Declares the device3_impl class which is a layer between device3 and +// the different 3-rd gen device impls (e.g. x300_impl) + +#ifndef INCLUDED_DEVICE3_IMPL_HPP +#define INCLUDED_DEVICE3_IMPL_HPP + +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/transport/chdr.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <uhd/types/sid.hpp> +#include <uhd/types/metadata.hpp> +#include <uhd/types/endianness.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/utils/tasks.hpp> +#include <uhd/device3.hpp> +#include "xports.hpp" +// Common FPGA cores: +#include "ctrl_iface.hpp" +#include "rx_dsp_core_3000.hpp" +#include "tx_dsp_core_3000.hpp" +#include "rx_vita_core_3000.hpp" +#include "tx_vita_core_3000.hpp" +#include "rx_frontend_core_200.hpp" +#include "tx_frontend_core_200.hpp" +#include "time_core_3000.hpp" +#include "gpio_atr_3000.hpp" +// RFNoC-specific includes: +#include "radio_ctrl_impl.hpp" + +namespace uhd { namespace usrp { + +/*********************************************************************** + * Default settings (any device3 may override these) + **********************************************************************/ +static const size_t DEVICE3_RX_FC_REQUEST_FREQ = 32; //per flow-control window +static const size_t DEVICE3_TX_FC_RESPONSE_FREQ = 8; +static const size_t DEVICE3_TX_FC_RESPONSE_CYCLES = 0; // Cycles: Off. + +static const size_t DEVICE3_TX_MAX_HDR_LEN = uhd::transport::vrt::chdr::max_if_hdr_words64 * sizeof(boost::uint64_t); // Bytes +static const size_t DEVICE3_RX_MAX_HDR_LEN = uhd::transport::vrt::chdr::max_if_hdr_words64 * sizeof(boost::uint64_t); // Bytes + +class device3_impl : public uhd::device3, public boost::enable_shared_from_this<device3_impl> +{ +public: + /*********************************************************************** + * device3-specific Types + **********************************************************************/ + typedef uhd::transport::bounded_buffer<uhd::async_metadata_t> async_md_type; + + //! The purpose of a transport + enum xport_type_t { + CTRL = 0, + TX_DATA, + RX_DATA + }; + + enum xport_t {AXI, ETH, PCIE}; + + //! Stores all streaming-related options + struct stream_options_t + { + //! Max size of the header in bytes for TX + size_t tx_max_len_hdr; + //! Max size of the header in bytes for RX + size_t rx_max_len_hdr; + //! How often we send ACKs to the upstream block per one full FC window + size_t rx_fc_request_freq; + //! How often the downstream block should send ACKs per one full FC window + size_t tx_fc_response_freq; + //! How often the downstream block should send ACKs in cycles + size_t tx_fc_response_cycles; + stream_options_t(void) + : tx_max_len_hdr(DEVICE3_TX_MAX_HDR_LEN) + , rx_max_len_hdr(DEVICE3_RX_MAX_HDR_LEN) + , rx_fc_request_freq(DEVICE3_RX_FC_REQUEST_FREQ) + , tx_fc_response_freq(DEVICE3_TX_FC_RESPONSE_FREQ) + , tx_fc_response_cycles(DEVICE3_TX_FC_RESPONSE_CYCLES) + {}; + }; + + /*********************************************************************** + * I/O Interface + **********************************************************************/ + uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &); + uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &); + bool recv_async_msg(uhd::async_metadata_t &async_metadata, double timeout); + + /*********************************************************************** + * Other public APIs + **********************************************************************/ + rfnoc::graph::sptr create_graph(const std::string &name=""); + +protected: + /*********************************************************************** + * Structors + **********************************************************************/ + device3_impl(); + virtual ~device3_impl() {}; + + /*********************************************************************** + * Streaming-related + **********************************************************************/ + // The 'rate' argument is so we can use these as subscribers to rate changes +public: // TODO make these protected again + void update_rx_streamers(double rate=-1.0); + void update_tx_streamers(double rate=-1.0); +protected: + + /*********************************************************************** + * Transport-related + **********************************************************************/ + stream_options_t stream_options; + + /*! \brief Create a transport to a given endpoint. + * + * \param address The endpoint address of the block we're creating a transport to. + * The source address in this value is not considered, only the + * destination address. + * \param xport_type Specify which kind of transport this is. + * \param args Additional arguments for the transport generation. See \ref page_transport + * for valid arguments. + */ + virtual uhd::both_xports_t make_transport( + const uhd::sid_t &address, + const xport_type_t xport_type, + const uhd::device_addr_t& args + ) = 0; + + virtual uhd::device_addr_t get_tx_hints(size_t) { return uhd::device_addr_t(); }; + virtual uhd::device_addr_t get_rx_hints(size_t) { return uhd::device_addr_t(); }; + virtual uhd::endianness_t get_transport_endianness(size_t mb_index) = 0; + + //! Is called after a streamer is generated + virtual void post_streamer_hooks(uhd::direction_t) {}; + + /*********************************************************************** + * Channel-related + **********************************************************************/ + /*! Merge a list of channels into the existing channel definition. + * + * Intelligently merge the channels described in \p chan_ids + * into the current channel definition. If none of the channels in + * \p chan_ids is in the current definition, they simply get appended. + * Otherwise, they get overwritten in the order of \p chan_ids. + * + * \param chan_ids List of block IDs for the channels. + * \param chan_args New channel args. Must have same length as chan_ids. + * + */ + void merge_channel_defs( + const std::vector<rfnoc::block_id_t> &chan_ids, + const std::vector<uhd::device_addr_t> &chan_args, + const uhd::direction_t dir + ); + + /*********************************************************************** + * RFNoC-Specific + **********************************************************************/ + void enumerate_rfnoc_blocks( + size_t device_index, + size_t n_blocks, + size_t base_port, + const uhd::sid_t &base_sid, + uhd::device_addr_t transport_args, + uhd::endianness_t endianness + ); + + /*********************************************************************** + * Members + **********************************************************************/ + //! A counter, designed to create unique SIDs + size_t _sid_framer; + + // TODO: Maybe move these to private + uhd::dict<std::string, boost::weak_ptr<uhd::rx_streamer> > _rx_streamers; + uhd::dict<std::string, boost::weak_ptr<uhd::tx_streamer> > _tx_streamers; + +private: + /*********************************************************************** + * Private Members + **********************************************************************/ + //! Buffer for async metadata + boost::shared_ptr<async_md_type> _async_md; + + //! This mutex locks the get_xx_stream() functions. + boost::mutex _transport_setup_mutex; +}; + +}} /* namespace uhd::usrp */ + +#endif /* INCLUDED_DEVICE3_IMPL_HPP */ +// vim: sw=4 expandtab: diff --git a/host/lib/usrp/device3/device3_io_impl.cpp b/host/lib/usrp/device3/device3_io_impl.cpp new file mode 100644 index 000000000..8c61f8f15 --- /dev/null +++ b/host/lib/usrp/device3/device3_io_impl.cpp @@ -0,0 +1,851 @@ +// +// Copyright 2014-2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +// Provides streaming-related functions which are used by device3 objects. + +#define DEVICE3_STREAMER // For the super_*_packet_handlers + +#include "device3_impl.hpp" +#include <uhd/rfnoc/constants.hpp> +#include <uhd/rfnoc/source_block_ctrl_base.hpp> +#include <uhd/rfnoc/sink_block_ctrl_base.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include "../common/async_packet_handler.hpp" +#include "../../transport/super_recv_packet_handler.hpp" +#include "../../transport/super_send_packet_handler.hpp" +#include "../../rfnoc/rx_stream_terminator.hpp" +#include "../../rfnoc/tx_stream_terminator.hpp" +#include <uhd/rfnoc/rate_node_ctrl.hpp> +#include <uhd/rfnoc/radio_ctrl.hpp> + +#define UHD_STREAMER_LOG() UHD_LOGV(never) + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +//! CVITA uses 12-Bit sequence numbers +static const boost::uint32_t HW_SEQ_NUM_MASK = 0xfff; + + +/*********************************************************************** + * Helper functions for get_?x_stream() + **********************************************************************/ +static uhd::stream_args_t sanitize_stream_args(const uhd::stream_args_t &args_) +{ + uhd::stream_args_t args = args_; + if (args.channels.empty()) { + args.channels = std::vector<size_t>(1, 0); + } + + return args; +} + +static void check_stream_sig_compatible(const rfnoc::stream_sig_t &stream_sig, stream_args_t &args, const std::string &tx_rx) +{ + if (args.otw_format.empty()) { + if (stream_sig.item_type.empty()) { + throw uhd::runtime_error(str( + boost::format("[%s Streamer] No otw_format defined!") % tx_rx + )); + } else { + args.otw_format = stream_sig.item_type; + } + } else if (not stream_sig.item_type.empty() and stream_sig.item_type != args.otw_format) { + throw uhd::runtime_error(str( + boost::format("[%s Streamer] Conflicting OTW types defined: args.otw_format = '%s' <=> stream_sig.item_type = '%s'") + % tx_rx % args.otw_format % stream_sig.item_type + )); + } + const size_t bpi = convert::get_bytes_per_item(args.otw_format); // bytes per item + if (stream_sig.packet_size) { + if (args.args.has_key("spp")) { + size_t args_spp = args.args.cast<size_t>("spp", 0); + if (args_spp * bpi != stream_sig.packet_size) { + throw uhd::runtime_error(str( + boost::format("[%s Streamer] Conflicting packet sizes defined: args yields %d bytes but stream_sig.packet_size is %d bytes") + % tx_rx % (args_spp * bpi) % stream_sig.packet_size + )); + } + } else { + args.args["spp"] = str(boost::format("%d") % (stream_sig.packet_size / bpi)); + } + } +} + +/*! \brief Returns a list of rx or tx channels for a streamer. + * + * If the given stream args contain instructions to set up channels, + * those are used. Otherwise, the current device's channel definition + * is consulted. + * + * \param args_ Stream args. + * \param[out] chan_list The list of channels in the correct order. + * \param[out] chan_args Channel args for every channel. `chan_args.size() == chan_list.size()` + */ +void generate_channel_list( + const uhd::stream_args_t &args_, + std::vector<uhd::rfnoc::block_id_t> &chan_list, + std::vector<device_addr_t> &chan_args +) { + uhd::stream_args_t args = args_; + BOOST_FOREACH(const size_t chan_idx, args.channels) { + //// Find block ID for this channel: + if (args.args.has_key(str(boost::format("block_id%d") % chan_idx))) { + chan_list.push_back( + uhd::rfnoc::block_id_t( + args.args.pop(str(boost::format("block_id%d") % chan_idx)) + ) + ); + chan_args.push_back(args.args); + } else if (args.args.has_key("block_id")) { + chan_list.push_back(args.args.get("block_id")); + chan_args.push_back(args.args); + chan_args.back().pop("block_id"); + } else { + throw uhd::runtime_error(str( + boost::format("Cannot create streamers: No block_id specified for channel %d.") + % chan_idx + )); + } + //// Find block port for this channel + if (args.args.has_key(str(boost::format("block_port%d") % chan_idx))) { + chan_args.back()["block_port"] = args.args.pop(str(boost::format("block_port%d") % chan_idx)); + } else if (args.args.has_key("block_port")) { + // We have to write it again, because the chan args from the + // property tree might have overwritten this + chan_args.back()["block_port"] = args.args.get("block_port"); + } + } +} + + +/*********************************************************************** + * RX Flow Control Functions + **********************************************************************/ +//! Stores the state of RX flow control +struct rx_fc_cache_t +{ + rx_fc_cache_t(): + last_seq_in(0){} + size_t last_seq_in; +}; + +/*! Determine the size of the flow control window in number of packets. + * + * This value depends on three things: + * - The packet size (in bytes), P + * - The size of the software buffer (in bytes), B + * - The desired buffer fullness, F + * + * The FC window size is thus X = floor(B*F/P). + * + * \param pkt_size The maximum packet size in bytes + * \param sw_buff_size Software buffer size in bytes + * \param rx_args If this has a key 'recv_buff_fullness', this value will + * be used for said fullness. Must be between 0.01 and 1. + * + * \returns The size of the flow control window in number of packets + */ +static size_t get_rx_flow_control_window( + size_t pkt_size, + size_t sw_buff_size, + const device_addr_t& rx_args +) { + double fullness_factor = rx_args.cast<double>( + "recv_buff_fullness", + uhd::rfnoc::DEFAULT_FC_RX_SW_BUFF_FULL_FACTOR + ); + + if (fullness_factor < 0.01 || fullness_factor > 1) { + throw uhd::value_error("recv_buff_fullness must be in [0.01, 1] inclusive (1% to 100%)"); + } + + size_t window_in_pkts = (static_cast<size_t>(sw_buff_size * fullness_factor) / pkt_size); + if (rx_args.has_key("max_recv_window")) { + window_in_pkts = std::min( + window_in_pkts, + rx_args.cast<size_t>("max_recv_window", window_in_pkts) + ); + } + if (window_in_pkts == 0) { + throw uhd::value_error("recv_buff_size must be larger than the recv_frame_size."); + } + UHD_ASSERT_THROW(size_t(sw_buff_size * fullness_factor) >= pkt_size * window_in_pkts); + return window_in_pkts; +} + + +/*! Send out RX flow control packets. + * + * For an rx stream, this function takes care of sending back + * a flow control packet to the source telling it which + * packets have been consumed. + * + * This function should only be called by the function handling + * the rx stream, usually recv() in super_recv_packet_handler. + * + * \param sid The SID that goes into this packet. This is the reversed() + * version of the data stream's SID. + * \param xport A transport object over which to send the data + * \param big_endian Endianness of the transport + * \param seq32_state Pointer to a variable that saves the 32-Bit state + * of the sequence numbers, since we only have 12 Bit + * sequence numbers in CHDR. + * \param last_seq The value to send: The last consumed packet's sequence number. + */ +static void handle_rx_flowctrl( + const sid_t &sid, + zero_copy_if::sptr xport, + endianness_t endianness, + boost::shared_ptr<rx_fc_cache_t> fc_cache, + const size_t last_seq +) { + static const size_t RXFC_PACKET_LEN_IN_WORDS = 2; + static const size_t RXFC_CMD_CODE_OFFSET = 0; + static const size_t RXFC_SEQ_NUM_OFFSET = 1; + + managed_send_buffer::sptr buff = xport->get_send_buff(0.0); + if (not buff) { + throw uhd::runtime_error("handle_rx_flowctrl timed out getting a send buffer"); + } + boost::uint32_t *pkt = buff->cast<boost::uint32_t *>(); + + // Recover sequence number. The sequence numbers handled by the streamers + // are 12 Bits, but we want to know the 32-Bit sequence number. + size_t &seq32 = fc_cache->last_seq_in; + const size_t seq12 = seq32 & HW_SEQ_NUM_MASK; + if (last_seq < seq12) + seq32 += (HW_SEQ_NUM_MASK + 1); + seq32 &= ~HW_SEQ_NUM_MASK; + seq32 |= last_seq; + + // Super-verbose mode: + //static size_t fc_pkt_count = 0; + //UHD_MSG(status) << "sending flow ctrl packet " << fc_pkt_count++ << ", acking " << str(boost::format("%04d\tseq_sw==0x%08x") % last_seq % seq32) << std::endl; + + //load packet info + vrt::if_packet_info_t packet_info; + packet_info.packet_type = vrt::if_packet_info_t::PACKET_TYPE_FC; + packet_info.num_payload_words32 = RXFC_PACKET_LEN_IN_WORDS; + packet_info.num_payload_bytes = packet_info.num_payload_words32*sizeof(boost::uint32_t); + packet_info.packet_count = seq32; + packet_info.sob = false; + packet_info.eob = false; + packet_info.sid = sid.get(); + packet_info.has_sid = true; + packet_info.has_cid = false; + packet_info.has_tsi = false; + packet_info.has_tsf = false; + packet_info.has_tlr = false; + + if (endianness == ENDIANNESS_BIG) { + // Load Header: + vrt::chdr::if_hdr_pack_be(pkt, packet_info); + // Load Payload: (the sequence number) + pkt[packet_info.num_header_words32+RXFC_CMD_CODE_OFFSET] = uhd::htonx<boost::uint32_t>(0); + pkt[packet_info.num_header_words32+RXFC_SEQ_NUM_OFFSET] = uhd::htonx<boost::uint32_t>(seq32); + } else { + // Load Header: + vrt::chdr::if_hdr_pack_le(pkt, packet_info); + // Load Payload: (the sequence number) + pkt[packet_info.num_header_words32+RXFC_CMD_CODE_OFFSET] = uhd::htowx<boost::uint32_t>(0); + pkt[packet_info.num_header_words32+RXFC_SEQ_NUM_OFFSET] = uhd::htowx<boost::uint32_t>(seq32); + } + + //std::cout << " SID=" << std::hex << sid << " hdr bits=" << packet_info.packet_type << " seq32=" << seq32 << std::endl; + //std::cout << "num_packet_words32: " << packet_info.num_packet_words32 << std::endl; + //for (size_t i = 0; i < packet_info.num_packet_words32; i++) { + //std::cout << str(boost::format("0x%08x") % pkt[i]) << " "; + //if (i % 2) { + //std::cout << std::endl; + //} + //} + + //send the buffer over the interface + buff->commit(sizeof(boost::uint32_t)*(packet_info.num_packet_words32)); +} + +/*********************************************************************** + * TX Flow Control Functions + **********************************************************************/ +//! Stores the state of TX flow control +struct tx_fc_cache_t +{ + tx_fc_cache_t(void): + stream_channel(0), + device_channel(0), + last_seq_out(0), + last_seq_ack(0), + seq_queue(1){} + size_t stream_channel; + size_t device_channel; + size_t last_seq_out; + size_t last_seq_ack; + uhd::transport::bounded_buffer<size_t> seq_queue; + boost::shared_ptr<device3_impl::async_md_type> async_queue; + boost::shared_ptr<device3_impl::async_md_type> old_async_queue; +}; + +/*! Return the size of the flow control window in packets. + * + * If the return value of this function is F, the last tx'd packet + * has index N and the last ack'd packet has index M, the amount of + * FC credit we have is C = F + M - N (i.e. we can send C more packets + * before getting another ack). + * + * Note: If `send_buff_size` is set in \p tx_hints, this will + * override hw_buff_size_. + */ +static size_t get_tx_flow_control_window( + size_t pkt_size, + const double hw_buff_size_, + const device_addr_t& tx_hints +) { + double hw_buff_size = tx_hints.cast<double>("send_buff_size", hw_buff_size_); + size_t window_in_pkts = (static_cast<size_t>(hw_buff_size) / pkt_size); + if (window_in_pkts == 0) { + throw uhd::value_error("send_buff_size must be larger than the send_frame_size."); + } + return window_in_pkts; +} + +static managed_send_buffer::sptr get_tx_buff_with_flowctrl( + task::sptr /*holds ref*/, + boost::shared_ptr<tx_fc_cache_t> fc_cache, + zero_copy_if::sptr xport, + size_t fc_window, + const double timeout +){ + while (true) + { + // delta is the amount of FC credit we've used up + const size_t delta = (fc_cache->last_seq_out & HW_SEQ_NUM_MASK) - (fc_cache->last_seq_ack & HW_SEQ_NUM_MASK); + // If we want to send another packet, we must have FC credit left + if ((delta & HW_SEQ_NUM_MASK) < fc_window) + break; + + // If credit is all used up, we check seq_queue for more. + const bool ok = fc_cache->seq_queue.pop_with_timed_wait(fc_cache->last_seq_ack, timeout); + if (not ok) { + return managed_send_buffer::sptr(); //timeout waiting for flow control + } + } + + managed_send_buffer::sptr buff = xport->get_send_buff(timeout); + if (buff) { + fc_cache->last_seq_out++; //update seq, this will actually be a send + } + return buff; +} + +#define DEVICE3_ASYNC_EVENT_CODE_FLOW_CTRL 0 +/*! Handle incoming messages. If they're flow control, update the TX FC cache. + * Otherwise, send them to the async message queue for the user to poll. + * + * This is run inside a uhd::task as long as this streamer lives. + */ +static void handle_tx_async_msgs( + boost::shared_ptr<tx_fc_cache_t> fc_cache, + zero_copy_if::sptr xport, + endianness_t endianness, + boost::function<double(void)> get_tick_rate +) { + managed_recv_buffer::sptr buff = xport->get_recv_buff(); + if (not buff) + return; + + //extract packet info + vrt::if_packet_info_t if_packet_info; + if_packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); + const boost::uint32_t *packet_buff = buff->cast<const boost::uint32_t *>(); + + //unpacking can fail + boost::uint32_t (*endian_conv)(boost::uint32_t) = uhd::ntohx; + try + { + if (endianness == ENDIANNESS_BIG) + { + vrt::chdr::if_hdr_unpack_be(packet_buff, if_packet_info); + endian_conv = uhd::ntohx; + } + else + { + vrt::chdr::if_hdr_unpack_le(packet_buff, if_packet_info); + endian_conv = uhd::wtohx; + } + } + catch(const std::exception &ex) + { + UHD_MSG(error) << "Error parsing async message packet: " << ex.what() << std::endl; + return; + } + + double tick_rate = get_tick_rate(); + if (tick_rate == rfnoc::tick_node_ctrl::RATE_UNDEFINED) { + tick_rate = 1; + } + + //fill in the async metadata + async_metadata_t metadata; + load_metadata_from_buff( + endian_conv, + metadata, + if_packet_info, + packet_buff, + tick_rate, + fc_cache->stream_channel + ); + + // TODO: Shouldn't we be polling if_packet_info.packet_type == PACKET_TYPE_FC? + // Thing is, on X300, packet_type == 0, so that wouldn't work. But it seems it should. + //The FC response and the burst ack are two indicators that the radio + //consumed packets. Use them to update the FC metadata + if (metadata.event_code == DEVICE3_ASYNC_EVENT_CODE_FLOW_CTRL) { + const size_t seq = metadata.user_payload[0]; + fc_cache->seq_queue.push_with_pop_on_full(seq); + } + + //FC responses don't propagate up to the user so filter them here + if (metadata.event_code != DEVICE3_ASYNC_EVENT_CODE_FLOW_CTRL) { + fc_cache->async_queue->push_with_pop_on_full(metadata); + metadata.channel = fc_cache->device_channel; + fc_cache->old_async_queue->push_with_pop_on_full(metadata); + standard_async_msg_prints(metadata); + } +} + + + +/*********************************************************************** + * Async Data + **********************************************************************/ +bool device3_impl::recv_async_msg( + async_metadata_t &async_metadata, double timeout +) +{ + return _async_md->pop_with_timed_wait(async_metadata, timeout); +} + +/*********************************************************************** + * Receive streamer + **********************************************************************/ +void device3_impl::update_rx_streamers(double /* rate */) +{ + BOOST_FOREACH(const std::string &block_id, _rx_streamers.keys()) { + UHD_STREAMER_LOG() << "[Device3] updating RX streamer to " << block_id << std::endl; + boost::shared_ptr<sph::recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_rx_streamers[block_id].lock()); + if (my_streamer) { + double tick_rate = my_streamer->get_terminator()->get_tick_rate(); + if (tick_rate == rfnoc::tick_node_ctrl::RATE_UNDEFINED) { + tick_rate = 1.0; + } + my_streamer->set_tick_rate(tick_rate); + double samp_rate = my_streamer->get_terminator()->get_output_samp_rate(); + if (samp_rate == rfnoc::rate_node_ctrl::RATE_UNDEFINED) { + samp_rate = 1.0; + } + // This formula is not derived by any scientific means -- we just need to + // increase the failure threshold as we increase rates. For 1 Msps, we use + // the default. + const size_t alignment_failure_factor = std::max(size_t(1), size_t(samp_rate * 1000 / tick_rate)); + double scaling = my_streamer->get_terminator()->get_output_scale_factor(); + if (scaling == rfnoc::scalar_node_ctrl::SCALE_UNDEFINED) { + scaling = 1/32767.; + } + UHD_STREAMER_LOG() << " New tick_rate == " << tick_rate << " New samp_rate == " << samp_rate << " New scaling == " << scaling << std::endl; + + my_streamer->set_tick_rate(tick_rate); + my_streamer->set_samp_rate(samp_rate); + // 1000 packets is the default alignment failure threshold + my_streamer->set_alignment_failure_threshold(1000 * alignment_failure_factor); + my_streamer->set_scale_factor(scaling); + } + } +} + +rx_streamer::sptr device3_impl::get_rx_stream(const stream_args_t &args_) +{ + boost::mutex::scoped_lock lock(_transport_setup_mutex); + stream_args_t args = sanitize_stream_args(args_); + + // I. Generate the channel list + std::vector<uhd::rfnoc::block_id_t> chan_list; + std::vector<device_addr_t> chan_args; + generate_channel_list(args, chan_list, chan_args); + // Note: All 'args.args' are merged into chan_args now. + + // II. Iterate over all channels + boost::shared_ptr<sph::recv_packet_streamer> my_streamer; + // The terminator's lifetime is coupled to the streamer. + // There is only one terminator. If the streamer has multiple channels, + // it will be connected to each upstream block. + rfnoc::rx_stream_terminator::sptr recv_terminator = rfnoc::rx_stream_terminator::make(); + for (size_t stream_i = 0; stream_i < chan_list.size(); stream_i++) { + // Get block ID and mb index + uhd::rfnoc::block_id_t block_id = chan_list[stream_i]; + UHD_STREAMER_LOG() << "[RX Streamer] chan " << stream_i << " connecting to " << block_id << std::endl; + // Update args so args.args is always valid for this particular channel: + args.args = chan_args[stream_i]; + size_t mb_index = block_id.get_device_no(); + size_t suggested_block_port = args.args.cast<size_t>("block_port", rfnoc::ANY_PORT); + + // Access to this channel's block control + uhd::rfnoc::source_block_ctrl_base::sptr blk_ctrl = + boost::dynamic_pointer_cast<uhd::rfnoc::source_block_ctrl_base>(get_block_ctrl(block_id)); + + // Connect the terminator with this channel's block. + size_t block_port = blk_ctrl->connect_downstream( + recv_terminator, + suggested_block_port, + args.args + ); + const size_t terminator_port = recv_terminator->connect_upstream(blk_ctrl); + blk_ctrl->set_downstream_port(block_port, terminator_port); + recv_terminator->set_upstream_port(terminator_port, block_port); + + // Check if the block connection is compatible (spp and item type) + check_stream_sig_compatible(blk_ctrl->get_output_signature(block_port), args, "RX"); + + // Setup the DSP transport hints + device_addr_t rx_hints = get_rx_hints(mb_index); + + //allocate sid and create transport + uhd::sid_t stream_address = blk_ctrl->get_address(block_port); + UHD_STREAMER_LOG() << "[RX Streamer] creating rx stream " << rx_hints.to_string() << std::endl; + both_xports_t xport = make_transport(stream_address, RX_DATA, rx_hints); + UHD_STREAMER_LOG() << std::hex << "[RX Streamer] data_sid = " << xport.send_sid << std::dec << " actual recv_buff_size = " << xport.recv_buff_size << std::endl; + + // Configure the block + blk_ctrl->set_destination(xport.send_sid.get_src(), block_port); + + blk_ctrl->sr_write(uhd::rfnoc::SR_RESP_OUT_DST_SID, xport.send_sid.get_src(), block_port); + UHD_STREAMER_LOG() << "[RX Streamer] resp_out_dst_sid == " << xport.send_sid.get_src() << std::endl; + + // Find all upstream radio nodes and set their response in SID to the host + std::vector<boost::shared_ptr<uhd::rfnoc::radio_ctrl> > upstream_radio_nodes = blk_ctrl->find_upstream_node<uhd::rfnoc::radio_ctrl>(); + UHD_STREAMER_LOG() << "[RX Streamer] Number of upstream radio nodes: " << upstream_radio_nodes.size() << std::endl; + BOOST_FOREACH(const boost::shared_ptr<uhd::rfnoc::radio_ctrl> &node, upstream_radio_nodes) { + node->sr_write(uhd::rfnoc::SR_RESP_OUT_DST_SID, xport.send_sid.get_src(), block_port); + } + + // To calculate the max number of samples per packet, we assume the maximum header length + // to avoid fragmentation should the entire header be used. + const size_t bpp = xport.recv->get_recv_frame_size() - stream_options.rx_max_len_hdr; // bytes per packet + const size_t bpi = convert::get_bytes_per_item(args.otw_format); // bytes per item + const size_t spp = std::min(args.args.cast<size_t>("spp", bpp/bpi), bpp/bpi); // samples per packet + UHD_STREAMER_LOG() << "[RX Streamer] spp == " << spp << std::endl; + + //make the new streamer given the samples per packet + if (not my_streamer) + my_streamer = boost::make_shared<sph::recv_packet_streamer>(spp); + my_streamer->resize(chan_list.size()); + + //init some streamer stuff + std::string conv_endianness; + if (get_transport_endianness(mb_index) == ENDIANNESS_BIG) { + my_streamer->set_vrt_unpacker(&vrt::chdr::if_hdr_unpack_be); + conv_endianness = "be"; + } else { + my_streamer->set_vrt_unpacker(&vrt::chdr::if_hdr_unpack_le); + conv_endianness = "le"; + } + + //set the converter + uhd::convert::id_type id; + id.input_format = args.otw_format + "_item32_" + conv_endianness; + id.num_inputs = 1; + id.output_format = args.cpu_format; + id.num_outputs = 1; + my_streamer->set_converter(id); + + //flow control setup + const size_t pkt_size = spp * bpi + stream_options.rx_max_len_hdr; + const size_t fc_window = get_rx_flow_control_window(pkt_size, xport.recv_buff_size, rx_hints); + const size_t fc_handle_window = std::max<size_t>(1, fc_window / stream_options.rx_fc_request_freq); + UHD_STREAMER_LOG()<< "[RX Streamer] Flow Control Window (minus one) = " << fc_window-1 << ", Flow Control Handler Window = " << fc_handle_window << std::endl; + blk_ctrl->configure_flow_control_out( + fc_window-1, // Leave one space for overrun packets TODO make this obsolete + block_port + ); + + //Give the streamer a functor to get the recv_buffer + //bind requires a zero_copy_if::sptr to add a streamer->xport lifetime dependency + my_streamer->set_xport_chan_get_buff( + stream_i, + boost::bind(&zero_copy_if::get_recv_buff, xport.recv, _1), + true /*flush*/ + ); + + //Give the streamer a functor to handle overruns + //bind requires a weak_ptr to break the a streamer->streamer circular dependency + //Using "this" is OK because we know that this device3_impl will outlive the streamer + my_streamer->set_overflow_handler( + stream_i, + boost::bind( + &uhd::rfnoc::rx_stream_terminator::handle_overrun, recv_terminator, + boost::weak_ptr<uhd::rx_streamer>(my_streamer), stream_i + ) + ); + + //Give the streamer a functor to send flow control messages + //handle_rx_flowctrl is static and has no lifetime issues + boost::shared_ptr<rx_fc_cache_t> fc_cache(new rx_fc_cache_t()); + my_streamer->set_xport_handle_flowctrl( + stream_i, boost::bind( + &handle_rx_flowctrl, + xport.send_sid, + xport.send, + get_transport_endianness(mb_index), + fc_cache, + _1 + ), + fc_handle_window, + true/*init*/ + ); + + //Give the streamer a functor issue stream cmd + //bind requires a shared pointer to add a streamer->framer lifetime dependency + my_streamer->set_issue_stream_cmd( + stream_i, + boost::bind(&uhd::rfnoc::source_block_ctrl_base::issue_stream_cmd, blk_ctrl, _1, block_port) + ); + + // Tell the streamer which SID is valid for this channel + my_streamer->set_xport_chan_sid(stream_i, true, xport.send_sid); + } + + // Connect the terminator to the streamer + my_streamer->set_terminator(recv_terminator); + + // Notify all blocks in this chain that they are connected to an active streamer + recv_terminator->set_rx_streamer(true, 0); + + // Store a weak pointer to prevent a streamer->device3_impl->streamer circular dependency. + // Note that we store the streamer only once, and use its terminator's + // ID to do so. + _rx_streamers[recv_terminator->unique_id()] = boost::weak_ptr<sph::recv_packet_streamer>(my_streamer); + + // Sets tick rate, samp rate and scaling on this streamer. + // A registered terminator is required to do this. + update_rx_streamers(); + + post_streamer_hooks(RX_DIRECTION); + return my_streamer; +} + +/*********************************************************************** + * Transmit streamer + **********************************************************************/ +void device3_impl::update_tx_streamers(double /* rate */) +{ + BOOST_FOREACH(const std::string &block_id, _tx_streamers.keys()) { + UHD_STREAMER_LOG() << "[Device3] updating TX streamer: " << block_id << std::endl; + boost::shared_ptr<sph::send_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::send_packet_streamer>(_tx_streamers[block_id].lock()); + if (my_streamer) { + double tick_rate = my_streamer->get_terminator()->get_tick_rate(); + if (tick_rate == rfnoc::tick_node_ctrl::RATE_UNDEFINED) { + tick_rate = 1.0; + } + double samp_rate = my_streamer->get_terminator()->get_input_samp_rate(); + if (samp_rate == rfnoc::rate_node_ctrl::RATE_UNDEFINED) { + samp_rate = 1.0; + } + double scaling = my_streamer->get_terminator()->get_input_scale_factor(); + if (scaling == rfnoc::scalar_node_ctrl::SCALE_UNDEFINED) { + scaling = 32767.; + } + UHD_STREAMER_LOG() << " New tick_rate == " << tick_rate << " New samp_rate == " << samp_rate << " New scaling == " << scaling << std::endl; + my_streamer->set_tick_rate(tick_rate); + my_streamer->set_samp_rate(samp_rate); + my_streamer->set_scale_factor(scaling); + } + } +} + +tx_streamer::sptr device3_impl::get_tx_stream(const uhd::stream_args_t &args_) +{ + boost::mutex::scoped_lock lock(_transport_setup_mutex); + stream_args_t args = sanitize_stream_args(args_); + + // I. Generate the channel list + std::vector<uhd::rfnoc::block_id_t> chan_list; + std::vector<device_addr_t> chan_args; + generate_channel_list(args, chan_list, chan_args); + // Note: All 'args.args' are merged into chan_args now. + + //shared async queue for all channels in streamer + boost::shared_ptr<async_md_type> async_md(new async_md_type(1000/*messages deep*/)); + + // II. Iterate over all channels + boost::shared_ptr<sph::send_packet_streamer> my_streamer; + // The terminator's lifetime is coupled to the streamer. + // There is only one terminator. If the streamer has multiple channels, + // it will be connected to each downstream block. + rfnoc::tx_stream_terminator::sptr send_terminator = rfnoc::tx_stream_terminator::make(); + for (size_t stream_i = 0; stream_i < chan_list.size(); stream_i++) { + // Get block ID and mb index + uhd::rfnoc::block_id_t block_id = chan_list[stream_i]; + // Update args so args.args is always valid for this particular channel: + args.args = chan_args[stream_i]; + size_t mb_index = block_id.get_device_no(); + size_t suggested_block_port = args.args.cast<size_t>("block_port", rfnoc::ANY_PORT); + + // Access to this channel's block control + uhd::rfnoc::sink_block_ctrl_base::sptr blk_ctrl = + boost::dynamic_pointer_cast<uhd::rfnoc::sink_block_ctrl_base>(get_block_ctrl(block_id)); + + // Connect the terminator with this channel's block. + // This will throw if the connection is not possible. + size_t block_port = blk_ctrl->connect_upstream( + send_terminator, + suggested_block_port, + args.args + ); + const size_t terminator_port = send_terminator->connect_downstream(blk_ctrl); + blk_ctrl->set_upstream_port(block_port, terminator_port); + send_terminator->set_downstream_port(terminator_port, block_port); + + // Check if the block connection is compatible (spp and item type) + check_stream_sig_compatible(blk_ctrl->get_input_signature(block_port), args, "TX"); + + // Setup the dsp transport hints + device_addr_t tx_hints = get_tx_hints(mb_index); + + //allocate sid and create transport + uhd::sid_t stream_address = blk_ctrl->get_address(block_port); + UHD_STREAMER_LOG() << "[TX Streamer] creating tx stream " << tx_hints.to_string() << std::endl; + both_xports_t xport = make_transport(stream_address, TX_DATA, tx_hints); + UHD_STREAMER_LOG() << std::hex << "[TX Streamer] data_sid = " << xport.send_sid << std::dec << std::endl; + + // To calculate the max number of samples per packet, we assume the maximum header length + // to avoid fragmentation should the entire header be used. + const size_t bpp = tx_hints.cast<size_t>("bpp", xport.send->get_send_frame_size()) - stream_options.tx_max_len_hdr; + const size_t bpi = convert::get_bytes_per_item(args.otw_format); // bytes per item + const size_t spp = std::min(args.args.cast<size_t>("spp", bpp/bpi), bpp/bpi); // samples per packet + UHD_STREAMER_LOG() << "[TX Streamer] spp == " << spp << std::endl; + + //make the new streamer given the samples per packet + if (not my_streamer) + my_streamer = boost::make_shared<sph::send_packet_streamer>(spp); + my_streamer->resize(chan_list.size()); + + //init some streamer stuff + std::string conv_endianness; + if (get_transport_endianness(mb_index) == ENDIANNESS_BIG) { + my_streamer->set_vrt_packer(&vrt::chdr::if_hdr_pack_be); + conv_endianness = "be"; + } else { + my_streamer->set_vrt_packer(&vrt::chdr::if_hdr_pack_le); + conv_endianness = "le"; + } + + //set the converter + uhd::convert::id_type id; + id.input_format = args.cpu_format; + id.num_inputs = 1; + id.output_format = args.otw_format + "_item32_" + conv_endianness; + id.num_outputs = 1; + my_streamer->set_converter(id); + + //flow control setup + const size_t pkt_size = spp * bpi + stream_options.tx_max_len_hdr; + // For flow control, this value is used to determine the window size in *packets* + size_t fc_window = get_tx_flow_control_window( + pkt_size, // This is the maximum packet size + blk_ctrl->get_fifo_size(block_port), + tx_hints // This can override the value reported by the block! + ); + const size_t fc_handle_window = std::max<size_t>(1, fc_window / stream_options.tx_fc_response_freq); + UHD_STREAMER_LOG() << "[TX Streamer] Flow Control Window = " << fc_window << ", Flow Control Handler Window = " << fc_handle_window << std::endl; + blk_ctrl->configure_flow_control_in( + stream_options.tx_fc_response_cycles, + fc_handle_window, /*pkts*/ + block_port + ); + + boost::shared_ptr<tx_fc_cache_t> fc_cache(new tx_fc_cache_t()); + fc_cache->stream_channel = stream_i; + fc_cache->device_channel = mb_index; + fc_cache->async_queue = async_md; + fc_cache->old_async_queue = _async_md; + + boost::function<double(void)> tick_rate_retriever = boost::bind( + &rfnoc::tick_node_ctrl::get_tick_rate, + send_terminator, + std::set< rfnoc::node_ctrl_base::sptr >() // Need to specify default args with bind + ); + task::sptr task = task::make( + boost::bind( + &handle_tx_async_msgs, + fc_cache, + xport.recv, + get_transport_endianness(mb_index), + tick_rate_retriever + ) + ); + + blk_ctrl->sr_write(uhd::rfnoc::SR_RESP_IN_DST_SID, xport.recv_sid.get_dst(), block_port); + UHD_STREAMER_LOG() << "[TX Streamer] resp_in_dst_sid == " << boost::format("0x%04X") % xport.recv_sid.get_dst() << std::endl; + // Find all downstream radio nodes and set their response in SID to the host + std::vector<boost::shared_ptr<uhd::rfnoc::radio_ctrl> > downstream_radio_nodes = blk_ctrl->find_downstream_node<uhd::rfnoc::radio_ctrl>(); + UHD_STREAMER_LOG() << "[TX Streamer] Number of downstream radio nodes: " << downstream_radio_nodes.size() << std::endl; + BOOST_FOREACH(const boost::shared_ptr<uhd::rfnoc::radio_ctrl> &node, downstream_radio_nodes) { + node->sr_write(uhd::rfnoc::SR_RESP_IN_DST_SID, xport.send_sid.get_src(), block_port); + } + + //Give the streamer a functor to get the send buffer + //get_tx_buff_with_flowctrl is static so bind has no lifetime issues + //xport.send (sptr) is required to add streamer->data-transport lifetime dependency + //task (sptr) is required to add a streamer->async-handler lifetime dependency + my_streamer->set_xport_chan_get_buff( + stream_i, + boost::bind(&get_tx_buff_with_flowctrl, task, fc_cache, xport.send, fc_window, _1) + ); + //Give the streamer a functor handled received async messages + my_streamer->set_async_receiver( + boost::bind(&async_md_type::pop_with_timed_wait, async_md, _1, _2) + ); + my_streamer->set_xport_chan_sid(stream_i, true, xport.send_sid); + // CHDR does not support trailers + my_streamer->set_enable_trailer(false); + } + + // Connect the terminator to the streamer + my_streamer->set_terminator(send_terminator); + + // Notify all blocks in this chain that they are connected to an active streamer + send_terminator->set_tx_streamer(true, 0); + + // Store a weak pointer to prevent a streamer->device3_impl->streamer circular dependency. + // Note that we store the streamer only once, and use its terminator's + // ID to do so. + _tx_streamers[send_terminator->unique_id()] = boost::weak_ptr<sph::send_packet_streamer>(my_streamer); + + // Sets tick rate, samp rate and scaling on this streamer + // A registered terminator is required to do this. + update_tx_streamers(); + + post_streamer_hooks(TX_DIRECTION); + return my_streamer; +} + + diff --git a/host/lib/usrp/multi_usrp.cpp b/host/lib/usrp/multi_usrp.cpp index 0c3c78fc6..cb1ba8784 100644 --- a/host/lib/usrp/multi_usrp.cpp +++ b/host/lib/usrp/multi_usrp.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2013 Ettus Research LLC +// Copyright 2010-2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -27,6 +27,7 @@ #include <uhd/usrp/dboard_eeprom.hpp> #include <uhd/convert.hpp> #include <uhd/utils/soft_register.hpp> +#include "legacy_compat.hpp" #include <boost/assign/list_of.hpp> #include <boost/thread.hpp> #include <boost/foreach.hpp> @@ -390,12 +391,28 @@ public: multi_usrp_impl(const device_addr_t &addr){ _dev = device::make(addr, device::USRP); _tree = _dev->get_tree(); + _is_device3 = bool(boost::dynamic_pointer_cast<uhd::device3>(_dev)); + + if (is_device3()) { + _legacy_compat = rfnoc::legacy_compat::make(get_device3(), addr); + } } device::sptr get_device(void){ return _dev; } + bool is_device3(void) { + return _is_device3; + } + + device3::sptr get_device3(void) { + if (not is_device3()) { + throw uhd::type_error("Cannot call get_device3() on a non-generation 3 device."); + } + return boost::dynamic_pointer_cast<uhd::device3>(_dev); + } + dict<std::string, std::string> get_usrp_rx_info(size_t chan){ mboard_chan_pair mcp = rx_chan_to_mcp(chan); dict<std::string, std::string> usrp_info; @@ -608,7 +625,12 @@ public: void issue_stream_cmd(const stream_cmd_t &stream_cmd, size_t chan){ if (chan != ALL_CHANS){ - _tree->access<stream_cmd_t>(rx_dsp_root(chan) / "stream_cmd").set(stream_cmd); + if (is_device3()) { + mboard_chan_pair mcp = rx_chan_to_mcp(chan); + _legacy_compat->issue_stream_cmd(stream_cmd, mcp.mboard, mcp.chan); + } else { + _tree->access<stream_cmd_t>(rx_dsp_root(chan) / "stream_cmd").set(stream_cmd); + } return; } for (size_t c = 0; c < get_rx_num_channels(); c++){ @@ -743,6 +765,9 @@ public: ******************************************************************/ rx_streamer::sptr get_rx_stream(const stream_args_t &args) { _check_link_rate(args, false); + if (is_device3()) { + return _legacy_compat->get_rx_stream(args); + } return this->get_device()->get_rx_stream(args); } @@ -809,28 +834,10 @@ public: } tune_result_t set_rx_freq(const tune_request_t &tune_request, size_t chan){ - tune_request_t local_request = tune_request; - - // If any mixer is driven by an external LO the daughterboard assumes that no CORDIC correction is - // necessary. Since the LO might be sourced from another daughterboard which would normally apply a - // cordic correction we must disable all DSP tuning to ensure identical configurations across daughterboards. - if (tune_request.dsp_freq_policy == tune_request.POLICY_AUTO and - tune_request.rf_freq_policy == tune_request.POLICY_AUTO) - { - for (size_t c = 0; c < get_rx_num_channels(); c++) { - UHD_VAR(get_rx_lo_source(ALL_LOS, c)); - if (get_rx_lo_source(ALL_LOS, c) == "external") { - local_request.dsp_freq_policy = tune_request.POLICY_MANUAL; - local_request.dsp_freq = 0; - break; - } - } - } - tune_result_t result = tune_xx_subdev_and_dsp(RX_SIGN, _tree->subtree(rx_dsp_root(chan)), _tree->subtree(rx_rf_fe_root(chan)), - local_request); + tune_request); //do_tune_freq_results_message(tune_request, result, get_rx_freq(chan), "RX"); return result; } @@ -920,7 +927,7 @@ public: } } else { // If the daughterboard doesn't expose it's LO(s) then it can only be internal - return std::vector<std::string> {1, "internal"}; + return std::vector<std::string>(1, "internal"); } } @@ -1286,6 +1293,9 @@ public: ******************************************************************/ tx_streamer::sptr get_tx_stream(const stream_args_t &args) { _check_link_rate(args, true); + if (is_device3()) { + return _legacy_compat->get_tx_stream(args); + } return this->get_device()->get_tx_stream(args); } @@ -1680,6 +1690,8 @@ public: private: device::sptr _dev; property_tree::sptr _tree; + bool _is_device3; + uhd::rfnoc::legacy_compat::sptr _legacy_compat; struct mboard_chan_pair{ size_t mboard, chan; @@ -1732,6 +1744,10 @@ private: fs_path rx_dsp_root(const size_t chan) { mboard_chan_pair mcp = rx_chan_to_mcp(chan); + if (is_device3()) { + return _legacy_compat->rx_dsp_root(mcp.mboard, mcp.chan); + } + if (_tree->exists(mb_root(mcp.mboard) / "rx_chan_dsp_mapping")) { std::vector<size_t> map = _tree->access<std::vector<size_t> >(mb_root(mcp.mboard) / "rx_chan_dsp_mapping").get(); UHD_ASSERT_THROW(map.size() > mcp.chan); @@ -1752,6 +1768,10 @@ private: fs_path tx_dsp_root(const size_t chan) { mboard_chan_pair mcp = tx_chan_to_mcp(chan); + if (is_device3()) { + return _legacy_compat->tx_dsp_root(mcp.mboard, mcp.chan); + } + if (_tree->exists(mb_root(mcp.mboard) / "tx_chan_dsp_mapping")) { std::vector<size_t> map = _tree->access<std::vector<size_t> >(mb_root(mcp.mboard) / "tx_chan_dsp_mapping").get(); UHD_ASSERT_THROW(map.size() > mcp.chan); @@ -1771,6 +1791,9 @@ private: fs_path rx_fe_root(const size_t chan) { mboard_chan_pair mcp = rx_chan_to_mcp(chan); + if (is_device3()) { + return _legacy_compat->rx_fe_root(mcp.mboard, mcp.chan); + } try { const subdev_spec_pair_t spec = get_rx_subdev_spec(mcp.mboard).at(mcp.chan); @@ -1785,6 +1808,9 @@ private: fs_path tx_fe_root(const size_t chan) { mboard_chan_pair mcp = tx_chan_to_mcp(chan); + if (is_device3()) { + return _legacy_compat->tx_fe_root(mcp.mboard, mcp.chan); + } try { const subdev_spec_pair_t spec = get_tx_subdev_spec(mcp.mboard).at(mcp.chan); diff --git a/host/lib/usrp/x300/CMakeLists.txt b/host/lib/usrp/x300/CMakeLists.txt index f8b129f89..ea237b008 100644 --- a/host/lib/usrp/x300/CMakeLists.txt +++ b/host/lib/usrp/x300/CMakeLists.txt @@ -24,6 +24,7 @@ ######################################################################## IF(ENABLE_X300) LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/x300_radio_ctrl_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_fw_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_fw_uart.cpp @@ -33,7 +34,6 @@ IF(ENABLE_X300) ${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_adc_dac_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cdecode.c ) ENDIF(ENABLE_X300) diff --git a/host/lib/usrp/x300/x300_dac_ctrl.cpp b/host/lib/usrp/x300/x300_dac_ctrl.cpp index d49fba383..6ffd1ede4 100644 --- a/host/lib/usrp/x300/x300_dac_ctrl.cpp +++ b/host/lib/usrp/x300/x300_dac_ctrl.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2014 Ettus Research LLC +// Copyright 2010-2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by diff --git a/host/lib/usrp/x300/x300_dac_ctrl.hpp b/host/lib/usrp/x300/x300_dac_ctrl.hpp index f2a407971..ca47a90e7 100644 --- a/host/lib/usrp/x300/x300_dac_ctrl.hpp +++ b/host/lib/usrp/x300/x300_dac_ctrl.hpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2014 Ettus Research LLC +// Copyright 2010-2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by diff --git a/host/lib/usrp/x300/x300_dboard_iface.cpp b/host/lib/usrp/x300/x300_dboard_iface.cpp index 87f537874..b28768f90 100644 --- a/host/lib/usrp/x300/x300_dboard_iface.cpp +++ b/host/lib/usrp/x300/x300_dboard_iface.cpp @@ -15,92 +15,16 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. // -#include "x300_impl.hpp" +#include "x300_dboard_iface.hpp" #include "x300_regs.hpp" -#include <uhd/usrp/dboard_iface.hpp> #include <uhd/utils/safe_call.hpp> #include <boost/assign/list_of.hpp> #include <boost/math/special_functions/round.hpp> -#include "ad7922_regs.hpp" //aux adc -#include "ad5623_regs.hpp" //aux dac using namespace uhd; using namespace uhd::usrp; using namespace boost::assign; -class x300_dboard_iface : public dboard_iface -{ -public: - x300_dboard_iface(const x300_dboard_iface_config_t &config); - ~x300_dboard_iface(void); - - special_props_t get_special_props(void) - { - special_props_t props; - props.soft_clock_divider = false; - props.mangle_i2c_addrs = (_config.dboard_slot == 1); - return props; - } - - void write_aux_dac(unit_t, aux_dac_t, double); - double read_aux_adc(unit_t, aux_adc_t); - - void set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); - boost::uint32_t get_pin_ctrl(unit_t unit); - void set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); - boost::uint32_t get_atr_reg(unit_t unit, atr_reg_t reg); - void set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); - boost::uint32_t get_gpio_ddr(unit_t unit); - void set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); - boost::uint32_t get_gpio_out(unit_t unit); - boost::uint32_t read_gpio(unit_t unit); - - void set_command_time(const uhd::time_spec_t& t); - uhd::time_spec_t get_command_time(void); - - void write_i2c(boost::uint16_t, const byte_vector_t &); - byte_vector_t read_i2c(boost::uint16_t, size_t); - - void set_clock_rate(unit_t, double); - double get_clock_rate(unit_t); - std::vector<double> get_clock_rates(unit_t); - void set_clock_enabled(unit_t, bool); - double get_codec_rate(unit_t); - - void write_spi( - unit_t unit, - const spi_config_t &config, - boost::uint32_t data, - size_t num_bits - ); - - boost::uint32_t read_write_spi( - unit_t unit, - const spi_config_t &config, - boost::uint32_t data, - size_t num_bits - ); - void set_fe_connection( - unit_t unit, - const std::string& fe_name, - const fe_connection_t& fe_conn - ); - - const x300_dboard_iface_config_t _config; - uhd::dict<unit_t, ad5623_regs_t> _dac_regs; - uhd::dict<unit_t, double> _clock_rates; - void _write_aux_dac(unit_t); - -}; - -/*********************************************************************** - * Make Function - **********************************************************************/ -dboard_iface::sptr x300_make_dboard_iface(const x300_dboard_iface_config_t &config) -{ - return dboard_iface::sptr(new x300_dboard_iface(config)); -} - /*********************************************************************** * Structors **********************************************************************/ @@ -375,12 +299,23 @@ void x300_dboard_iface::set_command_time(const uhd::time_spec_t& t) _config.cmd_time_ctrl->set_time(t); } +void x300_dboard_iface::add_rx_fe( + const std::string& fe_name, + rx_frontend_core_3000::sptr fe_core) +{ + _rx_fes[fe_name] = fe_core; +} + void x300_dboard_iface::set_fe_connection( - unit_t unit, const std::string& /*fe_name*/, + unit_t unit, const std::string& fe_name, const fe_connection_t& fe_conn) { if (unit == UNIT_RX) { - _config.rx_dsp->set_mux(fe_conn); + if (_rx_fes.has_key(fe_name)) { + _rx_fes[fe_name]->set_fe_connection(fe_conn); + } else { + throw uhd::assertion_error("front-end name was not registered: " + fe_name); + } } else { throw uhd::not_implemented_error("frontend connection not configurable for TX"); } diff --git a/host/lib/usrp/x300/x300_dboard_iface.hpp b/host/lib/usrp/x300/x300_dboard_iface.hpp new file mode 100644 index 000000000..124d768e8 --- /dev/null +++ b/host/lib/usrp/x300/x300_dboard_iface.hpp @@ -0,0 +1,115 @@ +// +// Copyright 2010-2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_X300_DBOARD_IFACE_HPP +#define INCLUDED_X300_DBOARD_IFACE_HPP + +#include "x300_clock_ctrl.hpp" +#include "spi_core_3000.hpp" +#include "i2c_core_100_wb32.hpp" +#include "gpio_atr_3000.hpp" +#include "rx_frontend_core_3000.hpp" +#include <uhd/usrp/dboard_iface.hpp> +#include "ad7922_regs.hpp" //aux adc +#include "ad5623_regs.hpp" //aux dac +#include <uhd/types/dict.hpp> + +struct x300_dboard_iface_config_t +{ + uhd::usrp::gpio_atr::db_gpio_atr_3000::sptr gpio; + spi_core_3000::sptr spi; + size_t rx_spi_slaveno; + size_t tx_spi_slaveno; + uhd::i2c_iface::sptr i2c; + x300_clock_ctrl::sptr clock; + x300_clock_which_t which_rx_clk; + x300_clock_which_t which_tx_clk; + boost::uint8_t dboard_slot; + uhd::timed_wb_iface::sptr cmd_time_ctrl; +}; + +class x300_dboard_iface : public uhd::usrp::dboard_iface +{ +public: + x300_dboard_iface(const x300_dboard_iface_config_t &config); + ~x300_dboard_iface(void); + + inline special_props_t get_special_props(void) + { + special_props_t props; + props.soft_clock_divider = false; + props.mangle_i2c_addrs = (_config.dboard_slot == 1); + return props; + } + + void write_aux_dac(unit_t, aux_dac_t, double); + double read_aux_adc(unit_t, aux_adc_t); + + void set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_pin_ctrl(unit_t unit); + void set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_atr_reg(unit_t unit, atr_reg_t reg); + void set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_ddr(unit_t unit); + void set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_out(unit_t unit); + boost::uint32_t read_gpio(unit_t unit); + + void set_command_time(const uhd::time_spec_t& t); + uhd::time_spec_t get_command_time(void); + + void write_i2c(boost::uint16_t, const uhd::byte_vector_t &); + uhd::byte_vector_t read_i2c(boost::uint16_t, size_t); + + void set_clock_rate(unit_t, double); + double get_clock_rate(unit_t); + std::vector<double> get_clock_rates(unit_t); + void set_clock_enabled(unit_t, bool); + double get_codec_rate(unit_t); + + void write_spi( + unit_t unit, + const uhd::spi_config_t &config, + boost::uint32_t data, + size_t num_bits + ); + + boost::uint32_t read_write_spi( + unit_t unit, + const uhd::spi_config_t &config, + boost::uint32_t data, + size_t num_bits + ); + void set_fe_connection( + unit_t unit, const std::string& name, + const uhd::usrp::fe_connection_t& fe_conn); + + void add_rx_fe( + const std::string& fe_name, + rx_frontend_core_3000::sptr fe_core); + +private: + const x300_dboard_iface_config_t _config; + uhd::dict<unit_t, ad5623_regs_t> _dac_regs; + uhd::dict<unit_t, double> _clock_rates; + uhd::dict<std::string, rx_frontend_core_3000::sptr> _rx_fes; + void _write_aux_dac(unit_t); +}; + + + +#endif /* INCLUDED_X300_DBOARD_IFACE_HPP */ diff --git a/host/lib/usrp/x300/x300_fw_common.h b/host/lib/usrp/x300/x300_fw_common.h index 6039ee376..e05cd9ec7 100644 --- a/host/lib/usrp/x300/x300_fw_common.h +++ b/host/lib/usrp/x300/x300_fw_common.h @@ -33,7 +33,7 @@ extern "C" { #define X300_REVISION_MIN 2 #define X300_FW_COMPAT_MAJOR 4 #define X300_FW_COMPAT_MINOR 0 -#define X300_FPGA_COMPAT_MAJOR 20 +#define X300_FPGA_COMPAT_MAJOR 0x20 //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 b59b920ab..ce257947e 100644 --- a/host/lib/usrp/x300/x300_impl.cpp +++ b/host/lib/usrp/x300/x300_impl.cpp @@ -1,5 +1,5 @@ // -// Copyright 2013-2015 Ettus Research LLC +// Copyright 2013-2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -47,24 +47,16 @@ using namespace uhd; using namespace uhd::usrp; +using namespace uhd::rfnoc; using namespace uhd::transport; using namespace uhd::niusrprio; using namespace uhd::usrp::gpio_atr; using namespace uhd::usrp::x300; namespace asio = boost::asio; -static bool has_dram_buff(wb_iface::sptr zpu_ctrl) { - bool dramR0 = dma_fifo_core_3000::check( - zpu_ctrl, SR_ADDR(SET0_BASE, ZPU_SR_DRAM_FIFO0), SR_ADDR(SET0_BASE, ZPU_RB_DRAM_FIFO0)); - bool dramR1 = dma_fifo_core_3000::check( - zpu_ctrl, SR_ADDR(SET0_BASE, ZPU_SR_DRAM_FIFO1), SR_ADDR(SET0_BASE, ZPU_RB_DRAM_FIFO1)); - return (dramR0 and dramR1); -} - static std::string get_fpga_option(wb_iface::sptr zpu_ctrl) { //Possible options: //1G = {0:1G, 1:1G} w/ DRAM, HG = {0:1G, 1:10G} w/ DRAM, XG = {0:10G, 1:10G} w/ DRAM - //1GS = {0:1G, 1:1G} w/ SRAM, HGS = {0:1G, 1:10G} w/ SRAM, XGS = {0:10G, 1:10G} w/ SRAM //HA = {0:1G, 1:Aurora} w/ DRAM, XA = {0:10G, 1:Aurora} w/ DRAM std::string option; @@ -84,9 +76,6 @@ static std::string get_fpga_option(wb_iface::sptr zpu_ctrl) { } else { option = "HG"; //Default } - if (not has_dram_buff(zpu_ctrl)) { - option += "S"; - } return option; } @@ -250,7 +239,7 @@ static device_addrs_t x300_find_pcie(const device_addr_t &hint, bool explicit_qu //set these values as empty string so the device may still be found //and the filter's below can still operate on the discovered device if (not hint.has_key("fpga")) { - new_addr["fpga"] = "HGS"; + new_addr["fpga"] = "HG"; } new_addr["name"] = ""; new_addr["serial"] = ""; @@ -388,15 +377,13 @@ static void x300_load_fw(wb_iface::sptr fw_reg_ctrl, const std::string &file_nam UHD_MSG(status) << " done!" << std::endl; } -x300_impl::x300_impl(const uhd::device_addr_t &dev_addr) +x300_impl::x300_impl(const uhd::device_addr_t &dev_addr) + : device3_impl() + , _sid_framer(0) { UHD_MSG(status) << "X300 initialization sequence..." << std::endl; - _type = device::USRP; _ignore_cal_file = dev_addr.has_key("ignore-cal-file"); - _async_md.reset(new async_md_type(1000/*messages deep*/)); - _tree = uhd::property_tree::make(); _tree->create<std::string>("/name").set("X-Series Device"); - _sid_framer = 0; const device_addrs_t device_args = separate_device_addr(dev_addr); _mb.resize(device_args.size()); @@ -519,6 +506,7 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) std::string eth0_addr = dev_addr.has_key("resource") ? dev_addr["resource"] : dev_addr["addr"]; eth_addrs.push_back(eth0_addr); + mb.next_src_addr = 0; //Host source address for blocks if (dev_addr.has_key("second_addr")) { std::string eth1_addr = dev_addr["second_addr"]; @@ -570,6 +558,8 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) const boost::uint32_t tx_data_fifos[2] = {X300_RADIO_DEST_PREFIX_TX, X300_RADIO_DEST_PREFIX_TX + 3}; mb.rio_fpga_interface->get_kernel_proxy()->get_rio_quirks().register_tx_streams(tx_data_fifos, 2); + _tree->create<size_t>(mb_path / "mtu/recv").set(X300_PCIE_RX_DATA_FRAME_SIZE); + _tree->create<size_t>(mb_path / "mtu/send").set(X300_PCIE_TX_DATA_FRAME_SIZE); _tree->create<double>(mb_path / "link_max_rate").set(X300_MAX_RATE_PCIE); } @@ -657,6 +647,8 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) << std::endl; } + _tree->create<size_t>(mb_path / "mtu/recv").set(_max_frame_sizes.recv_frame_size); + _tree->create<size_t>(mb_path / "mtu/send").set(_max_frame_sizes.send_frame_size); _tree->create<double>(mb_path / "link_max_rate").set(X300_MAX_RATE_10GIGE); } @@ -777,15 +769,6 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) } //////////////////////////////////////////////////////////////////// - // read dboard eeproms - //////////////////////////////////////////////////////////////////// - for (size_t i = 0; i < 8; i++) - { - if (i == 0 or i == 2) continue; //not used - mb.db_eeproms[i].load(*mb.zpu_i2c, 0x50 | i); - } - - //////////////////////////////////////////////////////////////////// // read hardware revision and compatibility number //////////////////////////////////////////////////////////////////// mb.hw_rev = 0; @@ -847,15 +830,14 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) //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, "internal"); + update_clock_source(mb, X300_DEFAULT_CLOCK_SOURCE); //////////////////////////////////////////////////////////////////// // create clock properties //////////////////////////////////////////////////////////////////// - _tree->create<double>(mb_path / "tick_rate") - .set_publisher(boost::bind(&x300_clock_ctrl::get_master_clock_rate, mb.clock)); - - _tree->create<time_spec_t>(mb_path / "time" / "cmd"); + _tree->create<double>(mb_path / "master_clock_rate") + .set_publisher(boost::bind(&x300_clock_ctrl::get_master_clock_rate, mb.clock)) + ; UHD_MSG(status) << "Radio 1x clock:" << (mb.clock->get_master_clock_rate()/1e6) << std::endl; @@ -898,78 +880,6 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) mb.zpu_ctrl->poke32(SR_ADDR(SETXB_BASE, i), 0); } - //////////////////////////////////////////////////////////////////// - // DRAM FIFO initialization - //////////////////////////////////////////////////////////////////// - mb.has_dram_buff = has_dram_buff(mb.zpu_ctrl); - if (mb.has_dram_buff) { - for (size_t i = 0; i < mboard_members_t::NUM_RADIOS; i++) { - static const size_t NUM_REGS = 8; - mb.dram_buff_ctrl[i] = dma_fifo_core_3000::make( - mb.zpu_ctrl, - SR_ADDR(SET0_BASE, ZPU_SR_DRAM_FIFO0+(i*NUM_REGS)), - SR_ADDR(SET0_BASE, ZPU_RB_DRAM_FIFO0+i)); - mb.dram_buff_ctrl[i]->resize(X300_DRAM_FIFO_SIZE * i, X300_DRAM_FIFO_SIZE); - - if (mb.dram_buff_ctrl[i]->ext_bist_supported()) { - UHD_MSG(status) << boost::format("Running BIST for DRAM FIFO %d... ") % i; - boost::uint32_t bisterr = mb.dram_buff_ctrl[i]->run_bist(); - if (bisterr != 0) { - throw uhd::runtime_error(str(boost::format("DRAM FIFO BIST failed! (code: %d)\n") % bisterr)); - } else { - double throughput = mb.dram_buff_ctrl[i]->get_bist_throughput(X300_BUS_CLOCK_RATE); - UHD_MSG(status) << (boost::format("pass (Throughput: %.1fMB/s)") % (throughput/1e6)) << std::endl; - } - } else { - if (mb.dram_buff_ctrl[i]->run_bist() != 0) { - throw uhd::runtime_error(str(boost::format("DRAM FIFO %d BIST failed!\n") % i)); - } - } - } - } - - //////////////////////////////////////////////////////////////////// - // setup radios - //////////////////////////////////////////////////////////////////// - this->setup_radio(mb_i, "A", dev_addr); - this->setup_radio(mb_i, "B", dev_addr); - - //////////////////////////////////////////////////////////////////// - // ADC test and cal - //////////////////////////////////////////////////////////////////// - if (dev_addr.has_key("self_cal_adc_delay")) { - self_cal_adc_xfer_delay(mb, true /* Apply ADC delay */); - } - if (dev_addr.has_key("ext_adc_self_test")) { - extended_adc_test(mb, dev_addr.cast<double>("ext_adc_self_test", 30)); - } else if ( ! dev_addr.has_key("disable_adc_self_test") ) { - self_test_adcs(mb); - } - - //////////////////////////////////////////////////////////////////// - // front panel gpio - //////////////////////////////////////////////////////////////////// - mb.fp_gpio = gpio_atr_3000::make(mb.radio_perifs[0].ctrl, radio::sr_addr(radio::FP_GPIO), radio::RB32_FP_GPIO); - BOOST_FOREACH(const gpio_attr_map_t::value_type attr, gpio_attr_map) - { - _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / attr.second) - .set(0) - .add_coerced_subscriber(boost::bind(&gpio_atr_3000::set_gpio_attr, mb.fp_gpio, attr.first, _1)); - } - _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "READBACK") - .set_publisher(boost::bind(&gpio_atr_3000::read_gpio, mb.fp_gpio)); - - //////////////////////////////////////////////////////////////////// - // register the time keepers - only one can be the highlander - //////////////////////////////////////////////////////////////////// - _tree->create<time_spec_t>(mb_path / "time" / "now") - .set_publisher(boost::bind(&time_core_3000::get_time_now, mb.radio_perifs[0].time64)) - .add_coerced_subscriber(boost::bind(&x300_impl::sync_times, this, mb, _1)) - .set(0.0); - _tree->create<time_spec_t>(mb_path / "time" / "pps") - .set_publisher(boost::bind(&time_core_3000::get_time_last_pps, mb.radio_perifs[0].time64)) - .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_next_pps, mb.radio_perifs[0].time64, _1)) - .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_next_pps, mb.radio_perifs[1].time64, _1)); //////////////////////////////////////////////////////////////////// // setup time sources and properties @@ -989,7 +899,7 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) // setup clock sources and properties //////////////////////////////////////////////////////////////////// _tree->create<std::string>(mb_path / "clock_source" / "value") - .set("internal") + .set(X300_DEFAULT_CLOCK_SOURCE) .add_coerced_subscriber(boost::bind(&x300_impl::update_clock_source, this, boost::ref(mb), _1)); static const std::vector<std::string> clock_source_options = boost::assign::list_of("internal")("external")("gpsdo"); @@ -1009,21 +919,11 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) .add_coerced_subscriber(boost::bind(&x300_clock_ctrl::set_ref_out, mb.clock, _1)); //initialize tick rate (must be done before setting time) - _tree->access<double>(mb_path / "tick_rate") - .add_coerced_subscriber(boost::bind(&x300_impl::set_tick_rate, this, boost::ref(mb), _1)) - .add_coerced_subscriber(boost::bind(&x300_impl::update_tick_rate, this, boost::ref(mb), _1)) - .set(mb.clock->get_master_clock_rate()); - - //////////////////////////////////////////////////////////////////// - // create frontend mapping - //////////////////////////////////////////////////////////////////// - std::vector<size_t> default_map(2, 0); default_map[1] = 1; - _tree->create<std::vector<size_t> >(mb_path / "rx_chan_dsp_mapping").set(default_map); - _tree->create<std::vector<size_t> >(mb_path / "tx_chan_dsp_mapping").set(default_map); - _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") - .add_coerced_subscriber(boost::bind(&x300_impl::update_subdev_spec, this, "rx", mb_i, _1)); - _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") - .add_coerced_subscriber(boost::bind(&x300_impl::update_subdev_spec, this, "tx", mb_i, _1)); + _tree->create<double>(mb_path / "tick_rate") + .add_coerced_subscriber(boost::bind(&device3_impl::update_tx_streamers, this, _1)) + .add_coerced_subscriber(boost::bind(&device3_impl::update_rx_streamers, this, _1)) + .set(mb.clock->get_master_clock_rate()) + ; //////////////////////////////////////////////////////////////////// // and do the misc mboard sensors @@ -1031,29 +931,55 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) _tree->create<sensor_value_t>(mb_path / "sensors" / "ref_locked") .set_publisher(boost::bind(&x300_impl::get_ref_locked, this, mb)); - //////////////////////////////////////////////////////////////////// - // do some post-init tasks - //////////////////////////////////////////////////////////////////// - subdev_spec_t rx_fe_spec, tx_fe_spec; - rx_fe_spec.push_back(subdev_spec_pair_t("A", - _tree->list(mb_path / "dboards" / "A" / "rx_frontends").at(0))); - rx_fe_spec.push_back(subdev_spec_pair_t("B", - _tree->list(mb_path / "dboards" / "B" / "rx_frontends").at(0))); - tx_fe_spec.push_back(subdev_spec_pair_t("A", - _tree->list(mb_path / "dboards" / "A" / "tx_frontends").at(0))); - tx_fe_spec.push_back(subdev_spec_pair_t("B", - _tree->list(mb_path / "dboards" / "B" / "tx_frontends").at(0))); - - _tree->access<subdev_spec_t>(mb_path / "rx_subdev_spec").set(rx_fe_spec); - _tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(tx_fe_spec); - - mb.regmap_db = boost::make_shared<uhd::soft_regmap_db_t>(); - mb.regmap_db->add(*mb.fw_regmap); - mb.regmap_db->add(*mb.radio_perifs[0].regmap); - mb.regmap_db->add(*mb.radio_perifs[1].regmap); - - _tree->create<uhd::soft_regmap_accessor_t::sptr>(mb_path / "registers") - .set(mb.regmap_db); + //////////////// 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, + mb.if_pkt_is_big_endian ? ENDIANNESS_BIG : ENDIANNESS_LITTLE + ); + //////////////// 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_MSG(warning) << "Too many Radio Blocks found. Using only the first two." << std::endl; + radio_ids.resize(2); + } + + BOOST_FOREACH(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, dev_addr.has_key("self_cal_adc_delay")); + } + + //////////////////////////////////////////////////////////////////// + // ADC test and cal + //////////////////////////////////////////////////////////////////// + if (dev_addr.has_key("self_cal_adc_delay")) { + rfnoc::x300_radio_ctrl_impl::self_cal_adc_xfer_delay( + mb.radios, mb.clock, + boost::bind(&x300_impl::wait_for_clk_locked, this, mb, fw_regmap_t::clk_status_reg_t::LMK_LOCK, _1), + true /* Apply ADC delay */); + } + if (dev_addr.has_key("ext_adc_self_test")) { + rfnoc::x300_radio_ctrl_impl::extended_adc_test( + mb.radios, + dev_addr.cast<double>("ext_adc_self_test", 30)); + } else if (not dev_addr.has_key("recover_mb_eeprom")){ + for (size_t i = 0; i < mb.radios.size(); i++) { + mb.radios.at(i)->self_test_adc(); + } + } + } else { + UHD_MSG(status) << "No Radio Block found. Assuming radio-less operation." << std::endl; + } mb.initialization_done = true; } @@ -1064,14 +990,6 @@ x300_impl::~x300_impl(void) { BOOST_FOREACH(mboard_members_t &mb, _mb) { - //Disable/reset ADC/DAC - mb.radio_perifs[0].regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1); - mb.radio_perifs[0].regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0); - mb.radio_perifs[0].regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 0); - mb.radio_perifs[0].regmap->misc_outs_reg.flush(); - mb.radio_perifs[1].regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 0); - mb.radio_perifs[1].regmap->misc_outs_reg.flush(); - //kill the claimer task and unclaim the device mb.claimer_task.reset(); { //Critical section @@ -1090,268 +1008,117 @@ x300_impl::~x300_impl(void) } } -void x300_impl::setup_radio(const size_t mb_i, const std::string &slot_name, const uhd::device_addr_t &dev_addr) +uint32_t x300_impl::allocate_pcie_dma_chan(const uhd::sid_t &tx_sid, const xport_type_t xport_type) { - const fs_path mb_path = "/mboards/"+boost::lexical_cast<std::string>(mb_i); - UHD_ASSERT_THROW(mb_i < _mb.size()); - mboard_members_t &mb = _mb[mb_i]; - const size_t radio_index = mb.get_radio_index(slot_name); - radio_perifs_t &perif = mb.radio_perifs[radio_index]; - - UHD_MSG(status) << boost::format("Initialize Radio%d control...") % radio_index << std::endl; - - //////////////////////////////////////////////////////////////////// - // radio control - //////////////////////////////////////////////////////////////////// - boost::uint8_t dest = (radio_index == 0)? X300_XB_DST_R0 : X300_XB_DST_R1; - boost::uint32_t ctrl_sid; - both_xports_t xport = this->make_transport(mb_i, dest, X300_RADIO_DEST_PREFIX_CTRL, device_addr_t(), ctrl_sid); - perif.ctrl = radio_ctrl_core_3000::make(mb.if_pkt_is_big_endian, xport.recv, xport.send, ctrl_sid, slot_name); - - perif.regmap = boost::make_shared<radio_regmap_t>(radio_index); - perif.regmap->initialize(*perif.ctrl, true); - - //Only Radio0 has the ADC/DAC reset bits. Those bits are reserved for Radio1 - if (radio_index == 0) { - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1); - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0); - perif.regmap->misc_outs_reg.flush(); - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 0); - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 1); - perif.regmap->misc_outs_reg.flush(); - } - perif.regmap->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 1); - - this->register_loopback_self_test(perif.ctrl); - - //////////////////////////////////////////////////////////////// - // Setup peripherals - //////////////////////////////////////////////////////////////// - perif.spi = spi_core_3000::make(perif.ctrl, radio::sr_addr(radio::SPI), radio::RB32_SPI); - perif.adc = x300_adc_ctrl::make(perif.spi, DB_ADC_SEN); - perif.dac = x300_dac_ctrl::make(perif.spi, DB_DAC_SEN, mb.clock->get_master_clock_rate()); - perif.leds = gpio_atr_3000::make_write_only(perif.ctrl, radio::sr_addr(radio::LEDS)); - perif.leds->set_atr_mode(MODE_ATR, 0xFFFFFFFF); - perif.rx_fe = rx_frontend_core_200::make(perif.ctrl, radio::sr_addr(radio::RX_FRONT)); - perif.rx_fe->set_dc_offset(rx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); - perif.rx_fe->set_dc_offset_auto(rx_frontend_core_200::DEFAULT_DC_OFFSET_ENABLE); - perif.tx_fe = tx_frontend_core_200::make(perif.ctrl, radio::sr_addr(radio::TX_FRONT)); - perif.tx_fe->set_dc_offset(tx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); - perif.tx_fe->set_iq_balance(tx_frontend_core_200::DEFAULT_IQ_BALANCE_VALUE); - perif.framer = rx_vita_core_3000::make(perif.ctrl, radio::sr_addr(radio::RX_CTRL)); - perif.ddc = rx_dsp_core_3000::make(perif.ctrl, radio::sr_addr(radio::RX_DSP)); - perif.ddc->set_link_rate(10e9/8); //whatever - perif.ddc->set_tick_rate(mb.clock->get_master_clock_rate()); - //The DRAM FIFO is treated as in internal radio FIFO for flow control purposes - tx_vita_core_3000::fc_monitor_loc fc_loc = - mb.has_dram_buff ? tx_vita_core_3000::FC_PRE_FIFO : tx_vita_core_3000::FC_PRE_RADIO; - perif.deframer = tx_vita_core_3000::make(perif.ctrl, radio::sr_addr(radio::TX_CTRL), fc_loc); - perif.duc = tx_dsp_core_3000::make(perif.ctrl, radio::sr_addr(radio::TX_DSP)); - perif.duc->set_link_rate(10e9/8); //whatever - perif.duc->set_tick_rate(mb.clock->get_master_clock_rate()); - - //////////////////////////////////////////////////////////////////// - // create time control objects - //////////////////////////////////////////////////////////////////// - time_core_3000::readback_bases_type time64_rb_bases; - time64_rb_bases.rb_now = radio::RB64_TIME_NOW; - time64_rb_bases.rb_pps = radio::RB64_TIME_PPS; - perif.time64 = time_core_3000::make(perif.ctrl, radio::sr_addr(radio::TIME), time64_rb_bases); - - //Capture delays are calibrated every time. The status is only printed is the user - //asks to run the xfer self cal using "self_cal_adc_delay" - self_cal_adc_capture_delay(mb, radio_index, dev_addr.has_key("self_cal_adc_delay")); - - _tree->access<time_spec_t>(mb_path / "time" / "cmd") - .add_coerced_subscriber(boost::bind(&radio_ctrl_core_3000::set_time, perif.ctrl, _1)); - - //////////////////////////////////////////////////////////////// - // create codec control objects - //////////////////////////////////////////////////////////////// - _tree->create<int>(mb_path / "rx_codecs" / slot_name / "gains"); //phony property so this dir exists - _tree->create<int>(mb_path / "tx_codecs" / slot_name / "gains"); //phony property so this dir exists - _tree->create<std::string>(mb_path / "rx_codecs" / slot_name / "name").set("ads62p48"); - _tree->create<std::string>(mb_path / "tx_codecs" / slot_name / "name").set("ad9146"); - - _tree->create<meta_range_t>(mb_path / "rx_codecs" / slot_name / "gains" / "digital" / "range").set(meta_range_t(0, 6.0, 0.5)); - _tree->create<double>(mb_path / "rx_codecs" / slot_name / "gains" / "digital" / "value") - .add_coerced_subscriber(boost::bind(&x300_adc_ctrl::set_gain, perif.adc, _1)).set(0); - - //////////////////////////////////////////////////////////////////// - // front end corrections - //////////////////////////////////////////////////////////////////// - perif.rx_fe->populate_subtree(_tree->subtree(mb_path / "rx_frontends" / slot_name)); - perif.tx_fe->populate_subtree(_tree->subtree(mb_path / "tx_frontends" / slot_name)); - - //////////////////////////////////////////////////////////////////// - // connect rx dsp control objects - //////////////////////////////////////////////////////////////////// - const fs_path rx_dsp_path = mb_path / "rx_dsps" / str(boost::format("%u") % radio_index); - perif.ddc->populate_subtree(_tree->subtree(rx_dsp_path)); - _tree->access<double>(rx_dsp_path / "rate" / "value") - .add_coerced_subscriber(boost::bind(&x300_impl::update_rx_samp_rate, this, boost::ref(mb), radio_index, _1)) - ; - _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") - .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); - - //////////////////////////////////////////////////////////////////// - // connect tx dsp control objects - //////////////////////////////////////////////////////////////////// - const fs_path tx_dsp_path = mb_path / "tx_dsps" / str(boost::format("%u") % radio_index); - perif.duc->populate_subtree(_tree->subtree(tx_dsp_path)); - _tree->access<double>(tx_dsp_path / "rate" / "value") - .add_coerced_subscriber(boost::bind(&x300_impl::update_tx_samp_rate, this, boost::ref(mb), radio_index, _1)) - ; - - //////////////////////////////////////////////////////////////////// - // create RF frontend interfacing - //////////////////////////////////////////////////////////////////// - const fs_path db_path = (mb_path / "dboards" / slot_name); - const size_t j = (slot_name == "B")? 0x2 : 0x0; - _tree->create<dboard_eeprom_t>(db_path / "rx_eeprom") - .set(mb.db_eeproms[X300_DB0_RX_EEPROM | j]) - .add_coerced_subscriber(boost::bind(&x300_impl::set_db_eeprom, this, mb.zpu_i2c, (0x50 | X300_DB0_RX_EEPROM | j), _1)); - _tree->create<dboard_eeprom_t>(db_path / "tx_eeprom") - .set(mb.db_eeproms[X300_DB0_TX_EEPROM | j]) - .add_coerced_subscriber(boost::bind(&x300_impl::set_db_eeprom, this, mb.zpu_i2c, (0x50 | X300_DB0_TX_EEPROM | j), _1)); - _tree->create<dboard_eeprom_t>(db_path / "gdb_eeprom") - .set(mb.db_eeproms[X300_DB0_GDB_EEPROM | j]) - .add_coerced_subscriber(boost::bind(&x300_impl::set_db_eeprom, this, mb.zpu_i2c, (0x50 | X300_DB0_GDB_EEPROM | j), _1)); - - //create a new dboard interface - x300_dboard_iface_config_t db_config; - db_config.gpio = db_gpio_atr_3000::make(perif.ctrl, radio::sr_addr(radio::GPIO), radio::RB32_GPIO); - db_config.spi = perif.spi; - db_config.rx_spi_slaveno = DB_RX_SEN; - db_config.tx_spi_slaveno = DB_TX_SEN; - db_config.i2c = mb.zpu_i2c; - db_config.clock = mb.clock; - db_config.rx_dsp = mb.radio_perifs[radio_index].ddc; - db_config.which_rx_clk = (slot_name == "A")? X300_CLOCK_WHICH_DB0_RX : X300_CLOCK_WHICH_DB1_RX; - db_config.which_tx_clk = (slot_name == "A")? X300_CLOCK_WHICH_DB0_TX : X300_CLOCK_WHICH_DB1_TX; - db_config.dboard_slot = (slot_name == "A")? 0 : 1; - db_config.cmd_time_ctrl = perif.ctrl; - - //create a new dboard manager - _dboard_managers[db_path] = dboard_manager::make( - mb.db_eeproms[X300_DB0_RX_EEPROM | j].id, - mb.db_eeproms[X300_DB0_TX_EEPROM | j].id, - mb.db_eeproms[X300_DB0_GDB_EEPROM | j].id, - x300_make_dboard_iface(db_config), - _tree->subtree(db_path) - ); - - //now that dboard is created -- register into rx antenna event - const std::string fe_name = _tree->list(db_path / "rx_frontends").front(); - _tree->access<std::string>(db_path / "rx_frontends" / fe_name / "antenna" / "value") - .add_coerced_subscriber(boost::bind(&x300_impl::update_atr_leds, this, mb.radio_perifs[radio_index].leds, _1)); - this->update_atr_leds(mb.radio_perifs[radio_index].leds, ""); //init anyway, even if never called - - //bind frontend corrections to the dboard freq props - const fs_path db_tx_fe_path = db_path / "tx_frontends"; - BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)) { - _tree->access<double>(db_tx_fe_path / name / "freq" / "value") - .add_coerced_subscriber(boost::bind(&x300_impl::set_tx_fe_corrections, this, mb_path, slot_name, _1)); - } - const fs_path db_rx_fe_path = db_path / "rx_frontends"; - BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)) { - _tree->access<double>(db_rx_fe_path / name / "freq" / "value") - .add_coerced_subscriber(boost::bind(&x300_impl::set_rx_fe_corrections, this, mb_path, slot_name, _1)); - } -} + static const uint32_t CTRL_CHANNEL = 0; + static const uint32_t FIRST_DATA_CHANNEL = 1; + if (xport_type == CTRL) { + return CTRL_CHANNEL; + } else { + // sid_t has no comparison defined + uint32_t raw_sid = tx_sid.get(); -void x300_impl::set_rx_fe_corrections(const uhd::fs_path &mb_path, const std::string &fe_name, const double lo_freq) -{ - if(not _ignore_cal_file){ - apply_rx_fe_corrections(this->get_tree()->subtree(mb_path), fe_name, lo_freq); - } -} + if (_dma_chan_pool.count(raw_sid) == 0) { + _dma_chan_pool[raw_sid] = _dma_chan_pool.size() + FIRST_DATA_CHANNEL; + UHD_MSG(status) << "[X300] Assigning PCIe DMA channel " << _dma_chan_pool[raw_sid] + << " to SID " << tx_sid.to_pp_string_hex() << std::endl; + } -void x300_impl::set_tx_fe_corrections(const uhd::fs_path &mb_path, const std::string &fe_name, const double lo_freq) -{ - if(not _ignore_cal_file){ - apply_tx_fe_corrections(this->get_tree()->subtree(mb_path), fe_name, lo_freq); + if (_dma_chan_pool.size() + FIRST_DATA_CHANNEL > X300_PCIE_MAX_CHANNELS) { + throw uhd::runtime_error("Trying to allocate more DMA channels than are available"); + } + return _dma_chan_pool[raw_sid]; } } -boost::uint32_t get_pcie_dma_channel(boost::uint8_t destination, boost::uint8_t prefix) -{ - static const boost::uint32_t RADIO_GRP_SIZE = 3; - static const boost::uint32_t RADIO0_GRP = 0; - static const boost::uint32_t RADIO1_GRP = 1; - - boost::uint32_t radio_grp = (destination == X300_XB_DST_R0) ? RADIO0_GRP : RADIO1_GRP; - return ((radio_grp * RADIO_GRP_SIZE) + prefix); +static boost::uint32_t extract_sid_from_pkt(void* pkt, size_t) { + return uhd::sid_t(uhd::wtohx(static_cast<const boost::uint32_t*>(pkt)[1])).get_dst(); } -x300_impl::both_xports_t x300_impl::make_transport( - const size_t mb_index, - const boost::uint8_t& destination, - const boost::uint8_t& prefix, - const uhd::device_addr_t& args, - boost::uint32_t& sid) -{ +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; - - sid_config_t config; - config.router_addr_there = X300_DEVICE_THERE; - config.dst_prefix = prefix; - config.router_dst_there = destination; - - // Choose the endpoint based on the destination - size_t endpoint = 0; - if (destination == X300_XB_DST_R1) { - if (mb.eth_conns.size() > 1) - endpoint = 1; - } - - // Decide on the IP/Interface pair based on the endpoint index - std::string interface_addr = mb.eth_conns[endpoint].addr; - config.iface_index = mb.eth_conns[endpoint].type; - - sid = this->allocate_sid(mb, config); - static const uhd::device_addr_t DEFAULT_XPORT_ARGS; - - const uhd::device_addr_t& xport_args = - (prefix != X300_RADIO_DEST_PREFIX_CTRL) ? args : DEFAULT_XPORT_ARGS; - + const uhd::device_addr_t& xport_args = (xport_type == CTRL) ? uhd::device_addr_t() : args; zero_copy_xport_params default_buff_args; + both_xports_t xports; if (mb.xport_path == "nirio") { - default_buff_args.send_frame_size = - (prefix == X300_RADIO_DEST_PREFIX_TX) - ? X300_PCIE_TX_DATA_FRAME_SIZE - : X300_PCIE_MSG_FRAME_SIZE; - - default_buff_args.recv_frame_size = - (prefix == X300_RADIO_DEST_PREFIX_RX) - ? X300_PCIE_RX_DATA_FRAME_SIZE - : X300_PCIE_MSG_FRAME_SIZE; - - default_buff_args.num_send_frames = - (prefix == X300_RADIO_DEST_PREFIX_TX) - ? X300_PCIE_DATA_NUM_FRAMES - : X300_PCIE_MSG_NUM_FRAMES; - - default_buff_args.num_recv_frames = - (prefix == X300_RADIO_DEST_PREFIX_RX) - ? X300_PCIE_DATA_NUM_FRAMES - : X300_PCIE_MSG_NUM_FRAMES; - - xports.recv = nirio_zero_copy::make( - mb.rio_fpga_interface, - get_pcie_dma_channel(destination, prefix), - default_buff_args, - xport_args); + xports.send_sid = this->allocate_sid(mb, address, X300_SRC_ADDR0, X300_XB_DST_PCI); + xports.recv_sid = xports.send_sid.reversed(); + + uint32_t dma_channel_num = allocate_pcie_dma_chan(xports.send_sid, xport_type); + if (xport_type == CTRL) { + //Transport for control stream + if (_ctrl_dma_xport.get() == NULL) { + //One underlying DMA channel will handle + //all control traffic + zero_copy_xport_params ctrl_buff_args; + ctrl_buff_args.send_frame_size = X300_PCIE_MSG_FRAME_SIZE; + ctrl_buff_args.recv_frame_size = X300_PCIE_MSG_FRAME_SIZE; + ctrl_buff_args.num_send_frames = X300_PCIE_MSG_NUM_FRAMES * X300_PCIE_MAX_MUXED_XPORTS; + ctrl_buff_args.num_recv_frames = X300_PCIE_MSG_NUM_FRAMES * X300_PCIE_MAX_MUXED_XPORTS; + + zero_copy_if::sptr base_xport = nirio_zero_copy::make( + mb.rio_fpga_interface, dma_channel_num, + ctrl_buff_args, uhd::device_addr_t()); + _ctrl_dma_xport = muxed_zero_copy_if::make(base_xport, extract_sid_from_pkt, X300_PCIE_MAX_MUXED_XPORTS); + } + //Create a virtual control transport + xports.recv = _ctrl_dma_xport->make_stream(xports.recv_sid.get_dst()); + } else { + //Transport for data stream + default_buff_args.send_frame_size = + (xport_type == TX_DATA) + ? X300_PCIE_TX_DATA_FRAME_SIZE + : X300_PCIE_MSG_FRAME_SIZE; + + default_buff_args.recv_frame_size = + (xport_type == RX_DATA) + ? X300_PCIE_RX_DATA_FRAME_SIZE + : X300_PCIE_MSG_FRAME_SIZE; + + default_buff_args.num_send_frames = + (xport_type == TX_DATA) + ? X300_PCIE_DATA_NUM_FRAMES + : X300_PCIE_MSG_NUM_FRAMES; + + default_buff_args.num_recv_frames = + (xport_type == RX_DATA) + ? X300_PCIE_DATA_NUM_FRAMES + : X300_PCIE_MSG_NUM_FRAMES; + + xports.recv = nirio_zero_copy::make( + mb.rio_fpga_interface, dma_channel_num, + default_buff_args, xport_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; + mb.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(); } else if (mb.xport_path == "eth") { + // Decide on the IP/Interface pair based on the endpoint index + std::string interface_addr = mb.eth_conns[mb.next_src_addr].addr; + const uint32_t xbar_src_addr = + mb.next_src_addr==0 ? X300_SRC_ADDR0 : X300_SRC_ADDR1; + const uint32_t xbar_src_dst = + mb.eth_conns[mb.next_src_addr].type==X300_IFACE_ETH0 ? X300_XB_DST_E0 : X300_XB_DST_E1; + mb.next_src_addr = (mb.next_src_addr + 1) % mb.eth_conns.size(); + + xports.send_sid = this->allocate_sid(mb, address, xbar_src_addr, xbar_src_dst); + xports.recv_sid = xports.send_sid.reversed(); /* Determine what the recommended frame size is for this * connection type.*/ @@ -1359,24 +1126,22 @@ x300_impl::both_xports_t x300_impl::make_transport( fs_path mboard_path = fs_path("/mboards/"+boost::lexical_cast<std::string>(mb_index) / "link_max_rate"); - UHD_ASSERT_THROW(mb.loaded_fpga_image.size() >= 2); - - if (mb.loaded_fpga_image.substr(0,2) == "HG") { + if (mb.loaded_fpga_image == "HG") { size_t max_link_rate = 0; - if (config.iface_index == X300_IFACE_ETH0) { + if (xbar_src_dst == X300_XB_DST_E0) { eth_data_rec_frame_size = X300_1GE_DATA_FRAME_MAX_SIZE; max_link_rate += X300_MAX_RATE_1GIGE; - } else if (config.iface_index == X300_IFACE_ETH1) { + } else if (xbar_src_dst == X300_XB_DST_E1) { eth_data_rec_frame_size = X300_10GE_DATA_FRAME_MAX_SIZE; max_link_rate += X300_MAX_RATE_10GIGE; } _tree->access<double>(mboard_path).set(max_link_rate); - } else if (mb.loaded_fpga_image.substr(0,2) == "XG" or mb.loaded_fpga_image.substr(0,2) == "XA") { + } else if (mb.loaded_fpga_image == "XG" or mb.loaded_fpga_image == "XA") { eth_data_rec_frame_size = X300_10GE_DATA_FRAME_MAX_SIZE; size_t max_link_rate = X300_MAX_RATE_10GIGE; max_link_rate *= mb.eth_conns.size(); _tree->access<double>(mboard_path).set(max_link_rate); - } else if (mb.loaded_fpga_image.substr(0,2) == "HA") { + } else if (mb.loaded_fpga_image == "HA") { eth_data_rec_frame_size = X300_1GE_DATA_FRAME_MAX_SIZE; size_t max_link_rate = X300_MAX_RATE_1GIGE; max_link_rate *= mb.eth_conns.size(); @@ -1414,22 +1179,22 @@ x300_impl::both_xports_t x300_impl::make_transport( // Make sure frame sizes do not exceed the max available value supported by UHD default_buff_args.send_frame_size = - (prefix == X300_RADIO_DEST_PREFIX_TX) + (xport_type == TX_DATA) ? std::min(system_max_send_frame_size, X300_10GE_DATA_FRAME_MAX_SIZE) : std::min(system_max_send_frame_size, X300_ETH_MSG_FRAME_SIZE); default_buff_args.recv_frame_size = - (prefix == X300_RADIO_DEST_PREFIX_RX) + (xport_type == RX_DATA) ? std::min(system_max_recv_frame_size, X300_10GE_DATA_FRAME_MAX_SIZE) : std::min(system_max_recv_frame_size, X300_ETH_MSG_FRAME_SIZE); default_buff_args.num_send_frames = - (prefix == X300_RADIO_DEST_PREFIX_TX) + (xport_type == TX_DATA) ? X300_ETH_DATA_NUM_FRAMES : X300_ETH_MSG_NUM_FRAMES; default_buff_args.num_recv_frames = - (prefix == X300_RADIO_DEST_PREFIX_RX) + (xport_type == RX_DATA) ? X300_ETH_DATA_NUM_FRAMES : X300_ETH_MSG_NUM_FRAMES; @@ -1445,7 +1210,7 @@ x300_impl::both_xports_t x300_impl::make_transport( // Create a threaded transport for the receive chain only // Note that this shouldn't affect PCIe - if (prefix == X300_RADIO_DEST_PREFIX_RX) { + if (xport_type == RX_DATA) { xports.recv = zero_copy_recv_offload::make( xports.recv, X300_THREAD_BUFFER_TIMEOUT @@ -1465,12 +1230,12 @@ x300_impl::both_xports_t x300_impl::make_transport( //send a mini packet with SID into the ZPU //ZPU will reprogram the ethernet framer UHD_LOG << "programming packet for new xport on " - << mb.get_pri_eth().addr << std::hex << "sid 0x" << sid << std::dec << std::endl; + << interface_addr << " sid " << xports.send_sid << std::endl; //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<boost::uint32_t *>()[0] = 0; //eth dispatch looks for != 0 - buff->cast<boost::uint32_t *>()[1] = uhd::htonx(sid); + buff->cast<boost::uint32_t *>()[1] = uhd::htonx(xports.send_sid.get()); buff->commit(8); buff.reset(); @@ -1487,60 +1252,27 @@ x300_impl::both_xports_t x300_impl::make_transport( } -boost::uint32_t x300_impl::allocate_sid(mboard_members_t &mb, const sid_config_t &config) -{ - const std::string &xport_path = mb.xport_path; - const boost::uint32_t stream = (config.dst_prefix | (config.router_dst_there << 2)) & 0xff; - boost::uint8_t sid_ret_addr = X300_SRC_ADDR_ETH0; - boost::uint32_t xb_port = X300_XB_DST_E0; - - // Use the interface index to decide on the ethernet port - if (config.iface_index == X300_IFACE_ETH1) { - sid_ret_addr = X300_SRC_ADDR_ETH1; - xb_port = X300_XB_DST_E1; - } - - // Ensure we choose the right port in the case of NI-RIO - if (xport_path == "nirio") { - xb_port = X300_XB_DST_PCI; - } - - int int_sid_ret_addr = int(sid_ret_addr); - - const boost::uint32_t sid = 0 - | (sid_ret_addr << 24) - | (_sid_framer << 16) - | (config.router_addr_there << 8) - | (stream << 0) - ; - - UHD_LOG << std::hex - << " sid 0x" << sid - << " return address 0x" << int_sid_ret_addr - << " framer 0x" << _sid_framer - << " stream 0x" << stream - << " router_dst_there 0x" << int(config.router_dst_there) - << " router_addr_there 0x" << int(config.router_addr_there) - << std::dec << std::endl; +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); + // 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), config.router_addr_there); + 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 does 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 + (stream)), config.router_dst_there); + // 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 + (sid_ret_addr)), xb_port); - - if (xport_path == "nirio") { - boost::uint32_t router_config_word = ((_sid_framer & 0xff) << 16) | //Return SID - get_pcie_dma_channel(config.router_dst_there, config.dst_prefix); //Dest - mb.rio_fpga_interface->get_kernel_proxy()->poke(PCIE_ROUTER_REG(0), router_config_word); - } + mb.zpu_ctrl->poke32(SR_ADDR(SETXB_BASE, 0 + src_addr), src_dst); - UHD_LOG << std::hex - << "done router config for sid 0x" << sid - << std::dec << std::endl; + UHD_LOG << "done router config for sid " << sid << std::endl; //increment for next setup _sid_framer++; @@ -1548,56 +1280,9 @@ boost::uint32_t x300_impl::allocate_sid(mboard_members_t &mb, const sid_config_t return sid; } -void x300_impl::update_atr_leds(gpio_atr_3000::sptr leds, const std::string &rx_ant) -{ - const bool is_txrx = (rx_ant == "TX/RX"); - const int rx_led = (1 << 2); - const int tx_led = (1 << 1); - const int txrx_led = (1 << 0); - leds->set_atr_reg(ATR_REG_IDLE, 0); - leds->set_atr_reg(ATR_REG_RX_ONLY, is_txrx? txrx_led : rx_led); - leds->set_atr_reg(ATR_REG_TX_ONLY, tx_led); - leds->set_atr_reg(ATR_REG_FULL_DUPLEX, rx_led | tx_led); -} - -void x300_impl::set_tick_rate(mboard_members_t &mb, const double rate) -{ - BOOST_FOREACH(radio_perifs_t &perif, mb.radio_perifs) { - perif.ctrl->set_tick_rate(rate); - perif.time64->set_tick_rate(rate); - perif.framer->set_tick_rate(rate); - perif.ddc->set_tick_rate(rate); - perif.duc->set_tick_rate(rate); - } -} - -void x300_impl::register_loopback_self_test(wb_iface::sptr iface) -{ - bool test_fail = false; - UHD_MSG(status) << "Performing register loopback test... " << std::flush; - size_t hash = size_t(time(NULL)); - for (size_t i = 0; i < 100; i++) - { - boost::hash_combine(hash, i); - iface->poke32(radio::sr_addr(radio::TEST), boost::uint32_t(hash)); - test_fail = iface->peek32(radio::RB32_TEST) != boost::uint32_t(hash); - if (test_fail) break; //exit loop on any failure - } - UHD_MSG(status) << ((test_fail)? " fail" : "pass") << std::endl; -} - -void x300_impl::radio_loopback(wb_iface::sptr iface, const bool on) -{ - iface->poke32(radio::sr_addr(radio::LOOPBACK), (on ? 0x1 : 0x0)); - UHD_MSG(status) << ((on)? "Radio Loopback On" : "Radio Loopback Off") << std::endl; -} - - - /*********************************************************************** * 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); @@ -1670,18 +1355,8 @@ void x300_impl::update_clock_source(mboard_members_t &mb, const std::string &sou } // Reset ADCs and DACs - for (size_t r = 0; r < mboard_members_t::NUM_RADIOS; r++) { - radio_perifs_t &perif = mb.radio_perifs[r]; - if (perif.regmap && r==0) { //ADC/DAC reset lines only exist in Radio0 - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1); - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0); - perif.regmap->misc_outs_reg.flush(); - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 0); - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 1); - perif.regmap->misc_outs_reg.flush(); - } - if (perif.adc) perif.adc->reset(); - if (perif.dac) perif.dac->reset(); + BOOST_FOREACH(rfnoc::x300_radio_ctrl_impl::sptr r, mb.radios) { + r->reset_codec(); } } @@ -1711,8 +1386,12 @@ void x300_impl::update_time_source(mboard_members_t &mb, const std::string &sour void x300_impl::sync_times(mboard_members_t &mb, const uhd::time_spec_t& t) { - BOOST_FOREACH(radio_perifs_t &perif, mb.radio_perifs) - perif.time64->set_time_sync(t); + std::vector<rfnoc::block_id_t> radio_ids = find_blocks<rfnoc::x300_radio_ctrl_impl>("Radio"); + BOOST_FOREACH(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); } @@ -1911,7 +1590,10 @@ void x300_impl::check_fpga_compat(const fs_path &mb_path, const mboard_members_t : "resource") % members.get_pri_eth().addr); - throw uhd::runtime_error(str(boost::format( + std::cout << "=========================================================" << std::endl; + std::cout << "Warning:" << std::endl; + //throw uhd::runtime_error(str(boost::format( + std::cout << (str(boost::format( "Expected FPGA compatibility number %d, but got %d:\n" "The FPGA image on your device is not compatible with this host code build.\n" "Download the appropriate FPGA images for this version of UHD.\n" @@ -1923,6 +1605,7 @@ void x300_impl::check_fpga_compat(const fs_path &mb_path, const mboard_members_t ) % int(X300_FPGA_COMPAT_MAJOR) % compat_major % print_utility_error("uhd_images_downloader.py") % image_loader_cmd)); + std::cout << "=========================================================" << std::endl; } _tree->create<std::string>(mb_path / "fpga_version").set(str(boost::format("%u.%u") % compat_major % compat_minor)); diff --git a/host/lib/usrp/x300/x300_impl.hpp b/host/lib/usrp/x300/x300_impl.hpp index b5bbbe136..d491e2bc0 100644 --- a/host/lib/usrp/x300/x300_impl.hpp +++ b/host/lib/usrp/x300/x300_impl.hpp @@ -19,55 +19,36 @@ #define INCLUDED_X300_IMPL_HPP #include <uhd/property_tree.hpp> -#include <uhd/device.hpp> +#include "../device3/device3_impl.hpp" #include <uhd/usrp/mboard_eeprom.hpp> -#include <uhd/usrp/dboard_manager.hpp> -#include <uhd/usrp/dboard_eeprom.hpp> #include <uhd/usrp/subdev_spec.hpp> #include <uhd/types/sensors.hpp> +#include "x300_radio_ctrl_impl.hpp" #include "x300_clock_ctrl.hpp" #include "x300_fw_common.h" #include <uhd/transport/udp_simple.hpp> //mtu -#include <uhd/utils/tasks.hpp> -#include "spi_core_3000.hpp" -#include "x300_adc_ctrl.hpp" -#include "x300_dac_ctrl.hpp" -#include "rx_vita_core_3000.hpp" -#include "tx_vita_core_3000.hpp" -#include "time_core_3000.hpp" -#include "rx_dsp_core_3000.hpp" -#include "tx_dsp_core_3000.hpp" #include "i2c_core_100_wb32.hpp" -#include "radio_ctrl_core_3000.hpp" -#include "rx_frontend_core_200.hpp" -#include "tx_frontend_core_200.hpp" -#include "gpio_atr_3000.hpp" -#include "dma_fifo_core_3000.hpp" #include <boost/weak_ptr.hpp> #include <uhd/usrp/gps_ctrl.hpp> -#include <uhd/usrp/mboard_eeprom.hpp> -#include <uhd/transport/bounded_buffer.hpp> #include <uhd/transport/nirio/niusrprio_session.h> #include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/transport/muxed_zero_copy_if.hpp> #include "recv_packet_demuxer_3000.hpp" #include "x300_regs.hpp" +///////////// RFNOC ///////////////////// +#include <uhd/rfnoc/block_ctrl.hpp> +///////////// RFNOC ///////////////////// +#include <boost/dynamic_bitset.hpp> static const std::string X300_FW_FILE_NAME = "usrp_x300_fw.bin"; +static const std::string X300_DEFAULT_CLOCK_SOURCE = "internal"; static const double X300_DEFAULT_TICK_RATE = 200e6; //Hz static const double X300_DEFAULT_DBOARD_CLK_RATE = 50e6; //Hz static const double X300_BUS_CLOCK_RATE = 166.666667e6; //Hz -static const size_t X300_TX_HW_BUFF_SIZE_SRAM = 520*1024; //512K SRAM buffer + 8K 2Clk FIFO -static const size_t X300_TX_FC_RESPONSE_FREQ_SRAM = 8; //per flow-control window -static const size_t X300_TX_HW_BUFF_SIZE_DRAM = 128*1024; -static const size_t X300_TX_FC_RESPONSE_FREQ_DRAM = 32; -static const boost::uint32_t X300_DRAM_FIFO_SIZE = 32*1024*1024; - static const size_t X300_RX_SW_BUFF_SIZE_ETH = 0x2000000;//32MiB For an ~8k frame size any size >32MiB is just wasted buffer space static const size_t X300_RX_SW_BUFF_SIZE_ETH_MACOS = 0x100000; //1Mib -static const double X300_RX_SW_BUFF_FULL_FACTOR = 0.90; //Buffer should ideally be 90% full. -static const size_t X300_RX_FC_REQUEST_FREQ = 32; //per flow-control window //The FIFO closest to the DMA controller is 1023 elements deep for RX and 1029 elements deep for TX //where an element is 8 bytes. For best throughput ensure that the data frame fits in these buffers. @@ -77,6 +58,8 @@ static const size_t X300_PCIE_TX_DATA_FRAME_SIZE = 8192; //bytes static const size_t X300_PCIE_DATA_NUM_FRAMES = 2048; static const size_t X300_PCIE_MSG_FRAME_SIZE = 256; //bytes static const size_t X300_PCIE_MSG_NUM_FRAMES = 64; +static const size_t X300_PCIE_MAX_CHANNELS = 6; +static const size_t X300_PCIE_MAX_MUXED_XPORTS = 32; static const size_t X300_10GE_DATA_FRAME_MAX_SIZE = 8000; //bytes static const size_t X300_1GE_DATA_FRAME_MAX_SIZE = 1472; //bytes @@ -88,46 +71,22 @@ static const size_t X300_ETH_MSG_NUM_FRAMES = 64; static const size_t X300_ETH_DATA_NUM_FRAMES = 32; static const double X300_DEFAULT_SYSREF_RATE = 10e6; -static const size_t X300_TX_MAX_HDR_LEN = // bytes - sizeof(boost::uint32_t) // Header - + sizeof(uhd::transport::vrt::if_packet_info_t().sid) // SID - + sizeof(uhd::transport::vrt::if_packet_info_t().tsf); // Timestamp -static const size_t X300_RX_MAX_HDR_LEN = // bytes - sizeof(boost::uint32_t) // Header - + sizeof(uhd::transport::vrt::if_packet_info_t().sid) // SID - + sizeof(uhd::transport::vrt::if_packet_info_t().tsf); // Timestamp - static const size_t X300_MAX_RATE_PCIE = 800000000; // bytes/s static const size_t X300_MAX_RATE_10GIGE = 800000000; // bytes/s static const size_t X300_MAX_RATE_1GIGE = 100000000; // bytes/s #define X300_RADIO_DEST_PREFIX_TX 0 -#define X300_RADIO_DEST_PREFIX_CTRL 1 -#define X300_RADIO_DEST_PREFIX_RX 2 - -#define X300_XB_DST_E0 0 -#define X300_XB_DST_E1 1 -#define X300_XB_DST_R0 2 // Radio 0 -> Slot A -#define X300_XB_DST_R1 3 // Radio 1 -> Slot B -#define X300_XB_DST_CE0 4 -#define X300_XB_DST_CE1 5 -#define X300_XB_DST_CE2 5 -#define X300_XB_DST_PCI 7 - -#define X300_DEVICE_THERE 2 -#define X300_SRC_ADDR_ETH0 0 -#define X300_SRC_ADDR_ETH1 1 - -//eeprom addrs for various boards -enum -{ - X300_DB0_RX_EEPROM = 0x5, - X300_DB0_TX_EEPROM = 0x4, - X300_DB0_GDB_EEPROM = 0x1, - X300_DB1_RX_EEPROM = 0x7, - X300_DB1_TX_EEPROM = 0x6, - X300_DB1_GDB_EEPROM = 0x3, -}; + +#define X300_XB_DST_E0 0 +#define X300_XB_DST_E1 1 +#define X300_XB_DST_PCI 2 +#define X300_XB_DST_R0 3 // Radio 0 -> Slot A +#define X300_XB_DST_R1 4 // Radio 1 -> Slot B +#define X300_XB_DST_CE0 5 + +#define X300_SRC_ADDR0 0 +#define X300_SRC_ADDR1 1 +#define X300_DST_ADDR 2 // Ethernet ports enum x300_eth_iface_t @@ -143,22 +102,7 @@ struct x300_eth_conn_t x300_eth_iface_t type; }; -struct x300_dboard_iface_config_t -{ - uhd::usrp::gpio_atr::db_gpio_atr_3000::sptr gpio; - spi_core_3000::sptr spi; - size_t rx_spi_slaveno; - size_t tx_spi_slaveno; - i2c_core_100_wb32::sptr i2c; - x300_clock_ctrl::sptr clock; - rx_dsp_core_3000::sptr rx_dsp; - x300_clock_which_t which_rx_clk; - x300_clock_which_t which_tx_clk; - boost::uint8_t dboard_slot; - uhd::timed_wb_iface::sptr cmd_time_ctrl; -}; -uhd::usrp::dboard_iface::sptr x300_make_dboard_iface(const x300_dboard_iface_config_t &); uhd::uart_iface::sptr x300_make_uart_iface(uhd::wb_iface::sptr iface); uhd::wb_iface::sptr x300_make_ctrl_iface_enet(uhd::transport::udp_simple::sptr udp, bool enable_errors = true); @@ -166,22 +110,14 @@ uhd::wb_iface::sptr x300_make_ctrl_iface_pcie(uhd::niusrprio::niriok_proxy::sptr uhd::device_addrs_t x300_find(const uhd::device_addr_t &hint_); -class x300_impl : public uhd::device +class x300_impl : public uhd::usrp::device3_impl { public: - typedef uhd::transport::bounded_buffer<uhd::async_metadata_t> async_md_type; x300_impl(const uhd::device_addr_t &); void setup_mb(const size_t which, const uhd::device_addr_t &); ~x300_impl(void); - //the io interface - uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &); - uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &); - - //support old async call - bool recv_async_msg(uhd::async_metadata_t &, double); - // used by x300_find_with_addr to find X300 devices. static boost::mutex claimer_mutex; //All claims and checks in this process are serialized static bool is_claimed(uhd::wb_iface::sptr); @@ -192,43 +128,26 @@ public: static x300_mboard_t get_mb_type_from_pcie(const std::string& resource, const std::string& rpc_port); static x300_mboard_t get_mb_type_from_eeprom(const uhd::usrp::mboard_eeprom_t& mb_eeprom); -private: - boost::shared_ptr<async_md_type> _async_md; +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 + ); - //perifs in the radio core - struct radio_perifs_t - { - //Interfaces - radio_ctrl_core_3000::sptr ctrl; - spi_core_3000::sptr spi; - x300_adc_ctrl::sptr adc; - x300_dac_ctrl::sptr dac; - time_core_3000::sptr time64; - rx_vita_core_3000::sptr framer; - rx_dsp_core_3000::sptr ddc; - tx_vita_core_3000::sptr deframer; - tx_dsp_core_3000::sptr duc; - uhd::usrp::gpio_atr::gpio_atr_3000::sptr leds; - rx_frontend_core_200::sptr rx_fe; - tx_frontend_core_200::sptr tx_fe; - //Registers - uhd::usrp::x300::radio_regmap_t::sptr regmap; - }; - - //overflow recovery impl - void handle_overflow(radio_perifs_t &perif, boost::weak_ptr<uhd::rx_streamer> streamer); +private: //vector of member objects per motherboard struct mboard_members_t { - uhd::dict<size_t, boost::weak_ptr<uhd::rx_streamer> > rx_streamers; - uhd::dict<size_t, boost::weak_ptr<uhd::tx_streamer> > tx_streamers; - bool initialization_done; uhd::task::sptr claimer_task; std::string xport_path; std::vector<x300_eth_conn_t> eth_conns; + size_t next_src_addr; // Discover the ethernet connections per motherboard void discover_eth(const uhd::usrp::mboard_eeprom_t mb_eeprom, @@ -250,24 +169,9 @@ private: spi_core_3000::sptr zpu_spi; i2c_core_100_wb32::sptr zpu_i2c; - //perifs in each radio - static const size_t NUM_RADIOS = 2; - radio_perifs_t radio_perifs[NUM_RADIOS]; //!< This is hardcoded s.t. radio_perifs[0] points to slot A and [1] to B - uhd::usrp::dboard_eeprom_t db_eeproms[8]; - //! Return the index of a radio component, given a slot name. This means DSPs, radio_perifs - size_t get_radio_index(const std::string &slot_name) { - UHD_ASSERT_THROW(slot_name == "A" or slot_name == "B"); - return slot_name == "A" ? 0 : 1; - } - - bool has_dram_buff; - dma_fifo_core_3000::sptr dram_buff_ctrl[NUM_RADIOS]; - - //other perifs on mboard x300_clock_ctrl::sptr clock; uhd::gps_ctrl::sptr gps; - uhd::usrp::gpio_atr::gpio_atr_3000::sptr fp_gpio; uhd::usrp::x300::fw_regmap_t::sptr fw_regmap; @@ -277,57 +181,25 @@ private: size_t hw_rev; std::string current_refclk_src; - uhd::soft_regmap_db_t::sptr regmap_db; + std::vector<uhd::rfnoc::x300_radio_ctrl_impl::sptr> radios; }; std::vector<mboard_members_t> _mb; //task for periodically reclaiming the device from others void claimer_loop(uhd::wb_iface::sptr); - boost::mutex _transport_setup_mutex; - - void register_loopback_self_test(uhd::wb_iface::sptr iface); - - void radio_loopback(uhd::wb_iface::sptr iface, const bool on); - - /*! \brief Initialize the radio component on a given slot. - * - * Call this function once per slot (A and B) and motherboard to initialize all the radio components. - * This will: - * - Reset and init DACs and ADCs - * - Setup controls for DAC, ADC, SPI and LEDs - * - Self test ADC - * - Sync DACs (for MIMO) - * - Initialize the property tree for control objects etc. (gain, rate...) - * - * \param mb_i Motherboard index - * \param slot_name Slot name (A or B). - */ - void setup_radio(const size_t, const std::string &slot_name, const uhd::device_addr_t &dev_addr); - size_t _sid_framer; - struct sid_config_t - { - boost::uint8_t router_addr_there; - boost::uint8_t dst_prefix; //2bits - boost::uint8_t router_dst_there; - x300_eth_iface_t iface_index; - }; - boost::uint32_t allocate_sid(mboard_members_t &mb, const sid_config_t &config); - struct both_xports_t - { - uhd::transport::zero_copy_if::sptr recv; - uhd::transport::zero_copy_if::sptr send; - size_t recv_buff_size; - size_t send_buff_size; - }; - both_xports_t make_transport( - const size_t mb_index, - const boost::uint8_t& destination, - const boost::uint8_t& prefix, - const uhd::device_addr_t& args, - boost::uint32_t& sid); + 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 + ); struct frame_size_t { @@ -343,6 +215,15 @@ private: */ frame_size_t determine_max_frame_size(const std::string &addr, const frame_size_t &user_mtu); + std::map<uint32_t, uint32_t> _dma_chan_pool; + uhd::transport::muxed_zero_copy_if::sptr _ctrl_dma_xport; + + /*! Allocate or return a previously allocated PCIe channel pair + * + * 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 xport_type_t xport_type); + //////////////////////////////////////////////////////////////////// // //Caching for transport interface re-use -- like sharing a DMA. @@ -366,26 +247,8 @@ private: uhd::dict<std::string, uhd::usrp::dboard_manager::sptr> _dboard_managers; - void set_rx_fe_corrections(const uhd::fs_path &mb_path, const std::string &fe_name, const double lo_freq); - void set_tx_fe_corrections(const uhd::fs_path &mb_path, const std::string &fe_name, const double lo_freq); bool _ignore_cal_file; - - /*! Update the IQ MUX settings for the radio peripheral according to given subdev spec. - * - * Also checks if the given subdev is valid for this device and updates the channel to DSP mapping. - * - * \param tx_rx "tx" or "rx", depending where you're setting the subdev spec - * \param mb_i Mainboard index number. - * \param spec Subdev spec - */ - void update_subdev_spec(const std::string &tx_rx, const size_t mb_i, const uhd::usrp::subdev_spec_t &spec); - - void set_tick_rate(mboard_members_t &, const double); - void update_tick_rate(mboard_members_t &, const double); - void update_rx_samp_rate(mboard_members_t&, const size_t, const double); - void update_tx_samp_rate(mboard_members_t&, const size_t, const double); - 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); @@ -403,19 +266,15 @@ private: void check_fw_compat(const uhd::fs_path &mb_path, uhd::wb_iface::sptr iface); void check_fpga_compat(const uhd::fs_path &mb_path, const mboard_members_t &members); - void update_atr_leds(uhd::usrp::gpio_atr::gpio_atr_3000::sptr, const std::string &ant); - - void self_cal_adc_capture_delay(mboard_members_t& mb, const size_t radio_i, bool print_status = false); - double self_cal_adc_xfer_delay(mboard_members_t& mb, bool apply_delay = false); - void self_test_adcs(mboard_members_t& mb, boost::uint32_t ramp_time_ms = 100); - - void extended_adc_test(mboard_members_t& mb, double duration_s); + /// 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); + uhd::endianness_t get_transport_endianness(size_t mb_index) { + return _mb[mb_index].if_pkt_is_big_endian ? uhd::ENDIANNESS_BIG : uhd::ENDIANNESS_LITTLE; + }; - //**PRECONDITION** - //This function assumes that all the VITA times in "radios" are synchronized - //to a common reference. Currently, this function is called in get_tx_stream - //which also has the same precondition. - static void synchronize_dacs(const std::vector<radio_perifs_t*>& mboards); + void post_streamer_hooks(uhd::direction_t dir); }; #endif /* INCLUDED_X300_IMPL_HPP */ +// vim: sw=4 expandtab: diff --git a/host/lib/usrp/x300/x300_io_impl.cpp b/host/lib/usrp/x300/x300_io_impl.cpp index 329405261..5314d1b9a 100644 --- a/host/lib/usrp/x300/x300_io_impl.cpp +++ b/host/lib/usrp/x300/x300_io_impl.cpp @@ -15,18 +15,19 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. // +#define DEVICE3_STREAMER + #include "x300_regs.hpp" #include "x300_impl.hpp" -#include "validate_subdev_spec.hpp" #include "../../transport/super_recv_packet_handler.hpp" #include "../../transport/super_send_packet_handler.hpp" #include <uhd/transport/nirio_zero_copy.hpp> #include "async_packet_handler.hpp" #include <uhd/transport/bounded_buffer.hpp> -#include <uhd/transport/chdr.hpp> #include <boost/bind.hpp> #include <uhd/utils/tasks.hpp> #include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> #include <boost/foreach.hpp> #include <boost/make_shared.hpp> @@ -35,589 +36,65 @@ using namespace uhd::usrp; using namespace uhd::transport; /*********************************************************************** - * update streamer rates + * Hooks for get_tx_stream() and get_rx_stream() **********************************************************************/ -void x300_impl::update_tick_rate(mboard_members_t &mb, const double rate) +device_addr_t x300_impl::get_rx_hints(size_t mb_index) { - BOOST_FOREACH(const size_t &dspno, mb.rx_streamers.keys()) - { - boost::shared_ptr<sph::recv_packet_streamer> my_streamer = - boost::dynamic_pointer_cast<sph::recv_packet_streamer>(mb.rx_streamers[dspno].lock()); - if (my_streamer) my_streamer->set_tick_rate(rate); - } - BOOST_FOREACH(const size_t &dspno, mb.tx_streamers.keys()) + device_addr_t rx_hints = _mb[mb_index].recv_args; + // (default to a large recv buff) + if (not rx_hints.has_key("recv_buff_size")) { - boost::shared_ptr<sph::send_packet_streamer> my_streamer = - boost::dynamic_pointer_cast<sph::send_packet_streamer>(mb.tx_streamers[dspno].lock()); - if (my_streamer) my_streamer->set_tick_rate(rate); - } -} - -void x300_impl::update_rx_samp_rate(mboard_members_t &mb, const size_t dspno, const double rate) -{ - if (not mb.rx_streamers.has_key(dspno)) return; - boost::shared_ptr<sph::recv_packet_streamer> my_streamer = - boost::dynamic_pointer_cast<sph::recv_packet_streamer>(mb.rx_streamers[dspno].lock()); - if (not my_streamer) return; - my_streamer->set_samp_rate(rate); - const double adj = mb.radio_perifs[dspno].ddc->get_scaling_adjustment(); - my_streamer->set_scale_factor(adj); -} - -void x300_impl::update_tx_samp_rate(mboard_members_t &mb, const size_t dspno, const double rate) -{ - if (not mb.tx_streamers.has_key(dspno)) return; - boost::shared_ptr<sph::send_packet_streamer> my_streamer = - boost::dynamic_pointer_cast<sph::send_packet_streamer>(mb.tx_streamers[dspno].lock()); - if (not my_streamer) return; - my_streamer->set_samp_rate(rate); - const double adj = mb.radio_perifs[dspno].duc->get_scaling_adjustment(); - my_streamer->set_scale_factor(adj); -} - -/*********************************************************************** - * Setup dboard muxing for IQ - **********************************************************************/ -void x300_impl::update_subdev_spec(const std::string &tx_rx, const size_t mb_i, const subdev_spec_t &spec) -{ - UHD_ASSERT_THROW(tx_rx == "tx" or tx_rx == "rx"); - UHD_ASSERT_THROW(mb_i < _mb.size()); - const std::string mb_name = boost::lexical_cast<std::string>(mb_i); - fs_path mb_root = "/mboards/" + mb_name; - - //sanity checking - validate_subdev_spec(_tree, spec, tx_rx, mb_name); - UHD_ASSERT_THROW(spec.size() <= 2); - if (spec.size() == 1) { - UHD_ASSERT_THROW(spec[0].db_name == "A" || spec[0].db_name == "B"); - } - else if (spec.size() == 2) { - UHD_ASSERT_THROW( - (spec[0].db_name == "A" && spec[1].db_name == "B") || - (spec[0].db_name == "B" && spec[1].db_name == "A") - ); - } - - std::vector<size_t> chan_to_dsp_map(spec.size(), 0); - // setup mux for this spec - for (size_t i = 0; i < spec.size(); i++) - { - const int radio_idx = _mb[mb_i].get_radio_index(spec[i].db_name); - chan_to_dsp_map[i] = radio_idx; - - //extract connection - const fs_path fe_path(mb_root / "dboards" / spec[i].db_name / (tx_rx + "_frontends") / spec[i].sd_name); - const std::string conn = _tree->access<std::string>(fe_path / "connection").get(); - if (tx_rx == "tx") { - //swap condition - _mb[mb_i].radio_perifs[radio_idx].tx_fe->set_mux(conn); - } else { - double if_freq = (_tree->exists(fe_path / "if_freq/value")) ? - _tree->access<double>(fe_path / "if_freq/value").get() : 0.0; - _mb[mb_i].radio_perifs[radio_idx].ddc->set_mux(usrp::fe_connection_t(conn, if_freq)); - _mb[mb_i].radio_perifs[radio_idx].rx_fe->set_mux(false); + if (_mb[mb_index].xport_path != "nirio") { + //For the ethernet transport, the buffer has to be set before creating + //the transport because it is independent of the frame size and # frames + //For nirio, the buffer size is not configurable by the user + #if defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD) + //limit buffer resize on macos or it will error + rx_hints["recv_buff_size"] = boost::lexical_cast<std::string>(X300_RX_SW_BUFF_SIZE_ETH_MACOS); + #elif defined(UHD_PLATFORM_LINUX) || defined(UHD_PLATFORM_WIN32) + //set to half-a-second of buffering at max rate + rx_hints["recv_buff_size"] = boost::lexical_cast<std::string>(X300_RX_SW_BUFF_SIZE_ETH); + #endif } } - - _tree->access<std::vector<size_t> >(mb_root / (tx_rx + "_chan_dsp_mapping")).set(chan_to_dsp_map); + return rx_hints; } -/*********************************************************************** - * RX flow control handler - **********************************************************************/ -static size_t get_rx_flow_control_window(size_t frame_size, size_t sw_buff_size, const device_addr_t& rx_args) +device_addr_t x300_impl::get_tx_hints(size_t mb_index) { - double fullness_factor = rx_args.cast<double>("recv_buff_fullness", X300_RX_SW_BUFF_FULL_FACTOR); - - if (fullness_factor < 0.01 || fullness_factor > 1) { - throw uhd::value_error("recv_buff_fullness must be between 0.01 and 1 inclusive (1% to 100%)"); + device_addr_t tx_hints = _mb[mb_index].send_args; + if (_mb[mb_index].xport_path != "nirio") { + tx_hints["bpp"] = boost::lexical_cast<std::string>(X300_1GE_DATA_FRAME_MAX_SIZE); } - - size_t window_in_pkts = (static_cast<size_t>(sw_buff_size * fullness_factor) / frame_size); - if (window_in_pkts == 0) { - throw uhd::value_error("recv_buff_size must be larger than the recv_frame_size."); - } - return window_in_pkts; + return tx_hints; } -static void handle_rx_flowctrl(const boost::uint32_t sid, zero_copy_if::sptr xport, bool big_endian, boost::shared_ptr<boost::uint32_t> seq32_state, const size_t last_seq) +void x300_impl::post_streamer_hooks(direction_t dir) { - managed_send_buffer::sptr buff = xport->get_send_buff(0.0); - if (not buff) - { - throw uhd::runtime_error("handle_rx_flowctrl timed out getting a send buffer"); - } - boost::uint32_t *pkt = buff->cast<boost::uint32_t *>(); - - //recover seq32 - boost::uint32_t &seq32 = *seq32_state; - const size_t seq12 = seq32 & 0xfff; - if (last_seq < seq12) seq32 += (1 << 12); - seq32 &= ~0xfff; - seq32 |= last_seq; - - //load packet info - vrt::if_packet_info_t packet_info; - packet_info.packet_type = vrt::if_packet_info_t::PACKET_TYPE_CONTEXT; - packet_info.num_payload_words32 = 2; - packet_info.num_payload_bytes = packet_info.num_payload_words32*sizeof(boost::uint32_t); - packet_info.packet_count = seq32; - packet_info.sob = false; - packet_info.eob = false; - packet_info.sid = sid; - packet_info.has_sid = true; - packet_info.has_cid = false; - packet_info.has_tsi = false; - packet_info.has_tsf = false; - packet_info.has_tlr = false; - - //load header - if (big_endian) - vrt::chdr::if_hdr_pack_be(pkt, packet_info); - else - vrt::chdr::if_hdr_pack_le(pkt, packet_info); - - //load payload - pkt[packet_info.num_header_words32+0] = uhd::htonx<boost::uint32_t>(0); - pkt[packet_info.num_header_words32+1] = uhd::htonx<boost::uint32_t>(seq32); - - //send the buffer over the interface - buff->commit(sizeof(boost::uint32_t)*(packet_info.num_packet_words32)); -} - - -/*********************************************************************** - * TX flow control handler - **********************************************************************/ -struct x300_tx_fc_guts_t -{ - x300_tx_fc_guts_t(void): - stream_channel(0), - device_channel(0), - last_seq_out(0), - last_seq_ack(0), - seq_queue(1){} - size_t stream_channel; - size_t device_channel; - size_t last_seq_out; - size_t last_seq_ack; - bounded_buffer<size_t> seq_queue; - boost::shared_ptr<x300_impl::async_md_type> async_queue; - boost::shared_ptr<x300_impl::async_md_type> old_async_queue; -}; - -#define X300_ASYNC_EVENT_CODE_FLOW_CTRL 0 - -/*! - * If the return value of this function is F, the last tx'd packet - * has index N and the last ack'd packet has index M, the amount of - * FC credit we have is C = F + M - N (i.e. we can send C more packets - * before getting another ack). - */ -static size_t get_tx_flow_control_window(size_t frame_size, const bool dram_buff, const device_addr_t& tx_args) -{ - double default_buff_size = dram_buff ? X300_TX_HW_BUFF_SIZE_DRAM : X300_TX_HW_BUFF_SIZE_SRAM; - double hw_buff_size = tx_args.cast<double>("send_buff_size", default_buff_size); - size_t window_in_pkts = (static_cast<size_t>(hw_buff_size) / frame_size); - if (window_in_pkts == 0) { - throw uhd::value_error("send_buff_size must be larger than the send_frame_size."); - } - return window_in_pkts; -} - -static void handle_tx_async_msgs(boost::shared_ptr<x300_tx_fc_guts_t> guts, zero_copy_if::sptr xport, bool big_endian, x300_clock_ctrl::sptr clock) -{ - managed_recv_buffer::sptr buff = xport->get_recv_buff(); - if (not buff) return; - - //extract packet info - vrt::if_packet_info_t if_packet_info; - if_packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); - const boost::uint32_t *packet_buff = buff->cast<const boost::uint32_t *>(); - - //unpacking can fail - boost::uint32_t (*endian_conv)(boost::uint32_t) = uhd::ntohx; - try - { - if (big_endian) - { - vrt::chdr::if_hdr_unpack_be(packet_buff, if_packet_info); - endian_conv = uhd::ntohx; - } - else - { - vrt::chdr::if_hdr_unpack_le(packet_buff, if_packet_info); - endian_conv = uhd::wtohx; - } - } - catch(const std::exception &ex) - { - UHD_MSG(error) << "Error parsing async message packet: " << ex.what() << std::endl; + if (dir != TX_DIRECTION) { return; } - //fill in the async metadata - async_metadata_t metadata; - load_metadata_from_buff( - endian_conv, metadata, if_packet_info, packet_buff, - clock->get_master_clock_rate(), guts->stream_channel); - - //The FC response and the burst ack are two indicators that the radio - //consumed packets. Use them to update the FC metadata - if (metadata.event_code == X300_ASYNC_EVENT_CODE_FLOW_CTRL or - metadata.event_code == async_metadata_t::EVENT_CODE_BURST_ACK - ) { - const size_t seq = metadata.user_payload[0]; - guts->seq_queue.push_with_pop_on_full(seq); - } - - //FC responses don't propagate up to the user so filter them here - if (metadata.event_code != X300_ASYNC_EVENT_CODE_FLOW_CTRL) { - guts->async_queue->push_with_pop_on_full(metadata); - metadata.channel = guts->device_channel; - guts->old_async_queue->push_with_pop_on_full(metadata); - standard_async_msg_prints(metadata); - } -} - -static managed_send_buffer::sptr get_tx_buff_with_flowctrl( - task::sptr /*holds ref*/, - boost::shared_ptr<x300_tx_fc_guts_t> guts, - zero_copy_if::sptr xport, - size_t fc_pkt_window, - const double timeout -){ - while (true) - { - // delta is the amount of FC credit we've used up - const size_t delta = (guts->last_seq_out & 0xfff) - (guts->last_seq_ack & 0xfff); - // If we want to send another packet, we must have FC credit left - if ((delta & 0xfff) < fc_pkt_window) break; - - // If credit is all used up, we check seq_queue for more. - const bool ok = guts->seq_queue.pop_with_timed_wait(guts->last_seq_ack, timeout); - if (not ok) return managed_send_buffer::sptr(); //timeout waiting for flow control - } - - managed_send_buffer::sptr buff = xport->get_send_buff(timeout); - if (buff) { - guts->last_seq_out++; //update seq, this will actually be a send - } - return buff; -} - -/*********************************************************************** - * Async Data - **********************************************************************/ -bool x300_impl::recv_async_msg( - async_metadata_t &async_metadata, double timeout -){ - return _async_md->pop_with_timed_wait(async_metadata, timeout); -} - -/*********************************************************************** - * Receive streamer - **********************************************************************/ -rx_streamer::sptr x300_impl::get_rx_stream(const uhd::stream_args_t &args_) -{ - boost::mutex::scoped_lock lock(_transport_setup_mutex); - stream_args_t args = args_; - - //setup defaults for unspecified values - if (not args.otw_format.empty() and args.otw_format != "sc16") - { - throw uhd::value_error("x300_impl::get_rx_stream only supports otw_format sc16"); - } - args.otw_format = "sc16"; - args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; - - boost::shared_ptr<sph::recv_packet_streamer> my_streamer; - for (size_t stream_i = 0; stream_i < args.channels.size(); stream_i++) - { - // Find the mainboard and subdev that corresponds to channel args.channels[stream_i] - const size_t chan = args.channels[stream_i]; - size_t mb_chan = chan, mb_index; - for (mb_index = 0; mb_index < _mb.size(); mb_index++) { - const subdev_spec_t &curr_subdev_spec = - _tree->access<subdev_spec_t>("/mboards/" + boost::lexical_cast<std::string>(mb_index) / "rx_subdev_spec").get(); - if (mb_chan < curr_subdev_spec.size()) { - break; - } else { - mb_chan -= curr_subdev_spec.size(); - } + // Loop through all tx streamers. Find all radios connected to one + // streamer. Sync those. + BOOST_FOREACH(const boost::weak_ptr<uhd::tx_streamer> &streamer_w, _tx_streamers.vals()) { + const boost::shared_ptr<sph::send_packet_streamer> streamer = + boost::dynamic_pointer_cast<sph::send_packet_streamer>(streamer_w.lock()); + if (not streamer) { + continue; } - // Find the DSP that corresponds to this mainboard and subdev - UHD_ASSERT_THROW(mb_index < _mb.size()); - mboard_members_t &mb = _mb[mb_index]; - const std::vector<size_t> dsp_map = _tree->access<std::vector<size_t> >("/mboards/" + boost::lexical_cast<std::string>(mb_index) / "rx_chan_dsp_mapping") - .get(); //.at(mb_chan); - UHD_ASSERT_THROW(mb_chan < dsp_map.size()); - const size_t radio_index = dsp_map[mb_chan]; - UHD_ASSERT_THROW(radio_index < 2); - radio_perifs_t &perif = mb.radio_perifs[radio_index]; - - //setup the dsp transport hints (default to a large recv buff) - device_addr_t device_addr = mb.recv_args; - if (not device_addr.has_key("recv_buff_size")) - { - if (mb.xport_path != "nirio") { - //For the ethernet transport, the buffer has to be set before creating - //the transport because it is independent of the frame size and # frames - //For nirio, the buffer size is not configurable by the user - #if defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD) - //limit buffer resize on macos or it will error - device_addr["recv_buff_size"] = boost::lexical_cast<std::string>(X300_RX_SW_BUFF_SIZE_ETH_MACOS); - #elif defined(UHD_PLATFORM_LINUX) || defined(UHD_PLATFORM_WIN32) - //set to half-a-second of buffering at max rate - device_addr["recv_buff_size"] = boost::lexical_cast<std::string>(X300_RX_SW_BUFF_SIZE_ETH); - #endif - } + std::vector<rfnoc::x300_radio_ctrl_impl::sptr> radio_ctrl_blks = + streamer->get_terminator()->find_downstream_node<rfnoc::x300_radio_ctrl_impl>(); + try { + //UHD_MSG(status) << "[X300] syncing " << radio_ctrl_blks.size() << " radios " << std::endl; + rfnoc::x300_radio_ctrl_impl::synchronize_dacs(radio_ctrl_blks); } - - //allocate sid and create transport - boost::uint8_t dest = (radio_index == 0)? X300_XB_DST_R0 : X300_XB_DST_R1; - boost::uint32_t data_sid; - UHD_LOG << "creating rx stream " << device_addr.to_string() << std::endl; - both_xports_t xport = this->make_transport(mb_index, dest, X300_RADIO_DEST_PREFIX_RX, device_addr, data_sid); - UHD_LOG << boost::format("data_sid = 0x%08x, actual recv_buff_size = %d\n") % data_sid % xport.recv_buff_size << std::endl; - - // To calculate the max number of samples per packet, we assume the maximum header length - // to avoid fragmentation should the entire header be used. - const size_t bpp = xport.recv->get_recv_frame_size() - X300_RX_MAX_HDR_LEN; // bytes per packet - const size_t bpi = convert::get_bytes_per_item(args.otw_format); // bytes per item - const size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi)); // samples per packet - - //make the new streamer given the samples per packet - if (not my_streamer) my_streamer = boost::make_shared<sph::recv_packet_streamer>(spp); - my_streamer->resize(args.channels.size()); - - //init some streamer stuff - std::string conv_endianness; - if (mb.if_pkt_is_big_endian) { - my_streamer->set_vrt_unpacker(&vrt::chdr::if_hdr_unpack_be); - conv_endianness = "be"; - } else { - my_streamer->set_vrt_unpacker(&vrt::chdr::if_hdr_unpack_le); - conv_endianness = "le"; + catch(const uhd::io_error &ex) { + throw uhd::io_error(str(boost::format("Failed to sync DACs! %s ") % ex.what())); } - - //set the converter - uhd::convert::id_type id; - id.input_format = args.otw_format + "_item32_" + conv_endianness; - id.num_inputs = 1; - id.output_format = args.cpu_format; - id.num_outputs = 1; - my_streamer->set_converter(id); - - perif.framer->clear(); - perif.framer->set_nsamps_per_packet(spp); //seems to be a good place to set this - perif.framer->set_sid((data_sid << 16) | (data_sid >> 16)); - perif.framer->setup(args); - perif.ddc->setup(args); - - //flow control setup - const size_t fc_window = get_rx_flow_control_window(xport.recv->get_recv_frame_size(), xport.recv_buff_size, device_addr); - const size_t fc_handle_window = std::max<size_t>(1, fc_window / X300_RX_FC_REQUEST_FREQ); - - UHD_LOG << "RX Flow Control Window = " << fc_window << ", RX Flow Control Handler Window = " << fc_handle_window << std::endl; - - perif.framer->configure_flow_control(fc_window); - - boost::shared_ptr<boost::uint32_t> seq32(new boost::uint32_t(0)); - //Give the streamer a functor to get the recv_buffer - //bind requires a zero_copy_if::sptr to add a streamer->xport lifetime dependency - my_streamer->set_xport_chan_get_buff( - stream_i, - boost::bind(&zero_copy_if::get_recv_buff, xport.recv, _1), - true /*flush*/ - ); - //Give the streamer a functor to handle overflows - //bind requires a weak_ptr to break the a streamer->streamer circular dependency - //Using "this" is OK because we know that x300_impl will outlive the streamer - my_streamer->set_overflow_handler( - stream_i, - boost::bind(&x300_impl::handle_overflow, this, boost::ref(perif), boost::weak_ptr<uhd::rx_streamer>(my_streamer)) - ); - //Give the streamer a functor to send flow control messages - //handle_rx_flowctrl is static and has no lifetime issues - my_streamer->set_xport_handle_flowctrl( - stream_i, boost::bind(&handle_rx_flowctrl, data_sid, xport.send, mb.if_pkt_is_big_endian, seq32, _1), - fc_handle_window, - true/*init*/ - ); - //Give the streamer a functor issue stream cmd - //bind requires a rx_vita_core_3000::sptr to add a streamer->framer lifetime dependency - my_streamer->set_issue_stream_cmd( - stream_i, boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1) - ); - - //Store a weak pointer to prevent a streamer->x300_impl->streamer circular dependency - mb.rx_streamers[radio_index] = boost::weak_ptr<sph::recv_packet_streamer>(my_streamer); - - //sets all tick and samp rates on this streamer - const fs_path mb_path = "/mboards/"+boost::lexical_cast<std::string>(mb_index); - _tree->access<double>(mb_path / "tick_rate").update(); - _tree->access<double>(mb_path / "rx_dsps" / boost::lexical_cast<std::string>(radio_index) / "rate" / "value").update(); } - - return my_streamer; } -void x300_impl::handle_overflow(x300_impl::radio_perifs_t &perif, boost::weak_ptr<uhd::rx_streamer> streamer) -{ - boost::shared_ptr<sph::recv_packet_streamer> my_streamer = - boost::dynamic_pointer_cast<sph::recv_packet_streamer>(streamer.lock()); - if (not my_streamer) return; //If the rx_streamer has expired then overflow handling makes no sense. - - if (my_streamer->get_num_channels() == 1) - { - perif.framer->handle_overflow(); - return; - } - - ///////////////////////////////////////////////////////////// - // MIMO overflow recovery time - ///////////////////////////////////////////////////////////// - //find out if we were in continuous mode before stopping - const bool in_continuous_streaming_mode = perif.framer->in_continuous_streaming_mode(); - //stop streaming - my_streamer->issue_stream_cmd(stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); - //flush transports - my_streamer->flush_all(0.001); - //restart streaming - if (in_continuous_streaming_mode) - { - stream_cmd_t stream_cmd(stream_cmd_t::STREAM_MODE_START_CONTINUOUS); - stream_cmd.stream_now = false; - stream_cmd.time_spec = perif.time64->get_time_now() + time_spec_t(0.01); - my_streamer->issue_stream_cmd(stream_cmd); - } -} - -/*********************************************************************** - * Transmit streamer - **********************************************************************/ -tx_streamer::sptr x300_impl::get_tx_stream(const uhd::stream_args_t &args_) -{ - boost::mutex::scoped_lock lock(_transport_setup_mutex); - stream_args_t args = args_; - - //setup defaults for unspecified values - if (not args.otw_format.empty() and args.otw_format != "sc16") - { - throw uhd::value_error("x300_impl::get_tx_stream only supports otw_format sc16"); - } - args.otw_format = "sc16"; - args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; - - //shared async queue for all channels in streamer - boost::shared_ptr<async_md_type> async_md(new async_md_type(1000/*messages deep*/)); - - std::vector<radio_perifs_t*> radios_list; - boost::shared_ptr<sph::send_packet_streamer> my_streamer; - for (size_t stream_i = 0; stream_i < args.channels.size(); stream_i++) - { - // Find the mainboard and subdev that corresponds to channel args.channels[stream_i] - const size_t chan = args.channels[stream_i]; - size_t mb_chan = chan, mb_index; - for (mb_index = 0; mb_index < _mb.size(); mb_index++) { - const subdev_spec_t &curr_subdev_spec = - _tree->access<subdev_spec_t>("/mboards/" + boost::lexical_cast<std::string>(mb_index) / "tx_subdev_spec").get(); - if (mb_chan < curr_subdev_spec.size()) { - break; - } else { - mb_chan -= curr_subdev_spec.size(); - } - } - // Find the DSP that corresponds to this mainboard and subdev - mboard_members_t &mb = _mb[mb_index]; - const size_t radio_index = _tree->access<std::vector<size_t> >("/mboards/" + boost::lexical_cast<std::string>(mb_index) / "tx_chan_dsp_mapping") - .get().at(mb_chan); - radio_perifs_t &perif = mb.radio_perifs[radio_index]; - radios_list.push_back(&perif); - - //setup the dsp transport hints (TODO) - device_addr_t device_addr = mb.send_args; - - //allocate sid and create transport - boost::uint8_t dest = (radio_index == 0)? X300_XB_DST_R0 : X300_XB_DST_R1; - boost::uint32_t data_sid; - UHD_LOG << "creating tx stream " << device_addr.to_string() << std::endl; - both_xports_t xport = this->make_transport(mb_index, dest, X300_RADIO_DEST_PREFIX_TX, device_addr, data_sid); - UHD_LOG << boost::format("data_sid = 0x%08x\n") % data_sid << std::endl; - - // To calculate the max number of samples per packet, we assume the maximum header length - // to avoid fragmentation should the entire header be used. - const size_t bpp = xport.send->get_send_frame_size() - X300_TX_MAX_HDR_LEN; - const size_t bpi = convert::get_bytes_per_item(args.otw_format); - const size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi)); - - //make the new streamer given the samples per packet - if (not my_streamer) my_streamer = boost::make_shared<sph::send_packet_streamer>(spp); - my_streamer->resize(args.channels.size()); - - std::string conv_endianness; - if (mb.if_pkt_is_big_endian) { - my_streamer->set_vrt_packer(&vrt::chdr::if_hdr_pack_be); - conv_endianness = "be"; - } else { - my_streamer->set_vrt_packer(&vrt::chdr::if_hdr_pack_le); - conv_endianness = "le"; - } - - //set the converter - uhd::convert::id_type id; - id.input_format = args.cpu_format; - id.num_inputs = 1; - id.output_format = args.otw_format + "_item32_" + conv_endianness; - id.num_outputs = 1; - my_streamer->set_converter(id); - - perif.deframer->clear(); - perif.deframer->setup(args); - perif.duc->setup(args); - - //flow control setup - size_t fc_window = get_tx_flow_control_window(xport.send->get_send_frame_size(), mb.has_dram_buff, device_addr); //In packets - const size_t fc_handle_window = std::max<size_t>(1, - fc_window/ (mb.has_dram_buff ? X300_TX_FC_RESPONSE_FREQ_DRAM : X300_TX_FC_RESPONSE_FREQ_SRAM)); - - UHD_LOG << "TX Flow Control Window = " << fc_window << ", TX Flow Control Handler Window = " << fc_handle_window << std::endl; - - perif.deframer->configure_flow_control(0/*cycs off*/, fc_handle_window); - boost::shared_ptr<x300_tx_fc_guts_t> guts(new x300_tx_fc_guts_t()); - guts->stream_channel = stream_i; - guts->device_channel = chan; - guts->async_queue = async_md; - guts->old_async_queue = _async_md; - task::sptr task = task::make(boost::bind(&handle_tx_async_msgs, guts, xport.recv, mb.if_pkt_is_big_endian, mb.clock)); - - //Give the streamer a functor to get the send buffer - //get_tx_buff_with_flowctrl is static so bind has no lifetime issues - //xport.send (sptr) is required to add streamer->data-transport lifetime dependency - //task (sptr) is required to add a streamer->async-handler lifetime dependency - my_streamer->set_xport_chan_get_buff( - stream_i, - boost::bind(&get_tx_buff_with_flowctrl, task, guts, xport.send, fc_window, _1) - ); - //Give the streamer a functor handled received async messages - my_streamer->set_async_receiver( - boost::bind(&async_md_type::pop_with_timed_wait, async_md, _1, _2) - ); - my_streamer->set_xport_chan_sid(stream_i, true, data_sid); - my_streamer->set_enable_trailer(false); //TODO not implemented trailer support yet - - //Store a weak pointer to prevent a streamer->x300_impl->streamer circular dependency - mb.tx_streamers[radio_index] = boost::weak_ptr<sph::send_packet_streamer>(my_streamer); - - //sets all tick and samp rates on this streamer - const fs_path mb_path = "/mboards/"+boost::lexical_cast<std::string>(mb_index); - _tree->access<double>(mb_path / "tick_rate").update(); - _tree->access<double>(mb_path / "tx_dsps" / boost::lexical_cast<std::string>(radio_index) / "rate" / "value").update(); - } - - synchronize_dacs(radios_list); - return my_streamer; -} +// vim: sw=4 expandtab: diff --git a/host/lib/usrp/x300/x300_radio_ctrl_impl.cpp b/host/lib/usrp/x300/x300_radio_ctrl_impl.cpp new file mode 100644 index 000000000..3a129b334 --- /dev/null +++ b/host/lib/usrp/x300/x300_radio_ctrl_impl.cpp @@ -0,0 +1,888 @@ +// +// Copyright 2015-2016 Ettus Research +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "x300_radio_ctrl_impl.hpp" + +#include "x300_dboard_iface.hpp" +#include "wb_iface_adapter.hpp" +#include "gpio_atr_3000.hpp" +#include "apply_corrections.hpp" +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/usrp/dboard_iface.hpp> +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/transport/chdr.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/make_shared.hpp> +#include <boost/date_time/posix_time/posix_time_io.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; +using namespace uhd::usrp::x300; + +static const size_t IO_MASTER_RADIO = 0; + +/**************************************************************************** + * Structors + ***************************************************************************/ +UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR(x300_radio_ctrl) + , _ignore_cal_file(false) +{ + UHD_RFNOC_BLOCK_TRACE() << "x300_radio_ctrl_impl::ctor() " << std::endl; + + //////////////////////////////////////////////////////////////////// + // Set up basic info + //////////////////////////////////////////////////////////////////// + _radio_type = (get_block_id().get_block_count() == 0) ? PRIMARY : SECONDARY; + _radio_slot = (get_block_id().get_block_count() == 0) ? "A" : "B"; + _radio_clk_rate = _tree->access<double>("master_clock_rate").get(); + + //////////////////////////////////////////////////////////////////// + // Set up peripherals + //////////////////////////////////////////////////////////////////// + wb_iface::sptr ctrl = _get_ctrl(IO_MASTER_RADIO); + _regs = boost::make_shared<radio_regmap_t>(_radio_type==PRIMARY?0:1); + _regs->initialize(*ctrl, true); + + //Only Radio0 has the ADC/DAC reset bits. Those bits are reserved for Radio1 + 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.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); + + //////////////////////////////////////////////////////////////// + // Setup peripherals + //////////////////////////////////////////////////////////////// + _spi = spi_core_3000::make(ctrl, + radio_ctrl_impl::regs::sr_addr(radio_ctrl_impl::regs::SPI), + radio_ctrl_impl::regs::RB_SPI); + _leds = gpio_atr::gpio_atr_3000::make_write_only(ctrl, regs::sr_addr(regs::LEDS)); + _leds->set_atr_mode(usrp::gpio_atr::MODE_ATR, usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL); + _adc = x300_adc_ctrl::make(_spi, DB_ADC_SEN); + _dac = x300_dac_ctrl::make(_spi, DB_DAC_SEN, _radio_clk_rate); + + if (_radio_type==PRIMARY) { + _fp_gpio = gpio_atr::gpio_atr_3000::make(ctrl, regs::sr_addr(regs::FP_GPIO), regs::RB_FP_GPIO); + BOOST_FOREACH(const gpio_atr::gpio_attr_map_t::value_type attr, gpio_atr::gpio_attr_map) { + _tree->create<boost::uint32_t>(fs_path("gpio") / "FP0" / attr.second) + .set(0) + .add_coerced_subscriber(boost::bind(&gpio_atr::gpio_atr_3000::set_gpio_attr, _fp_gpio, attr.first, _1)); + } + _tree->create<boost::uint32_t>(fs_path("gpio") / "FP0" / "READBACK") + .set_publisher(boost::bind(&gpio_atr::gpio_atr_3000::read_gpio, _fp_gpio)); + } + + //////////////////////////////////////////////////////////////// + // create legacy codec control objects + //////////////////////////////////////////////////////////////// + _tree->create<int>("rx_codecs" / _radio_slot / "gains"); //phony property so this dir exists + _tree->create<int>("tx_codecs" / _radio_slot / "gains"); //phony property so this dir exists + _tree->create<std::string>("rx_codecs" / _radio_slot / "name").set("ads62p48"); + _tree->create<std::string>("tx_codecs" / _radio_slot / "name").set("ad9146"); + + _tree->create<meta_range_t>("rx_codecs" / _radio_slot / "gains" / "digital" / "range").set(meta_range_t(0, 6.0, 0.5)); + _tree->create<double>("rx_codecs" / _radio_slot / "gains" / "digital" / "value") + .add_coerced_subscriber(boost::bind(&x300_adc_ctrl::set_gain, _adc, _1)).set(0) + ; + + //////////////////////////////////////////////////////////////// + // create front-end objects + //////////////////////////////////////////////////////////////// + for (size_t i = 0; i < _get_num_radios(); i++) { + _rx_fe_map[i].core = rx_frontend_core_3000::make(_get_ctrl(i), regs::sr_addr(x300_regs::RX_RE_BASE)); + _rx_fe_map[i].core->set_adc_rate(_radio_clk_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(_tree->subtree(_root_path / "rx_fe_corrections" / i)); + + _tx_fe_map[i].core = tx_frontend_core_200::make(_get_ctrl(i), regs::sr_addr(x300_regs::TX_FE_BASE)); + _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(_tree->subtree(_root_path / "tx_fe_corrections" / i)); + } + + //////////////////////////////////////////////////////////////// + // Update default SPP (overwrites the default value from the XML file) + //////////////////////////////////////////////////////////////// + const size_t max_bytes_header = uhd::transport::vrt::chdr::max_if_hdr_words64 * sizeof(uint64_t); + const size_t default_spp = (_tree->access<size_t>("mtu/recv").get() - max_bytes_header) + / (2 * sizeof(int16_t)); + _tree->access<int>(get_arg_path("spp") / "value").set(default_spp); +} + +x300_radio_ctrl_impl::~x300_radio_ctrl_impl() +{ + // Tear down our part of the tree: + _tree->remove(fs_path("rx_codecs" / _radio_slot)); + _tree->remove(fs_path("tx_codecs" / _radio_slot)); + _tree->remove(_root_path / "rx_fe_corrections"); + _tree->remove(_root_path / "tx_fe_corrections"); + if (_radio_type==PRIMARY) { + BOOST_FOREACH(const gpio_atr::gpio_attr_map_t::value_type attr, gpio_atr::gpio_attr_map) { + _tree->remove(fs_path("gpio") / "FP0" / attr.second); + } + _tree->remove(fs_path("gpio") / "FP0" / "READBACK"); + } + + // Reset peripherals + 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(); +} + +/**************************************************************************** + * API calls + ***************************************************************************/ +double x300_radio_ctrl_impl::set_rate(double /* rate */) +{ + // On X3x0, tick rate can't actually be changed at runtime + return get_rate(); +} + +void x300_radio_ctrl_impl::set_tx_antenna(const std::string &ant, const size_t chan) +{ + _tree->access<std::string>( + fs_path("dboards" / _radio_slot / "tx_frontends" / _tx_fe_map.at(chan).db_fe_name / "antenna" / "value") + ).set(ant); +} + +void x300_radio_ctrl_impl::set_rx_antenna(const std::string &ant, const size_t chan) +{ + _tree->access<std::string>( + fs_path("dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name / "antenna" / "value") + ).set(ant); +} + +double x300_radio_ctrl_impl::set_tx_frequency(const double freq, const size_t chan) +{ + return _tree->access<double>( + fs_path("dboards" / _radio_slot / "tx_frontends" / _tx_fe_map.at(chan).db_fe_name / "freq" / "value") + ).set(freq).get(); +} + +double x300_radio_ctrl_impl::get_tx_frequency(const size_t chan) +{ + return _tree->access<double>( + fs_path("dboards" / _radio_slot / "tx_frontends" / _tx_fe_map.at(chan).db_fe_name / "freq" / "value") + ).get(); +} + +double x300_radio_ctrl_impl::set_rx_frequency(const double freq, const size_t chan) +{ + return _tree->access<double>( + fs_path("dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name / "freq" / "value") + ).set(freq).get(); +} + +double x300_radio_ctrl_impl::get_rx_frequency(const size_t chan) +{ + return _tree->access<double>( + fs_path("dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name / "freq" / "value") + ).get(); +} + +double x300_radio_ctrl_impl::set_tx_gain(const double gain, const size_t chan) +{ + //TODO: This is extremely hacky! + fs_path path("dboards" / _radio_slot / "tx_frontends" / _tx_fe_map.at(chan).db_fe_name / "gains"); + std::vector<std::string> gain_stages = _tree->list(path); + if (gain_stages.size() == 1) { + const double actual_gain = _tree->access<double>(path / gain_stages[0] / "value").set(gain).get(); + radio_ctrl_impl::set_tx_gain(actual_gain, chan); + return gain; + } else { + UHD_MSG(warning) << "set_tx_gain: could not apply gain for this daughterboard."; + radio_ctrl_impl::set_tx_gain(0.0, chan); + return 0.0; + } +} + +double x300_radio_ctrl_impl::set_rx_gain(const double gain, const size_t chan) +{ + //TODO: This is extremely hacky! + fs_path path("dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name / "gains"); + std::vector<std::string> gain_stages = _tree->list(path); + if (gain_stages.size() == 1) { + const double actual_gain = _tree->access<double>(path / gain_stages[0] / "value").set(gain).get(); + radio_ctrl_impl::set_rx_gain(actual_gain, chan); + return gain; + } else { + UHD_MSG(warning) << "set_rx_gain: could not apply gain for this daughterboard."; + radio_ctrl_impl::set_tx_gain(0.0, chan); + return 0.0; + } +} + + +template <typename map_type> +static size_t _get_chan_from_map(std::map<size_t, map_type> map, const std::string &fe) +{ + // TODO replace with 'auto' when possible + typedef typename std::map<size_t, map_type>::iterator chan_iterator; + for (chan_iterator it = map.begin(); it != map.end(); ++it) { + if (it->second.db_fe_name == fe) { + return it->first; + } + + } + throw uhd::runtime_error(str( + boost::format("Invalid daughterboard frontend name: %s") + % fe + )); +} + +size_t x300_radio_ctrl_impl::get_chan_from_dboard_fe(const std::string &fe, const uhd::direction_t direction) +{ + 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 x300_radio_ctrl_impl::get_dboard_fe_from_chan(const size_t chan, const uhd::direction_t direction) +{ + 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(); + } +} + +double x300_radio_ctrl_impl::get_output_samp_rate(size_t chan) +{ + // TODO: chan should never be ANY_PORT, but due to our current graph search + // method, this can actually happen: + if (chan == ANY_PORT) { + chan = 0; + for (size_t i = 0; i < _get_num_radios(); i++) { + if (_is_streamer_active(uhd::RX_DIRECTION, chan)) { + chan = i; + break; + } + } + } + return _rx_fe_map.at(chan).core->get_output_rate(); +} + +/**************************************************************************** + * Radio control and setup + ***************************************************************************/ +void x300_radio_ctrl_impl::setup_radio(uhd::i2c_iface::sptr zpu_i2c, x300_clock_ctrl::sptr clock, bool verbose) +{ + _self_cal_adc_capture_delay(verbose); + + //////////////////////////////////////////////////////////////////// + // create RF frontend interfacing + //////////////////////////////////////////////////////////////////// + static const size_t BASE_ADDR = 0x50; + static const size_t RX_EEPROM_ADDR = 0x5; + static const size_t TX_EEPROM_ADDR = 0x4; + static const size_t GDB_EEPROM_ADDR = 0x1; + const static std::vector<size_t> EEPROM_ADDRS = + boost::assign::list_of(RX_EEPROM_ADDR)(TX_EEPROM_ADDR)(GDB_EEPROM_ADDR); + const static std::vector<std::string> EEPROM_PATHS = + boost::assign::list_of("rx_eeprom")("tx_eeprom")("gdb_eeprom"); + + const size_t DB_OFFSET = (_radio_slot == "A") ? 0x0 : 0x2; + const fs_path db_path = ("dboards" / _radio_slot); + 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 + _tree->create<dboard_eeprom_t>(db_path / EEPROM_PATHS[i]) + .set(_db_eeproms[addr]) + .add_coerced_subscriber(boost::bind(&dboard_eeprom_t::store, + _db_eeproms[addr], boost::ref(*zpu_i2c), (BASE_ADDR | addr))); + } + + //create a new dboard interface + x300_dboard_iface_config_t db_config; + db_config.gpio = gpio_atr::db_gpio_atr_3000::make(_get_ctrl(IO_MASTER_RADIO), + radio_ctrl_impl::regs::sr_addr(radio_ctrl_impl::regs::GPIO), radio_ctrl_impl::regs::RB_DB_GPIO); + 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_slot == "A") ? X300_CLOCK_WHICH_DB0_RX : X300_CLOCK_WHICH_DB1_RX; + db_config.which_tx_clk = (_radio_slot == "A") ? X300_CLOCK_WHICH_DB0_TX : X300_CLOCK_WHICH_DB1_TX; + db_config.dboard_slot = (_radio_slot == "A")? 0 : 1; + db_config.cmd_time_ctrl = _get_ctrl(IO_MASTER_RADIO); + + //create a new dboard manager + boost::shared_ptr<x300_dboard_iface> db_iface = boost::make_shared<x300_dboard_iface>(db_config); + _db_manager = dboard_manager::make( + _db_eeproms[RX_EEPROM_ADDR + DB_OFFSET].id, + _db_eeproms[TX_EEPROM_ADDR + DB_OFFSET].id, + _db_eeproms[GDB_EEPROM_ADDR + DB_OFFSET].id, + db_iface, _tree->subtree(db_path), + true // defer daughterboard intitialization + ); + + size_t rx_chan = 0, tx_chan = 0; + BOOST_FOREACH(const std::string& fe, _db_manager->get_rx_frontends()) { + if (rx_chan >= _get_num_radios()) { + 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 = _tree->access<std::string>(fe_path / "connection").get(); + const double if_freq = (_tree->exists(fe_path / "if_freq/value")) ? + _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++; + } + BOOST_FOREACH(const std::string& fe, _db_manager->get_tx_frontends()) { + if (tx_chan >= _get_num_radios()) { + break; + } + _tx_fe_map[tx_chan].db_fe_name = fe; + const fs_path fe_path(db_path / "tx_frontends" / fe); + const std::string conn = _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); + + // 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() + and _tree->exists(db_path / "rx_frontends" / _rx_fe_map[0].db_fe_name / "antenna" / "value")) { + _tree->access<std::string>(db_path / "rx_frontends" / _rx_fe_map[0].db_fe_name / "antenna" / "value") + .add_coerced_subscriber(boost::bind(&x300_radio_ctrl_impl::_update_atr_leds, this, _1)); + } + _update_atr_leds(""); //init anyway, even if never called + + //bind frontend corrections to the dboard freq props + const fs_path db_tx_fe_path = db_path / "tx_frontends"; + BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)) { + _tree->access<double>(db_tx_fe_path / name / "freq" / "value") + .add_coerced_subscriber(boost::bind(&x300_radio_ctrl_impl::set_tx_fe_corrections, this, _radio_slot, _1)); + } + const fs_path db_rx_fe_path = db_path / "rx_frontends"; + BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)) { + _tree->access<double>(db_rx_fe_path / name / "freq" / "value") + .add_coerced_subscriber(boost::bind(&x300_radio_ctrl_impl::set_rx_fe_corrections, this, _radio_slot, _1)); + } + + //////////////////////////////////////////////////////////////// + // Set tick rate + //////////////////////////////////////////////////////////////// + const double tick_rate = get_output_samp_rate(0); + if (_radio_type==PRIMARY) { + // Slot A is the highlander timekeeper + _tree->access<double>("tick_rate").set(tick_rate); + } + radio_ctrl_impl::set_rate(tick_rate); +} + +void x300_radio_ctrl_impl::set_rx_fe_corrections( + const std::string &slot_name, + const double lo_freq +) { + if (not _ignore_cal_file) { + apply_rx_fe_corrections(_tree, slot_name, lo_freq); + } +} + +void x300_radio_ctrl_impl::set_tx_fe_corrections( + const std::string &slot_name, + const double lo_freq +) { + if (not _ignore_cal_file) { + apply_tx_fe_corrections(_tree, slot_name, lo_freq); + } +} + +void x300_radio_ctrl_impl::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(); + } + UHD_ASSERT_THROW(bool(_adc)); + UHD_ASSERT_THROW(bool(_dac)); + _adc->reset(); + _dac->reset(); +} + +void x300_radio_ctrl_impl::self_test_adc(boost::uint32_t ramp_time_ms) +{ + //Bypass all front-end corrections + for (size_t i = 0; i < _get_num_radios(); 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. + boost::this_thread::sleep(boost::posix_time::microsec(1000)); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1); + + boost::this_thread::sleep(boost::posix_time::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}")%unique_id()%i_status%q_status).str()); + } + + //Restore front-end corrections + for (size_t i = 0; i < _get_num_radios(); i++) { + _rx_fe_map[i].core->bypass_all(false); + } +} + +void x300_radio_ctrl_impl::extended_adc_test(const std::vector<x300_radio_ctrl_impl::sptr>& radios, double duration_s) +{ + static const size_t SECS_PER_ITER = 5; + UHD_MSG(status) << boost::format("Running Extended ADC Self-Test (Duration=%.0fs, %ds/iteration)...\n") + % 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++) { + //Print date and time + boost::posix_time::time_facet *facet = new boost::posix_time::time_facet("%d-%b-%Y %H:%M:%S"); + std::ostringstream time_strm; + time_strm.imbue(std::locale(std::locale::classic(), facet)); + time_strm << boost::posix_time::second_clock::local_time(); + //Run self-test + UHD_MSG(status) << boost::format("-- [%s] Iteration %06d... ") % time_strm.str() % (iter+1); + try { + for (size_t i = 0; i < radios.size(); i++) { + radios[i]->self_test_adc((SECS_PER_ITER*1000)/radios.size()); + } + UHD_MSG(status) << "passed" << std::endl; + } catch(std::exception &e) { + num_failures++; + UHD_MSG(status) << e.what() << std::endl; + } + } + if (num_failures == 0) { + UHD_MSG(status) << "Extended ADC Self-Test PASSED\n"; + } else { + throw uhd::runtime_error( + (boost::format("Extended ADC Self-Test FAILED!!! (%d/%d failures)\n") % num_failures % num_iters).str()); + } +} + +void x300_radio_ctrl_impl::synchronize_dacs(const std::vector<x300_radio_ctrl_impl::sptr>& radios) +{ + if (radios.size() < 2) return; //Nothing to synchronize + + //**PRECONDITION** + //This function assumes that all the VITA times in "radios" are synchronized + //to a common reference. Currently, this function is called in get_tx_stream + //which also has the same precondition. + + //Reinitialize and resync all DACs + for (size_t i = 0; i < radios.size(); i++) { + radios[i]->_dac->reset(); + } + + //Get a rough estimate of the cumulative command latency + boost::posix_time::ptime t_start = boost::posix_time::microsec_clock::local_time(); + for (size_t i = 0; i < radios.size(); i++) { + radios[i]->user_reg_read64(regs::RB_TIME_NOW); //Discard value. We are just timing the call + } + boost::posix_time::time_duration t_elapsed = + boost::posix_time::microsec_clock::local_time() - t_start; + + //Add 100% of headroom + uncertaintly to the command time + boost::uint64_t t_sync_us = (t_elapsed.total_microseconds() * 2) + 13000 /*Scheduler latency*/; + + //Pick radios[0] as the time reference. + uhd::time_spec_t sync_time = + radios[0]->_time64->get_time_now() + uhd::time_spec_t(((double)t_sync_us)/1e6); + + //Send the sync command + for (size_t i = 0; i < radios.size(); i++) { + radios[i]->set_command_tick_rate(radios[i]->_radio_clk_rate, IO_MASTER_RADIO); + radios[i]->_regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_SYNC, 0); + radios[i]->set_command_time(sync_time, IO_MASTER_RADIO); + //Arm FRAMEP/N sync pulse by asserting a rising edge + radios[i]->_regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_SYNC, 1); + radios[i]->_regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_SYNC, 0); + radios[i]->set_command_time(uhd::time_spec_t(0.0), IO_MASTER_RADIO); + } + + //Wait and check status + boost::this_thread::sleep(boost::posix_time::microseconds(t_sync_us)); + for (size_t i = 0; i < radios.size(); i++) { + radios[i]->_dac->verify_sync(); + } +} + +double x300_radio_ctrl_impl::self_cal_adc_xfer_delay( + const std::vector<x300_radio_ctrl_impl::sptr>& radios, + x300_clock_ctrl::sptr clock, + boost::function<void(double)> wait_for_clk_locked, + bool apply_delay) +{ + UHD_MSG(status) << "Running ADC transfer delay self-cal: " << std::flush; + + //Effective resolution of the self-cal. + static const size_t NUM_DELAY_STEPS = 100; + + double master_clk_period = (1.0e9 / clock->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; + + UHD_MSG(status) << "Measuring..." << std::flush; + double cached_clk_delay = clock->get_clock_delay(X300_CLOCK_WHICH_ADC0); + double fpga_clk_delay = clock->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->set_clock_delay(X300_CLOCK_WHICH_ADC0, delay_incr*i + delay_start); + wait_for_clk_locked(0.1); + + boost::uint32_t err_code = 0; + for (size_t r = 0; r < radios.size(); r++) { + //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. + radios[r]->_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 + radios[r]->_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + radios[r]->_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1); + //50ms @ 200MHz = 10 million samples + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + if (radios[r]->_regs->misc_ins_reg.read(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_LOCKED)) { + err_code += radios[r]->_regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_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. + radios[r]->_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 + radios[r]->_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + radios[r]->_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1); + //50ms @ 200MHz = 10 million samples + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + if (radios[r]->_regs->misc_ins_reg.read(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_LOCKED)) { + err_code += radios[r]->_regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_ERROR); + } else { + err_code += 100; //Increment error code by 100 to indicate no lock + } + } + //UHD_MSG(status) << (boost::format("XferDelay=%fns, Error=%d\n") % delay % err_code); + results.push_back(std::pair<double,bool>(delay, err_code==0)); + } + + //Calculate the valid window + 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; + 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) { + UHD_MSG(status) << "Validating..." << std::flush; + //Apply delay + win_center = clock->set_clock_delay(X300_CLOCK_WHICH_ADC0, win_center); //Sets ADC0 and ADC1 + wait_for_clk_locked(0.1); + //Validate + for (size_t r = 0; r < radios.size(); r++) { + radios[r]->self_test_adc(2000); + } + } else { + //Restore delay + clock->set_clock_delay(X300_CLOCK_WHICH_ADC0, cached_clk_delay); //Sets ADC0 and ADC1 + } + + //Teardown + for (size_t r = 0; r < radios.size(); r++) { + radios[r]->_adc->set_test_word("normal", "normal"); + radios[r]->_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + } + UHD_MSG(status) << (boost::format(" done (FPGA->ADC=%.3fns%s, Window=%.3fns)\n") % + (win_center-fpga_clk_delay) % (cycle_slip?" +cyc":"") % win_length); + + return win_center; +} +/**************************************************************************** + * Helpers + ***************************************************************************/ +void x300_radio_ctrl_impl::_update_atr_leds(const std::string &rx_ant) +{ + const bool is_txrx = (rx_ant == "TX/RX"); + const int rx_led = (1 << 2); + const int tx_led = (1 << 1); + const int txrx_led = (1 << 0); + _leds->set_atr_reg(gpio_atr::ATR_REG_IDLE, 0); + _leds->set_atr_reg(gpio_atr::ATR_REG_RX_ONLY, is_txrx? txrx_led : rx_led); + _leds->set_atr_reg(gpio_atr::ATR_REG_TX_ONLY, tx_led); + _leds->set_atr_reg(gpio_atr::ATR_REG_FULL_DUPLEX, rx_led | tx_led); +} + +void x300_radio_ctrl_impl::_self_cal_adc_capture_delay(bool print_status) +{ + if (print_status) UHD_MSG(status) << "Running ADC capture delay self-cal..." << std::flush; + + static const boost::uint32_t NUM_DELAY_STEPS = 32; //The IDELAYE2 element has 32 steps + static const boost::uint32_t NUM_RETRIES = 2; //Retry self-cal if it fails in warmup situations + static const boost::int32_t MIN_WINDOW_LEN = 4; + + boost::int32_t win_start = -1, win_stop = -1; + boost::uint32_t iter = 0; + while (iter++ < NUM_RETRIES) { + for (boost::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); + + boost::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); + //10ms @ 200MHz = 2 million samples + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + 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); + //10ms @ 200MHz = 2 million samples + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + 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_MSG(status) << (boost::format("CapTap=%d, Error=%d\n") % 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; + boost::this_thread::sleep(boost::posix_time::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."); + } + + boost::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); + + if (print_status) { + double tap_delay = (1.0e12 / _radio_clk_rate) / (2*32); //in ps + UHD_MSG(status) << boost::format(" done (Tap=%d, Window=%d, TapDelay=%.3fps, Iter=%d)\n") % ideal_tap % (win_stop-win_start) % tap_delay % iter; + } +} + +void x300_radio_ctrl_impl::_check_adc(const boost::uint32_t val) +{ + //Wait for previous control transaction to flush + user_reg_read64(regs::RB_TEST); + //Wait for ADC test pattern to propagate + boost::this_thread::sleep(boost::posix_time::microsec(5)); + //Read value of RX readback register and verify + boost::uint32_t adc_rb = static_cast<boost::uint32_t>(user_reg_read64(regs::RB_TEST)>>32); + adc_rb ^= 0xfffc0000; //adapt for I inversion in FPGA + if (val != adc_rb) { + throw uhd::runtime_error( + (boost::format("ADC self-test failed for %s. (Exp=0x%x, Got=0x%x)")%unique_id()%val%adc_rb).str()); + } +} + +/**************************************************************************** + * Helpers + ***************************************************************************/ +bool x300_radio_ctrl_impl::check_radio_config() +{ + UHD_RFNOC_BLOCK_TRACE() << "x300_radio_ctrl_impl::check_radio_config() " << std::endl; + const fs_path rx_fe_path = fs_path("dboards" / _radio_slot / "rx_frontends"); + for (size_t chan = 0; chan < _get_num_radios(); chan++) { + if (_tree->exists(rx_fe_path / _rx_fe_map.at(chan).db_fe_name / "enabled")) { + const bool chan_active = _is_streamer_active(RX_DIRECTION, chan); + _tree->access<bool>(rx_fe_path / _rx_fe_map.at(chan).db_fe_name / "enabled") + .set(chan_active) + ; + } + } + + const fs_path tx_fe_path = fs_path("dboards" / _radio_slot / "tx_frontends"); + for (size_t chan = 0; chan < _get_num_radios(); chan++) { + if (_tree->exists(tx_fe_path / _rx_fe_map.at(chan).db_fe_name / "enabled")) { + const bool chan_active = _is_streamer_active(TX_DIRECTION, chan); + _tree->access<bool>(tx_fe_path / _rx_fe_map.at(chan).db_fe_name / "enabled") + .set(chan_active) + ; + } + } + + return true; +} + +/**************************************************************************** + * Register block + ***************************************************************************/ +UHD_RFNOC_BLOCK_REGISTER(x300_radio_ctrl, "X300Radio"); diff --git a/host/lib/usrp/x300/x300_radio_ctrl_impl.hpp b/host/lib/usrp/x300/x300_radio_ctrl_impl.hpp new file mode 100644 index 000000000..f2150a982 --- /dev/null +++ b/host/lib/usrp/x300/x300_radio_ctrl_impl.hpp @@ -0,0 +1,198 @@ +// +// Copyright 2015-2016 Ettus Research +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_X300_RADIO_CTRL_IMPL_HPP +#define INCLUDED_LIBUHD_RFNOC_X300_RADIO_CTRL_IMPL_HPP + +#include "radio_ctrl_impl.hpp" +#include "x300_clock_ctrl.hpp" +#include "spi_core_3000.hpp" +#include "x300_adc_ctrl.hpp" +#include "x300_dac_ctrl.hpp" +#include "x300_regs.hpp" +#include "rx_frontend_core_3000.hpp" +#include "tx_frontend_core_200.hpp" +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/usrp/gpio_defs.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Provide access to an X300 radio. + */ +class x300_radio_ctrl_impl : public radio_ctrl_impl +{ +public: + typedef boost::shared_ptr<x300_radio_ctrl_impl> sptr; + + /************************************************************************ + * Structors + ***********************************************************************/ + UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR_DECL(x300_radio_ctrl) + virtual ~x300_radio_ctrl_impl(); + + /************************************************************************ + * API calls + ***********************************************************************/ + double set_rate(double rate); + + void set_tx_antenna(const std::string &ant, const size_t chan); + void set_rx_antenna(const std::string &ant, const size_t chan); + + double set_tx_frequency(const double freq, const size_t chan); + double set_rx_frequency(const double freq, const size_t chan); + double get_tx_frequency(const size_t chan); + double get_rx_frequency(const size_t chan); + + double set_tx_gain(const double gain, const size_t chan); + double set_rx_gain(const double gain, const size_t chan); + + size_t get_chan_from_dboard_fe(const std::string &fe, const direction_t dir); + std::string get_dboard_fe_from_chan(const size_t chan, const direction_t dir); + + double get_output_samp_rate(size_t port); + + /************************************************************************ + * Hardware setup and control + ***********************************************************************/ + /*! Set up the radio. No API calls may be made before this one. + */ + void setup_radio( + uhd::i2c_iface::sptr zpu_i2c, x300_clock_ctrl::sptr clock, bool verbose); + + void reset_codec(); + + void self_test_adc( + boost::uint32_t ramp_time_ms = 100); + + static void extended_adc_test( + const std::vector<x300_radio_ctrl_impl::sptr>&, double duration_s); + + static void synchronize_dacs( + const std::vector<x300_radio_ctrl_impl::sptr>& radios); + + static double self_cal_adc_xfer_delay( + const std::vector<x300_radio_ctrl_impl::sptr>& radios, + x300_clock_ctrl::sptr clock, + boost::function<void(double)> wait_for_clk_locked, + bool apply_delay); + +protected: + virtual bool check_radio_config(); + +private: + class radio_regmap_t : public uhd::soft_regmap_t { + public: + typedef boost::shared_ptr<radio_regmap_t> sptr; + 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(regs::sr_addr(regs::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(regs::RB_MISC_IO) { } + } misc_ins_reg; + + radio_regmap_t(int radio_num) : soft_regmap_t("radio" + boost::lexical_cast<std::string>(radio_num) + "_regmap") { + add_to_map(misc_outs_reg, "misc_outs_reg", PRIVATE); + add_to_map(misc_ins_reg, "misc_ins_reg", PRIVATE); + } + }; + + struct x300_regs { + static const uint32_t TX_FE_BASE = 224; + static const uint32_t RX_RE_BASE = 232; + }; + + void _update_atr_leds(const std::string &rx_ant); + + void _self_cal_adc_capture_delay(bool print_status); + + void _check_adc(const boost::uint32_t val); + + void set_rx_fe_corrections(const std::string &fe_name, const double lo_freq); + void set_tx_fe_corrections(const std::string &fe_name, const double lo_freq); + +private: // members + enum radio_connection_t { PRIMARY, SECONDARY }; + + radio_connection_t _radio_type; + std::string _radio_slot; + //! Radio clock rate is the rate at which the ADC and DAC are running at. + // Not necessarily this block's sampling rate (tick rate). + double _radio_clk_rate; + + radio_regmap_t::sptr _regs; + usrp::gpio_atr::gpio_atr_3000::sptr _leds; + spi_core_3000::sptr _spi; + x300_adc_ctrl::sptr _adc; + x300_dac_ctrl::sptr _dac; + usrp::gpio_atr::gpio_atr_3000::sptr _fp_gpio; + + std::map<size_t, usrp::dboard_eeprom_t> _db_eeproms; + usrp::dboard_manager::sptr _db_manager; + + 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::map<size_t, rx_fe_perif> _rx_fe_map; + std::map<size_t, tx_fe_perif> _tx_fe_map; + + bool _ignore_cal_file; + +}; /* class radio_ctrl_impl */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_X300_RADIO_CTRL_IMPL_HPP */ +// vim: sw=4 et: diff --git a/host/lib/usrp/x300/x300_regs.hpp b/host/lib/usrp/x300/x300_regs.hpp index 69a8d5d9f..458d6ba0f 100644 --- a/host/lib/usrp/x300/x300_regs.hpp +++ b/host/lib/usrp/x300/x300_regs.hpp @@ -22,41 +22,6 @@ #include <stdint.h> #include <uhd/utils/soft_register.hpp> -namespace uhd { namespace usrp { namespace radio { - -static UHD_INLINE uint32_t sr_addr(const uint32_t offset) -{ - return offset * 4; -} - -static const uint32_t DACSYNC = 5; -static const uint32_t LOOPBACK = 6; -static const uint32_t TEST = 7; -static const uint32_t SPI = 8; -static const uint32_t GPIO = 16; -static const uint32_t MISC_OUTS = 24; -static const uint32_t READBACK = 32; -static const uint32_t TX_CTRL = 64; -static const uint32_t RX_CTRL = 96; -static const uint32_t TIME = 128; -static const uint32_t RX_DSP = 144; -static const uint32_t TX_DSP = 184; -static const uint32_t LEDS = 195; -static const uint32_t FP_GPIO = 201; -static const uint32_t RX_FRONT = 208; -static const uint32_t TX_FRONT = 216; - -static const uint32_t RB32_GPIO = 0; -static const uint32_t RB32_SPI = 4; -static const uint32_t RB64_TIME_NOW = 8; -static const uint32_t RB64_TIME_PPS = 16; -static const uint32_t RB32_TEST = 24; -static const uint32_t RB32_RX = 28; -static const uint32_t RB32_FP_GPIO = 32; -static const uint32_t RB32_MISC_INS = 36; - -}}} // namespace - static const int BL_ADDRESS = 0; static const int BL_DATA = 1; @@ -87,6 +52,7 @@ static const int ZPU_SR_DRAM_FIFO1 = 80; static const int ZPU_RB_SPI = 2; static const int ZPU_RB_CLK_STATUS = 3; static const int ZPU_RB_COMPAT_NUM = 6; +static const int ZPU_RB_NUM_CE = 7; static const int ZPU_RB_SFP0_TYPE = 4; static const int ZPU_RB_SFP1_TYPE = 5; static const int ZPU_RB_DRAM_FIFO0 = 10; @@ -250,49 +216,6 @@ namespace uhd { namespace usrp { namespace x300 { } }; - class radio_regmap_t : public uhd::soft_regmap_t { - public: - typedef boost::shared_ptr<radio_regmap_t> sptr; - 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] - - misc_outs_reg_t(): uhd::soft_reg32_wo_t(uhd::usrp::radio::sr_addr(uhd::usrp::radio::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); - } - } misc_outs_reg; - - class misc_ins_reg_t : public uhd::soft_reg32_ro_t { - public: - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_Q_LOCKED, /*width*/ 1, /*shift*/ 0); //[0] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_I_LOCKED, /*width*/ 1, /*shift*/ 1); //[1] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_Q_LOCKED, /*width*/ 1, /*shift*/ 2); //[2] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_I_LOCKED, /*width*/ 1, /*shift*/ 3); //[3] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_Q_ERROR, /*width*/ 1, /*shift*/ 4); //[4] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_I_ERROR, /*width*/ 1, /*shift*/ 5); //[5] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_Q_ERROR, /*width*/ 1, /*shift*/ 6); //[6] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_I_ERROR, /*width*/ 1, /*shift*/ 7); //[7] - - misc_ins_reg_t(): uhd::soft_reg32_ro_t(uhd::usrp::radio::RB32_MISC_INS) { } - } misc_ins_reg; - - radio_regmap_t(int radio_num) : soft_regmap_t("radio" + boost::lexical_cast<std::string>(radio_num) + "_regmap") { - add_to_map(misc_outs_reg, "misc_outs_reg", PUBLIC); - add_to_map(misc_ins_reg, "misc_ins_reg", PUBLIC); - } - }; - }}} #endif /* INCLUDED_X300_REGS_HPP */ |