diff options
Diffstat (limited to 'host/lib/rfnoc')
44 files changed, 7774 insertions, 0 deletions
diff --git a/host/lib/rfnoc/CMakeLists.txt b/host/lib/rfnoc/CMakeLists.txt new file mode 100644 index 000000000..130b4173e --- /dev/null +++ b/host/lib/rfnoc/CMakeLists.txt @@ -0,0 +1,53 @@ +# +# 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/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +LIBUHD_APPEND_SOURCES( + # Infrastructure: + ${CMAKE_CURRENT_SOURCE_DIR}/block_ctrl_base.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/block_ctrl_base_factory.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/block_ctrl_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/blockdef_xml_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/block_id.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ctrl_iface.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/graph_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/legacy_compat.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/node_ctrl_base.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rate_node_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rx_stream_terminator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/scalar_node_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/sink_block_ctrl_base.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/sink_node_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/source_block_ctrl_base.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/source_node_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/stream_sig.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tick_node_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tx_stream_terminator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/wb_iface_adapter.cpp + # Default block control classes: + ${CMAKE_CURRENT_SOURCE_DIR}/ddc_block_ctrl_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/duc_block_ctrl_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/radio_ctrl_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dma_fifo_block_ctrl_impl +) + +INCLUDE_SUBDIRECTORY(nocscript) diff --git a/host/lib/rfnoc/block_ctrl_base.cpp b/host/lib/rfnoc/block_ctrl_base.cpp new file mode 100644 index 000000000..21bd32a1c --- /dev/null +++ b/host/lib/rfnoc/block_ctrl_base.cpp @@ -0,0 +1,587 @@ +// +// 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/>. +// + +// This file contains the block control functions for block controller classes. +// See block_ctrl_base_factory.cpp for discovery and factory functions. + +#include "ctrl_iface.hpp" +#include "nocscript/block_iface.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/convert.hpp> +#include <uhd/rfnoc/block_ctrl_base.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <boost/bind.hpp> + +#define UHD_BLOCK_LOG() UHD_LOGV(never) + +using namespace uhd; +using namespace uhd::rfnoc; +using std::string; + +/*********************************************************************** + * Helpers + **********************************************************************/ +//! Convert register to a peek/poke compatible address +inline boost::uint32_t _sr_to_addr(boost::uint32_t reg) { return reg * 4; }; +inline boost::uint32_t _sr_to_addr64(boost::uint32_t reg) { return reg * 8; }; // for peek64 + +/*********************************************************************** + * Structors + **********************************************************************/ +block_ctrl_base::block_ctrl_base( + const make_args_t &make_args +) : _tree(make_args.tree), + _transport_is_big_endian(make_args.is_big_endian), + _ctrl_ifaces(make_args.ctrl_ifaces), + _base_address(make_args.base_address & 0xFFF0) +{ + UHD_BLOCK_LOG() << "block_ctrl_base()" << std::endl; + + /*** Identify this block (NoC-ID, block-ID, and block definition) *******/ + // Read NoC-ID (name is passed in through make_args): + boost::uint64_t noc_id = sr_read64(SR_READBACK_REG_ID); + _block_def = blockdef::make_from_noc_id(noc_id); + if (_block_def) UHD_BLOCK_LOG() << "Found valid blockdef" << std::endl; + if (not _block_def) + _block_def = blockdef::make_from_noc_id(DEFAULT_NOC_ID); + UHD_ASSERT_THROW(_block_def); + // For the block ID, we start with block count 0 and increase until + // we get a block ID that's not already registered: + _block_id.set(make_args.device_index, make_args.block_name, 0); + while (_tree->exists("xbar/" + _block_id.get_local())) { + _block_id++; + } + UHD_BLOCK_LOG() + << "NOC ID: " << str(boost::format("0x%016X ") % noc_id) + << "Block ID: " << _block_id << std::endl; + + /*** Initialize property tree *******************************************/ + _root_path = "xbar/" + _block_id.get_local(); + _tree->create<boost::uint64_t>(_root_path / "noc_id").set(noc_id); + + /*** Reset block state *******************************************/ + clear(); + + /*** Configure ports ****************************************************/ + size_t n_valid_input_buffers = 0; + BOOST_FOREACH(const size_t ctrl_port, get_ctrl_ports()) { + // Set source addresses: + sr_write(SR_BLOCK_SID, get_address(ctrl_port), ctrl_port); + // Set sink buffer sizes: + settingsbus_reg_t reg = SR_READBACK_REG_FIFOSIZE; + uint64_t value = sr_read64(reg, ctrl_port); + size_t buf_size_log2 = value & 0xFF; + size_t buf_size_bytes = BYTES_PER_LINE * (1 << buf_size_log2); // Bytes == 8 * 2^x + if (buf_size_bytes > 0) n_valid_input_buffers++; + _tree->create<size_t>(_root_path / "input_buffer_size" / ctrl_port).set(buf_size_bytes); + } + + /*** Register names *****************************************************/ + blockdef::registers_t sregs = _block_def->get_settings_registers(); + BOOST_FOREACH(const std::string ®_name, sregs.keys()) { + if (DEFAULT_NAMED_SR.has_key(reg_name)) { + throw uhd::runtime_error(str( + boost::format("Register name %s is already defined!") + % reg_name + )); + } + _tree->create<size_t>(_root_path / "registers" / "sr" / reg_name) + .set(sregs.get(reg_name)); + } + blockdef::registers_t rbacks = _block_def->get_readback_registers(); + BOOST_FOREACH(const std::string ®_name, rbacks.keys()) { + _tree->create<size_t>(_root_path / "registers"/ "rb" / reg_name) + .set(rbacks.get(reg_name)); + } + + /*** Init I/O port definitions ******************************************/ + _init_port_defs("in", _block_def->get_input_ports()); + _init_port_defs("out", _block_def->get_output_ports()); + // FIXME this warning always fails until the input buffer code above is fixed + if (_tree->list(_root_path / "ports/in").size() != n_valid_input_buffers) { + UHD_MSG(warning) << + boost::format("[%s] defines %d input buffer sizes, but %d input ports") + % get_block_id().get() % n_valid_input_buffers % _tree->list(_root_path / "ports/in").size() + << std::endl; + } + + /*** Init default block args ********************************************/ + _nocscript_iface = nocscript::block_iface::make(this); + _init_block_args(); +} + +block_ctrl_base::~block_ctrl_base() +{ + _tree->remove(_root_path); +} + +void block_ctrl_base::_init_port_defs( + const std::string &direction, + blockdef::ports_t ports, + const size_t first_port_index +) { + size_t port_index = first_port_index; + BOOST_FOREACH(const blockdef::port_t &port_def, ports) { + fs_path port_path = _root_path / "ports" / direction / port_index; + if (not _tree->exists(port_path)) { + _tree->create<blockdef::port_t>(port_path); + } + UHD_RFNOC_BLOCK_TRACE() << "Adding port definition at " << port_path + << boost::format(": type = '%s' pkt_size = '%s' vlen = '%s'") % port_def["type"] % port_def["pkt_size"] % port_def["vlen"] + << std::endl; + _tree->access<blockdef::port_t>(port_path).set(port_def); + port_index++; + } +} + +void block_ctrl_base::_init_block_args() +{ + blockdef::args_t args = _block_def->get_args(); + fs_path arg_path = _root_path / "args"; + BOOST_FOREACH(const size_t port, get_ctrl_ports()) { + _tree->create<std::string>(arg_path / port); + } + + // First, create all nodes. + BOOST_FOREACH(const blockdef::arg_t &arg, args) { + fs_path arg_type_path = arg_path / arg["port"] / arg["name"] / "type"; + _tree->create<std::string>(arg_type_path).set(arg["type"]); + fs_path arg_val_path = arg_path / arg["port"] / arg["name"] / "value"; + if (arg["type"] == "int_vector") { throw uhd::runtime_error("not yet implemented: int_vector"); } + else if (arg["type"] == "int") { _tree->create<int>(arg_val_path); } + else if (arg["type"] == "double") { _tree->create<double>(arg_val_path); } + else if (arg["type"] == "string") { _tree->create<string>(arg_val_path); } + else { UHD_THROW_INVALID_CODE_PATH(); } + } + // Next: Create all the subscribers and coercers. + // TODO: Add coercer +#define _SUBSCRIBE_CHECK_AND_RUN(type, arg_tag, error_message) \ + _tree->access<type>(arg_val_path).add_coerced_subscriber(boost::bind((&nocscript::block_iface::run_and_check), _nocscript_iface, arg[#arg_tag], error_message)) + BOOST_FOREACH(const blockdef::arg_t &arg, args) { + fs_path arg_val_path = arg_path / arg["port"] / arg["name"] / "value"; + if (not arg["check"].empty()) { + if (arg["type"] == "string") { _SUBSCRIBE_CHECK_AND_RUN(string, check, arg["check_message"]); } + else if (arg["type"] == "int") { _SUBSCRIBE_CHECK_AND_RUN(int, check, arg["check_message"]); } + else if (arg["type"] == "double") { _SUBSCRIBE_CHECK_AND_RUN(double, check, arg["check_message"]); } + else if (arg["type"] == "int_vector") { throw uhd::runtime_error("not yet implemented: int_vector"); } + else { UHD_THROW_INVALID_CODE_PATH(); } + } + if (not arg["action"].empty()) { + if (arg["type"] == "string") { _SUBSCRIBE_CHECK_AND_RUN(string, action, ""); } + else if (arg["type"] == "int") { _SUBSCRIBE_CHECK_AND_RUN(int, action, ""); } + else if (arg["type"] == "double") { _SUBSCRIBE_CHECK_AND_RUN(double, action, ""); } + else if (arg["type"] == "int_vector") { throw uhd::runtime_error("not yet implemented: int_vector"); } + else { UHD_THROW_INVALID_CODE_PATH(); } + } + } + + // Finally: Set the values. This will call subscribers, if we have any. + BOOST_FOREACH(const blockdef::arg_t &arg, args) { + fs_path arg_val_path = arg_path / arg["port"] / arg["name"] / "value"; + if (not arg["value"].empty()) { + if (arg["type"] == "int_vector") { throw uhd::runtime_error("not yet implemented: int_vector"); } + else if (arg["type"] == "int") { _tree->access<int>(arg_val_path).set(boost::lexical_cast<int>(arg["value"])); } + else if (arg["type"] == "double") { _tree->access<double>(arg_val_path).set(boost::lexical_cast<double>(arg["value"])); } + else if (arg["type"] == "string") { _tree->access<string>(arg_val_path).set(arg["value"]); } + else { UHD_THROW_INVALID_CODE_PATH(); } + } + } +} + +/*********************************************************************** + * FPGA control & communication + **********************************************************************/ +wb_iface::sptr block_ctrl_base::get_ctrl_iface(const size_t block_port) +{ + return _ctrl_ifaces[block_port]; +} + +std::vector<size_t> block_ctrl_base::get_ctrl_ports() const +{ + std::vector<size_t> ctrl_ports; + ctrl_ports.reserve(_ctrl_ifaces.size()); + std::pair<size_t, wb_iface::sptr> it; + BOOST_FOREACH(it, _ctrl_ifaces) { + ctrl_ports.push_back(it.first); + } + return ctrl_ports; +} + +void block_ctrl_base::sr_write(const boost::uint32_t reg, const boost::uint32_t data, const size_t port) +{ + //UHD_BLOCK_LOG() << " "; + //UHD_RFNOC_BLOCK_TRACE() << boost::format("sr_write(%d, %08X, %d)") % reg % data % port << std::endl; + if (not _ctrl_ifaces.count(port)) { + throw uhd::key_error(str(boost::format("[%s] sr_write(): No such port: %d") % get_block_id().get() % port)); + } + try { + _ctrl_ifaces[port]->poke32(_sr_to_addr(reg), data); + } + catch(const std::exception &ex) { + throw uhd::io_error(str(boost::format("[%s] sr_write() failed: %s") % get_block_id().get() % ex.what())); + } +} + +void block_ctrl_base::sr_write(const std::string ®, const boost::uint32_t data, const size_t port) +{ + boost::uint32_t reg_addr = 255; + if (DEFAULT_NAMED_SR.has_key(reg)) { + reg_addr = DEFAULT_NAMED_SR[reg]; + } else { + if (not _tree->exists(_root_path / "registers" / "sr" / reg)) { + throw uhd::key_error(str( + boost::format("Unknown settings register name: %s") + % reg + )); + } + reg_addr = boost::uint32_t(_tree->access<size_t>(_root_path / "registers" / "sr" / reg).get()); + } + UHD_BLOCK_LOG() << " "; + UHD_RFNOC_BLOCK_TRACE() << boost::format("sr_write(%s, %08X) ==> ") % reg % data << std::endl; + return sr_write(reg_addr, data, port); +} + +boost::uint64_t block_ctrl_base::sr_read64(const settingsbus_reg_t reg, const size_t port) +{ + if (not _ctrl_ifaces.count(port)) { + throw uhd::key_error(str(boost::format("[%s] sr_read64(): No such port: %d") % get_block_id().get() % port)); + } + try { + return _ctrl_ifaces[port]->peek64(_sr_to_addr64(reg)); + } + catch(const std::exception &ex) { + throw uhd::io_error(str(boost::format("[%s] sr_read64() failed: %s") % get_block_id().get() % ex.what())); + } +} + +boost::uint32_t block_ctrl_base::sr_read32(const settingsbus_reg_t reg, const size_t port) +{ + if (not _ctrl_ifaces.count(port)) { + throw uhd::key_error(str(boost::format("[%s] sr_read32(): No such port: %d") % get_block_id().get() % port)); + } + try { + return _ctrl_ifaces[port]->peek32(_sr_to_addr64(reg)); + } + catch(const std::exception &ex) { + throw uhd::io_error(str(boost::format("[%s] sr_read32() failed: %s") % get_block_id().get() % ex.what())); + } +} + +boost::uint64_t block_ctrl_base::user_reg_read64(const boost::uint32_t addr, const size_t port) +{ + try { + // Set readback register address + sr_write(SR_READBACK_ADDR, addr, port); + // Read readback register via RFNoC + return sr_read64(SR_READBACK_REG_USER, port); + } + catch(const std::exception &ex) { + throw uhd::io_error(str(boost::format("%s user_reg_read64() failed: %s") % get_block_id().get() % ex.what())); + } +} + +boost::uint64_t block_ctrl_base::user_reg_read64(const std::string ®, const size_t port) +{ + if (not _tree->exists(_root_path / "registers" / "rb" / reg)) { + throw uhd::key_error(str( + boost::format("Invalid readback register name: %s") + % reg + )); + } + return user_reg_read64(boost::uint32_t( + _tree->access<size_t>(_root_path / "registers" / "rb" / reg).get() + ), port); +} + +boost::uint32_t block_ctrl_base::user_reg_read32(const boost::uint32_t addr, const size_t port) +{ + try { + // Set readback register address + sr_write(SR_READBACK_ADDR, addr, port); + // Read readback register via RFNoC + return sr_read32(SR_READBACK_REG_USER, port); + } + catch(const std::exception &ex) { + throw uhd::io_error(str(boost::format("[%s] user_reg_read32() failed: %s") % get_block_id().get() % ex.what())); + } +} + +boost::uint32_t block_ctrl_base::user_reg_read32(const std::string ®, const size_t port) +{ + if (not _tree->exists(_root_path / "registers" / "rb" / reg)) { + throw uhd::key_error(str( + boost::format("Invalid readback register name: %s") + % reg + )); + } + return user_reg_read32(boost::uint32_t( + _tree->access<size_t>(_root_path / "registers" / "sr" / reg).get() + ), port); +} + +void block_ctrl_base::set_command_time( + const time_spec_t &time_spec, + const size_t port +) { + if (port == ANY_PORT) { + BOOST_FOREACH(const size_t specific_port, get_ctrl_ports()) { + set_command_time(time_spec, specific_port); + } + return; + } + boost::shared_ptr<ctrl_iface> iface_sptr = + boost::dynamic_pointer_cast<ctrl_iface>(get_ctrl_iface(port)); + if (not iface_sptr) { + throw uhd::assertion_error(str( + boost::format("[%s] Cannot set command time on port '%d'") + % unique_id() % port + )); + } + + iface_sptr->set_time(time_spec); +} + +time_spec_t block_ctrl_base::get_command_time( + const size_t port +) { + boost::shared_ptr<ctrl_iface> iface_sptr = + boost::dynamic_pointer_cast<ctrl_iface>(get_ctrl_iface(port)); + if (not iface_sptr) { + throw uhd::assertion_error(str( + boost::format("[%s] Cannot get command time on port '%d'") + % unique_id() % port + )); + } + + return iface_sptr->get_time(); +} + +void block_ctrl_base::set_command_tick_rate( + const double tick_rate, + const size_t port +) { + if (port == ANY_PORT) { + BOOST_FOREACH(const size_t specific_port, get_ctrl_ports()) { + set_command_tick_rate(tick_rate, specific_port); + } + return; + } + boost::shared_ptr<ctrl_iface> iface_sptr = + boost::dynamic_pointer_cast<ctrl_iface>(get_ctrl_iface(port)); + if (not iface_sptr) { + throw uhd::assertion_error(str( + boost::format("[%s] Cannot set command time on port '%d'") + % unique_id() % port + )); + } + + iface_sptr->set_tick_rate(tick_rate); +} + +void block_ctrl_base::clear_command_time(const size_t port) +{ + boost::shared_ptr<ctrl_iface> iface_sptr = + boost::dynamic_pointer_cast<ctrl_iface>(get_ctrl_iface(port)); + if (not iface_sptr) { + throw uhd::assertion_error(str( + boost::format("[%s] Cannot set command time on port '%d'") + % unique_id() % port + )); + } + + iface_sptr->set_time(time_spec_t(0.0)); +} + +void block_ctrl_base::clear(const size_t /* port */) +{ + UHD_RFNOC_BLOCK_TRACE() << "block_ctrl_base::clear() " << std::endl; + // Call parent... + node_ctrl_base::clear(); + // ...then child + BOOST_FOREACH(const size_t port_index, get_ctrl_ports()) { + _clear(port_index); + } +} + +boost::uint32_t block_ctrl_base::get_address(size_t block_port) { + UHD_ASSERT_THROW(block_port < 16); + return (_base_address & 0xFFF0) | (block_port & 0xF); +} + +/*********************************************************************** + * Argument handling + **********************************************************************/ +void block_ctrl_base::set_args(const uhd::device_addr_t &args, const size_t port) +{ + BOOST_FOREACH(const std::string &key, args.keys()) { + if (_tree->exists(get_arg_path(key, port))) { + set_arg(key, args.get(key), port); + } + } +} + +void block_ctrl_base::set_arg(const std::string &key, const std::string &val, const size_t port) +{ + fs_path arg_path = get_arg_path(key, port); + if (not _tree->exists(arg_path / "value")) { + throw uhd::runtime_error(str( + boost::format("Attempting to set uninitialized argument '%s' on block '%s'") + % key % unique_id() + )); + } + + std::string type = _tree->access<std::string>(arg_path / "type").get(); + fs_path arg_val_path = arg_path / "value"; + try { + if (type == "string") { + _tree->access<std::string>(arg_val_path).set(val); + } + else if (type == "int") { + _tree->access<int>(arg_val_path).set(boost::lexical_cast<int>(val)); + } + else if (type == "double") { + _tree->access<double>(arg_val_path).set(boost::lexical_cast<double>(val)); + } + else if (type == "int_vector") { + throw uhd::runtime_error("not yet implemented: int_vector"); + } + } catch (const boost::bad_lexical_cast &) { + throw uhd::value_error(str( + boost::format("Error trying to cast value %s == '%s' to type '%s'") + % key % val % type + )); + } +} + +device_addr_t block_ctrl_base::get_args(const size_t port) const +{ + device_addr_t args; + BOOST_FOREACH(const std::string &key, _tree->list(_root_path / "args" / port)) { + args[key] = get_arg(key); + } + return args; +} + +std::string block_ctrl_base::get_arg(const std::string &key, const size_t port) const +{ + fs_path arg_path = get_arg_path(key, port); + if (not _tree->exists(arg_path / "value")) { + throw uhd::runtime_error(str( + boost::format("Attempting to get uninitialized argument '%s' on block '%s'") + % key % unique_id() + )); + } + + std::string type = _tree->access<std::string>(arg_path / "type").get(); + fs_path arg_val_path = arg_path / "value"; + if (type == "string") { + return _tree->access<std::string>(arg_val_path).get(); + } + else if (type == "int") { + return boost::lexical_cast<std::string>(_tree->access<int>(arg_val_path).get()); + } + else if (type == "double") { + return boost::lexical_cast<std::string>(_tree->access<double>(arg_val_path).get()); + } + else if (type == "int_vector") { + throw uhd::runtime_error("not yet implemented: int_vector"); + } + + UHD_THROW_INVALID_CODE_PATH(); + return ""; +} + +std::string block_ctrl_base::get_arg_type(const std::string &key, const size_t port) const +{ + fs_path arg_type_path = _root_path / "args" / port / key / "type"; + return _tree->access<std::string>(arg_type_path).get(); +} + +stream_sig_t block_ctrl_base::_resolve_port_def(const blockdef::port_t &port_def) const +{ + if (not port_def.is_valid()) { + throw uhd::runtime_error(str( + boost::format("Invalid port definition: %s") % port_def.to_string() + )); + } + + // TODO this entire section is pretty dumb at this point. Needs better + // checks. + stream_sig_t stream_sig; + // Item Type + if (port_def.is_variable("type")) { + std::string var_name = port_def["type"].substr(1); + // TODO check this is even a string + stream_sig.item_type = get_arg(var_name); + } else if (port_def.is_keyword("type")) { + throw uhd::runtime_error("keywords resolution for type not yet implemented"); + } else { + stream_sig.item_type = port_def["type"]; + } + //UHD_RFNOC_BLOCK_TRACE() << " item type: " << stream_sig.item_type << std::endl; + + // Vector length + if (port_def.is_variable("vlen")) { + std::string var_name = port_def["vlen"].substr(1); + stream_sig.vlen = boost::lexical_cast<size_t>(get_arg(var_name)); + } else if (port_def.is_keyword("vlen")) { + throw uhd::runtime_error("keywords resolution for vlen not yet implemented"); + } else { + stream_sig.vlen = boost::lexical_cast<size_t>(port_def["vlen"]); + } + //UHD_RFNOC_BLOCK_TRACE() << " vector length: " << stream_sig.vlen << std::endl; + + // Packet size + if (port_def.is_variable("pkt_size")) { + std::string var_name = port_def["pkt_size"].substr(1); + stream_sig.packet_size = boost::lexical_cast<size_t>(get_arg(var_name)); + } else if (port_def.is_keyword("pkt_size")) { + if (port_def["pkt_size"] != "%vlen") { + throw uhd::runtime_error("generic keywords resolution for pkt_size not yet implemented"); + } + if (stream_sig.vlen == 0) { + stream_sig.packet_size = 0; + } else { + if (stream_sig.item_type.empty()) { + throw uhd::runtime_error("cannot resolve pkt_size if item type is not given"); + } + size_t bpi = uhd::convert::get_bytes_per_item(stream_sig.item_type); + stream_sig.packet_size = stream_sig.vlen * bpi; + } + } else { + stream_sig.packet_size = boost::lexical_cast<size_t>(port_def["pkt_size"]); + } + //UHD_RFNOC_BLOCK_TRACE() << " packet size: " << stream_sig.vlen << std::endl; + + return stream_sig; +} + + +/*********************************************************************** + * Hooks & Derivables + **********************************************************************/ +void block_ctrl_base::_clear(const size_t port) +{ + UHD_RFNOC_BLOCK_TRACE() << "block_ctrl_base::_clear() " << std::endl; + sr_write(SR_CLEAR_TX_FC, 0x00C1EA12, port); // 'CLEAR', but we can write anything, really + sr_write(SR_CLEAR_RX_FC, 0x00C1EA12, port); // 'CLEAR', but we can write anything, really +} + +// vim: sw=4 et: diff --git a/host/lib/rfnoc/block_ctrl_base_factory.cpp b/host/lib/rfnoc/block_ctrl_base_factory.cpp new file mode 100644 index 000000000..aab2ed475 --- /dev/null +++ b/host/lib/rfnoc/block_ctrl_base_factory.cpp @@ -0,0 +1,96 @@ +// +// 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 <boost/format.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/rfnoc/blockdef.hpp> +#include <uhd/rfnoc/block_ctrl_base.hpp> + +#define UHD_FACTORY_LOG() UHD_LOGV(never) + +using namespace uhd; +using namespace uhd::rfnoc; + +typedef uhd::dict<std::string, block_ctrl_base::make_t> block_fcn_reg_t; +// Instantiate the block function registry container +UHD_SINGLETON_FCN(block_fcn_reg_t, get_block_fcn_regs); + +void block_ctrl_base::register_block( + const make_t &make, + const std::string &key +) { + if (get_block_fcn_regs().has_key(key)) { + throw uhd::runtime_error( + str(boost::format("Attempting to register an RFNoC block with key %s for the second time.") % key) + ); + } + + get_block_fcn_regs().set(key, make); +} + +/*! Look up names for blocks in XML files using NoC ID. + */ +static void lookup_block_key(boost::uint64_t noc_id, make_args_t &make_args) +{ + try { + blockdef::sptr bd = blockdef::make_from_noc_id(noc_id); + if (not bd) { + make_args.block_key = DEFAULT_BLOCK_NAME; + make_args.block_name = DEFAULT_BLOCK_NAME; + return; + } + UHD_ASSERT_THROW(bd->is_block()); + make_args.block_key = bd->get_key(); + make_args.block_name = bd->get_name(); + return; + } catch (std::exception &e) { + UHD_MSG(warning) << str(boost::format("Error while looking up name for NoC-ID %016X.\n%s") % noc_id % e.what()) << std::endl; + } + + make_args.block_key = DEFAULT_BLOCK_NAME; + make_args.block_name = DEFAULT_BLOCK_NAME; +} + + +block_ctrl_base::sptr block_ctrl_base::make( + const make_args_t &make_args_, + boost::uint64_t noc_id +) { + UHD_FACTORY_LOG() << "[RFNoC Factory] block_ctrl_base::make() " << std::endl; + make_args_t make_args = make_args_; + + // Check if a block key was specified, in this case, we *must* either + // create a specialized block controller class or throw + if (make_args.block_key.empty()) { + lookup_block_key(noc_id, make_args); + } else if (not get_block_fcn_regs().has_key(make_args.block_key)) { + throw uhd::runtime_error( + str(boost::format("No block controller class registered for key '%s'.") % make_args.block_key) + ); + } + if (not get_block_fcn_regs().has_key(make_args.block_key)) { + make_args.block_key = DEFAULT_BLOCK_NAME; + } + if (make_args.block_name.empty()) { + make_args.block_name = make_args.block_key; + } + + UHD_FACTORY_LOG() << "[RFNoC Factory] Using controller key '" << make_args.block_key << "' and block name '" << make_args.block_name << "'" << std::endl; + return get_block_fcn_regs()[make_args.block_key](make_args); +} + diff --git a/host/lib/rfnoc/block_ctrl_impl.cpp b/host/lib/rfnoc/block_ctrl_impl.cpp new file mode 100644 index 000000000..be21538f3 --- /dev/null +++ b/host/lib/rfnoc/block_ctrl_impl.cpp @@ -0,0 +1,33 @@ +// +// 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 <uhd/rfnoc/block_ctrl.hpp> + +using namespace uhd::rfnoc; + +class block_ctrl_impl : public block_ctrl +{ +public: + UHD_RFNOC_BLOCK_CONSTRUCTOR(block_ctrl) + { + // nop + } + + // Very empty class, this one +}; + +UHD_RFNOC_BLOCK_REGISTER(block_ctrl, DEFAULT_BLOCK_NAME); diff --git a/host/lib/rfnoc/block_id.cpp b/host/lib/rfnoc/block_id.cpp new file mode 100644 index 000000000..55bd5e6f7 --- /dev/null +++ b/host/lib/rfnoc/block_id.cpp @@ -0,0 +1,150 @@ +// +// 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 <boost/format.hpp> +#include <boost/regex.hpp> +#include <boost/lexical_cast.hpp> +#include <uhd/exception.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <uhd/rfnoc/block_id.hpp> + +#include <iostream> + +using namespace uhd::rfnoc; + +block_id_t::block_id_t() : + _device_no(0), + _block_name(""), + _block_ctr(0) +{ +} + +block_id_t::block_id_t(const std::string &block_str) + : _device_no(0), + _block_name(""), + _block_ctr(0) +{ + if (not set(block_str)) { + throw uhd::value_error("block_id_t: Invalid block ID string."); + } +} + +block_id_t::block_id_t( + const size_t device_no, + const std::string &block_name, + const size_t block_ctr +) : _device_no(device_no), + _block_name(block_name), + _block_ctr(block_ctr) +{ + if (not is_valid_blockname(block_name)) { + throw uhd::value_error("block_id_t: Invalid block name."); + } +} + +bool block_id_t::is_valid_blockname(const std::string &block_name) +{ + return boost::regex_match(block_name, boost::regex(VALID_BLOCKNAME_REGEX)); +} + +bool block_id_t::is_valid_block_id(const std::string &block_name) +{ + return boost::regex_match(block_name, boost::regex(VALID_BLOCKID_REGEX)); +} + +std::string block_id_t::to_string() const +{ + return str(boost::format("%d/%s") + % get_device_no() + % get_local() + ); +} + +std::string block_id_t::get_local() const +{ + return str(boost::format("%s_%d") + % get_block_name() + % get_block_count() + ); +} + +uhd::fs_path block_id_t::get_tree_root() const +{ + return str(boost::format("/mboards/%d/xbar/%s") + % get_device_no() + % get_local() + ); +} + +bool block_id_t::match(const std::string &block_str) +{ + boost::cmatch matches; + if (not boost::regex_match(block_str.c_str(), matches, boost::regex(VALID_BLOCKID_REGEX))) { + return false; + } + try { + return (matches[1] == "" or boost::lexical_cast<size_t>(matches[1]) == _device_no) + and (matches[2] == "" or matches[2] == _block_name) + and (matches[3] == "" or boost::lexical_cast<size_t>(matches[3]) == _block_ctr) + and not (matches[1] == "" and matches[2] == "" and matches[3] == ""); + } catch (const std::bad_cast &e) { + return false; + } + return false; +} + +bool block_id_t::set(const std::string &new_name) +{ + boost::cmatch matches; + if (not boost::regex_match(new_name.c_str(), matches, boost::regex(VALID_BLOCKID_REGEX))) { + return false; + } + if (not (matches[1] == "")) { + _device_no = boost::lexical_cast<size_t>(matches[1]); + } + if (not (matches[2] == "")) { + _block_name = matches[2]; + } + if (not (matches[3] == "")) { + _block_ctr = boost::lexical_cast<size_t>(matches[3]); + } + return true; +} + +bool block_id_t::set( + const size_t device_no, + const std::string &block_name, + const size_t block_ctr +) { + if (not set_block_name(block_name)) { + return false; + } + set_device_no(device_no); + set_block_count(block_ctr); + return true; +} + +bool block_id_t::set_block_name(const std::string &block_name) +{ + if (not is_valid_blockname(block_name)) { + return false; + } + _block_name = block_name; + return true; +} + diff --git a/host/lib/rfnoc/blockdef_xml_impl.cpp b/host/lib/rfnoc/blockdef_xml_impl.cpp new file mode 100644 index 000000000..5ff69d512 --- /dev/null +++ b/host/lib/rfnoc/blockdef_xml_impl.cpp @@ -0,0 +1,442 @@ +// +// 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/>. +// + +#include <uhd/exception.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <uhd/rfnoc/blockdef.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/paths.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/filesystem/operations.hpp> +#include <boost/property_tree/ptree.hpp> +#include <boost/property_tree/xml_parser.hpp> +#include <cstdlib> + +using namespace uhd; +using namespace uhd::rfnoc; +namespace fs = boost::filesystem; +namespace pt = boost::property_tree; + +static const fs::path XML_BLOCKS_SUBDIR("blocks"); +static const fs::path XML_COMPONENTS_SUBDIR("components"); +static const fs::path XML_EXTENSION(".xml"); + + +/**************************************************************************** + * port_t stuff + ****************************************************************************/ +const device_addr_t blockdef::port_t::PORT_ARGS( + "name," + "type," + "vlen=0," + "pkt_size=0," + "optional=0," + "bursty=0," + "port," +); + +blockdef::port_t::port_t() +{ + // This guarantees that we can access these keys + // even if they were never initialized: + BOOST_FOREACH(const std::string &key, PORT_ARGS.keys()) { + set(key, PORT_ARGS[key]); + } +} + +bool blockdef::port_t::is_variable(const std::string &key) const +{ + const std::string &val = get(key); + return (val[0] == '$'); +} + +bool blockdef::port_t::is_keyword(const std::string &key) const +{ + const std::string &val = get(key); + return (val[0] == '%'); +} + +bool blockdef::port_t::is_valid() const +{ + // Check we have all the keys: + BOOST_FOREACH(const std::string &key, PORT_ARGS.keys()) { + if (not has_key(key)) { + return false; + } + } + + // Twelve of the clock, all seems well + return true; +} + +std::string blockdef::port_t::to_string() const +{ + std::string result; + BOOST_FOREACH(const std::string &key, PORT_ARGS.keys()) { + if (has_key(key)) { + result += str(boost::format("%s=%s,") % key % get(key)); + } + } + + return result; +} + +/**************************************************************************** + * arg_t stuff + ****************************************************************************/ +const device_addr_t blockdef::arg_t::ARG_ARGS( + // List all tags/args an <arg> can have here: + "name," + "type," + "value," + "check," + "check_message," + "action," + "port=0," +); + +const std::set<std::string> blockdef::arg_t::VALID_TYPES = boost::assign::list_of + // List all tags/args a <type> can have here: + ("string") + ("int") + ("int_vector") + ("double") +; + +blockdef::arg_t::arg_t() +{ + // This guarantees that we can access these keys + // even if they were never initialized: + BOOST_FOREACH(const std::string &key, ARG_ARGS.keys()) { + set(key, ARG_ARGS[key]); + } +} + +bool blockdef::arg_t::is_valid() const +{ + // 1. Check we have all the keys: + BOOST_FOREACH(const std::string &key, ARG_ARGS.keys()) { + if (not has_key(key)) { + return false; + } + } + + // 2. Check arg type is valid + if (not get("type").empty() and not VALID_TYPES.count(get("type"))) { + return false; + } + + // Twelve of the clock, all seems well + return true; +} + +std::string blockdef::arg_t::to_string() const +{ + std::string result; + BOOST_FOREACH(const std::string &key, ARG_ARGS.keys()) { + if (has_key(key)) { + result += str(boost::format("%s=%s,") % key % get(key)); + } + } + + return result; +} + +/**************************************************************************** + * blockdef_impl stuff + ****************************************************************************/ +class blockdef_xml_impl : public blockdef +{ +public: + enum xml_repr_t { + DESCRIBES_BLOCK, + DESCRIBES_COMPONENT + }; + + //! Returns a list of base paths for the XML files. + // It is assumed that block definitions are in a subdir with name + // XML_BLOCKS_SUBDIR and component definitions in a subdir with name + // XML_COMPONENTS_SUBDIR + static std::vector<boost::filesystem::path> get_xml_paths() + { + std::vector<boost::filesystem::path> paths; + + // Path from environment variable + if (std::getenv(XML_PATH_ENV.c_str()) != NULL) { + paths.push_back(boost::filesystem::path(std::getenv(XML_PATH_ENV.c_str()))); + } + + // Finally, the default path + const boost::filesystem::path pkg_path = uhd::get_pkg_path(); + paths.push_back(pkg_path / XML_DEFAULT_PATH); + + return paths; + } + + //! Matches a NoC ID through substring matching + static bool match_noc_id(const std::string &lhs_, boost::uint64_t rhs_) + { + // Sanitize input: Make both values strings with all uppercase + // characters and no leading 0x. Check inputs are valid. + std::string lhs = boost::to_upper_copy(lhs_); + std::string rhs = str(boost::format("%016X") % rhs_); + if (lhs.size() > 2 and lhs[0] == '0' and lhs[1] == 'X') { + lhs = lhs.substr(2); + } + UHD_ASSERT_THROW(rhs.size() == 16); + if (lhs.size() < 4 or lhs.size() > 16) { + throw uhd::value_error(str(boost::format( + "%s is not a valid NoC ID (must be hexadecimal, min 4 and max 16 characters)" + ) % lhs_)); + } + + // OK, all good now. Next, we try and match the substring lhs in rhs: + return (rhs.find(lhs) == 0); + } + + //! Open the file at filename and see if it's a block definition for the given NoC ID + static bool has_noc_id(boost::uint64_t noc_id, const fs::path &filename) + { + pt::ptree propt; + try { + read_xml(filename.string(), propt); + BOOST_FOREACH(pt::ptree::value_type &v, propt.get_child("nocblock.ids")) { + if (v.first == "id" and match_noc_id(v.second.data(), noc_id)) { + return true; + } + } + } catch (std::exception &e) { + UHD_MSG(warning) << "has_noc_id(): caught exception " << e.what() << std::endl; + return false; + } + return false; + } + + blockdef_xml_impl(const fs::path &filename, boost::uint64_t noc_id, xml_repr_t type=DESCRIBES_BLOCK) : + _type(type), + _noc_id(noc_id) + { + //UHD_MSG(status) << "Reading XML file: " << filename.string().c_str() << std::endl; + read_xml(filename.string(), _pt); + try { + // Check key is valid + get_key(); + // Check name is valid + get_name(); + // Check there's at least one port + ports_t in = get_input_ports(); + ports_t out = get_output_ports(); + if (in.empty() and out.empty()) { + throw uhd::runtime_error("Block does not define inputs or outputs."); + } + // Check args are valid + get_args(); + // TODO any more checks? + } catch (const std::exception &e) { + throw uhd::runtime_error(str( + boost::format("Invalid block definition in %s: %s") + % filename.string() % e.what() + )); + } + } + + bool is_block() const + { + return _type == DESCRIBES_BLOCK; + } + + bool is_component() const + { + return _type == DESCRIBES_COMPONENT; + } + + std::string get_key() const + { + try { + return _pt.get<std::string>("nocblock.key"); + } catch (const pt::ptree_bad_path &) { + return _pt.get<std::string>("nocblock.blockname"); + } + } + + std::string get_name() const + { + return _pt.get<std::string>("nocblock.blockname"); + } + + boost::uint64_t noc_id() const + { + return _noc_id; + } + + ports_t get_input_ports() + { + return _get_ports("sink"); + } + + ports_t get_output_ports() + { + return _get_ports("source"); + } + + ports_t _get_ports(const std::string &port_type) + { + std::set<size_t> port_numbers; + size_t n_ports = 0; + ports_t ports; + BOOST_FOREACH(pt::ptree::value_type &v, _pt.get_child("nocblock.ports")) { + if (v.first != port_type) continue; + // Now we have the correct sink or source node: + port_t port; + BOOST_FOREACH(const std::string &key, port_t::PORT_ARGS.keys()) { + port[key] = v.second.get(key, port_t::PORT_ARGS[key]); + } + // We have to be extra-careful with the port numbers: + if (port["port"].empty()) { + port["port"] = boost::lexical_cast<std::string>(n_ports); + } + size_t new_port_number; + try { + new_port_number = boost::lexical_cast<size_t>(port["port"]); + } catch (const boost::bad_lexical_cast &e) { + throw uhd::value_error(str( + boost::format("Invalid port number '%s' on port '%s'") + % port["port"] % port["name"] + )); + } + if (port_numbers.count(new_port_number) or new_port_number > MAX_NUM_PORTS) { + throw uhd::value_error(str( + boost::format("Port '%s' has invalid port number %d!") + % port["name"] % new_port_number + )); + } + port_numbers.insert(new_port_number); + n_ports++; + ports.push_back(port); + } + return ports; + } + + std::vector<size_t> get_all_port_numbers() + { + std::set<size_t> set_ports; + BOOST_FOREACH(const port_t &port, get_input_ports()) { + set_ports.insert(boost::lexical_cast<size_t>(port["port"])); + } + BOOST_FOREACH(const port_t &port, get_output_ports()) { + set_ports.insert(boost::lexical_cast<size_t>(port["port"])); + } + return std::vector<size_t>(set_ports.begin(), set_ports.end()); + } + + + blockdef::args_t get_args() + { + args_t args; + bool is_valid = true; + pt::ptree def; + BOOST_FOREACH(pt::ptree::value_type &v, _pt.get_child("nocblock.args", def)) { + arg_t arg; + if (v.first != "arg") continue; + BOOST_FOREACH(const std::string &key, arg_t::ARG_ARGS.keys()) { + arg[key] = v.second.get(key, arg_t::ARG_ARGS[key]); + } + if (arg["type"].empty()) { + arg["type"] = "string"; + } + if (not arg.is_valid()) { + UHD_MSG(warning) << boost::format("Found invalid argument: %s") % arg.to_string() << std::endl; + is_valid = false; + } + args.push_back(arg); + } + if (not is_valid) { + throw uhd::runtime_error(str( + boost::format("Found invalid arguments for block %s.") + % get_name() + )); + } + return args; + } + + registers_t get_settings_registers() + { + return _get_regs("setreg"); + } + + registers_t get_readback_registers() + { + return _get_regs("readback"); + } + + registers_t _get_regs(const std::string ®_type) + { + registers_t registers; + pt::ptree def; + BOOST_FOREACH(pt::ptree::value_type &v, _pt.get_child("nocblock.registers", def)) { + if (v.first != reg_type) continue; + registers[v.second.get<std::string>("name")] = + boost::lexical_cast<size_t>(v.second.get<size_t>("address")); + } + return registers; + } + + +private: + + //! Tells us if is this for a NoC block, or a component. + const xml_repr_t _type; + //! The NoC-ID as reported (there may be several valid NoC IDs, this is the one used) + const boost::uint64_t _noc_id; + + //! This is a boost property tree, not the same as + // our property tree. + pt::ptree _pt; + +}; + +blockdef::sptr blockdef::make_from_noc_id(boost::uint64_t noc_id) +{ + std::vector<fs::path> paths = blockdef_xml_impl::get_xml_paths(); + // Iterate over all paths + BOOST_FOREACH(const fs::path &base_path, paths) { + fs::path this_path = base_path / XML_BLOCKS_SUBDIR; + if (not fs::exists(this_path) or not fs::is_directory(this_path)) { + continue; + } + // Iterate over all .xml files + fs::directory_iterator end_itr; + for (fs::directory_iterator i(this_path); i != end_itr; ++i) { + if (not fs::exists(*i) or fs::is_directory(*i) or fs::is_empty(*i)) { + continue; + } + if (i->path().filename().extension() != XML_EXTENSION) { + continue; + } + if (blockdef_xml_impl::has_noc_id(noc_id, i->path())) { + return blockdef::sptr(new blockdef_xml_impl(i->path(), noc_id)); + } + } + } + + return blockdef::sptr(); +} +// vim: sw=4 et: diff --git a/host/lib/rfnoc/ctrl_iface.cpp b/host/lib/rfnoc/ctrl_iface.cpp new file mode 100644 index 000000000..4518e88cf --- /dev/null +++ b/host/lib/rfnoc/ctrl_iface.cpp @@ -0,0 +1,376 @@ +// +// Copyright 2012-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 "ctrl_iface.hpp" +#include "async_packet_handler.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/types/sid.hpp> +#include <uhd/transport/chdr.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/thread/thread.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <queue> + +using namespace uhd; +using namespace uhd::rfnoc; +using namespace uhd::transport; + +static const double ACK_TIMEOUT = 2.0; //supposed to be worst case practical timeout +static const double MASSIVE_TIMEOUT = 10.0; //for when we wait on a timed command +static const size_t SR_READBACK = 32; + +ctrl_iface::~ctrl_iface(void){ + /* NOP */ +} + +class ctrl_iface_impl: public ctrl_iface +{ +public: + + ctrl_iface_impl(const bool big_endian, + uhd::transport::zero_copy_if::sptr ctrl_xport, + uhd::transport::zero_copy_if::sptr resp_xport, + const boost::uint32_t sid, const std::string &name + ) : + _link_type(vrt::if_packet_info_t::LINK_TYPE_CHDR), + _packet_type(vrt::if_packet_info_t::PACKET_TYPE_CONTEXT), + _bige(big_endian), + _ctrl_xport(ctrl_xport), _resp_xport(resp_xport), + _sid(sid), + _name(name), + _seq_out(0), + _timeout(ACK_TIMEOUT), + _resp_queue(128/*max response msgs*/), + _resp_queue_size(_resp_xport ? _resp_xport->get_num_recv_frames() : 3), + _rb_address(uhd::rfnoc::SR_READBACK) + { + if (resp_xport) { + while (resp_xport->get_recv_buff(0.0)) {} //flush + } + this->set_time(uhd::time_spec_t(0.0)); + this->set_tick_rate(1.0); //something possible but bogus + } + + ~ctrl_iface_impl(void) + { + _timeout = ACK_TIMEOUT; //reset timeout to something small + UHD_SAFE_CALL( + this->peek32(0);//dummy peek with the purpose of ack'ing all packets + _async_task.reset();//now its ok to release the task + ) + } + + /******************************************************************* + * Peek and poke 32 bit implementation + ******************************************************************/ + void poke32(const wb_addr_type addr, const boost::uint32_t data) + { + boost::mutex::scoped_lock lock(_mutex); + this->send_pkt(addr/4, data); + this->wait_for_ack(false); + } + + boost::uint32_t peek32(const wb_addr_type addr) + { + boost::mutex::scoped_lock lock(_mutex); + this->send_pkt(_rb_address, addr/8); + const boost::uint64_t res = this->wait_for_ack(true); + const boost::uint32_t lo = boost::uint32_t(res & 0xffffffff); + const boost::uint32_t hi = boost::uint32_t(res >> 32); + return ((addr/4) & 0x1)? hi : lo; + } + + boost::uint64_t peek64(const wb_addr_type addr) + { + boost::mutex::scoped_lock lock(_mutex); + this->send_pkt(_rb_address, addr/8); + return this->wait_for_ack(true); + } + + /******************************************************************* + * Update methods for time + ******************************************************************/ + void set_time(const uhd::time_spec_t &time) + { + boost::mutex::scoped_lock lock(_mutex); + _time = time; + _use_time = _time != uhd::time_spec_t(0.0); + if (_use_time) _timeout = MASSIVE_TIMEOUT; //permanently sets larger timeout + } + + uhd::time_spec_t get_time(void) + { + boost::mutex::scoped_lock lock(_mutex); + return _time; + } + + void set_tick_rate(const double rate) + { + boost::mutex::scoped_lock lock(_mutex); + _tick_rate = rate; + } + +private: + // This is the buffer type for messages in radio control core. + struct resp_buff_type + { + boost::uint32_t data[8]; + }; + + /******************************************************************* + * Primary control and interaction private methods + ******************************************************************/ + UHD_INLINE void send_pkt(const boost::uint32_t addr, const boost::uint32_t data = 0) + { + managed_send_buffer::sptr buff = _ctrl_xport->get_send_buff(0.0); + if (not buff) { + throw uhd::runtime_error("fifo ctrl timed out getting a send buffer"); + } + boost::uint32_t *pkt = buff->cast<boost::uint32_t *>(); + + //load packet info + vrt::if_packet_info_t packet_info; + packet_info.link_type = _link_type; + packet_info.packet_type = _packet_type; + packet_info.num_payload_words32 = 2; + packet_info.num_payload_bytes = packet_info.num_payload_words32*sizeof(boost::uint32_t); + packet_info.packet_count = _seq_out; + packet_info.tsf = _time.to_ticks(_tick_rate); + 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 = _use_time; + packet_info.has_tlr = false; + + //load header + if (_bige) vrt::if_hdr_pack_be(pkt, packet_info); + else vrt::if_hdr_pack_le(pkt, packet_info); + + //load payload + pkt[packet_info.num_header_words32+0] = (_bige)? uhd::htonx(addr) : uhd::htowx(addr); + pkt[packet_info.num_header_words32+1] = (_bige)? uhd::htonx(data) : uhd::htowx(data); + //UHD_MSG(status) << boost::format("0x%08x, 0x%08x\n") % addr % data; + //send the buffer over the interface + _outstanding_seqs.push(_seq_out); + buff->commit(sizeof(boost::uint32_t)*(packet_info.num_packet_words32)); + + _seq_out++;//inc seq for next call + } + + UHD_INLINE boost::uint64_t wait_for_ack(const bool readback) + { + while (readback or (_outstanding_seqs.size() >= _resp_queue_size)) + { + //get seq to ack from outstanding packets list + UHD_ASSERT_THROW(not _outstanding_seqs.empty()); + const size_t seq_to_ack = _outstanding_seqs.front(); + _outstanding_seqs.pop(); + + //parse the packet + vrt::if_packet_info_t packet_info; + resp_buff_type resp_buff; + memset(&resp_buff, 0x00, sizeof(resp_buff)); + boost::uint32_t const *pkt = NULL; + managed_recv_buffer::sptr buff; + + //get buffer from response endpoint - or die in timeout + if (_resp_xport) + { + buff = _resp_xport->get_recv_buff(_timeout); + try + { + UHD_ASSERT_THROW(bool(buff)); + UHD_ASSERT_THROW(buff->size() > 0); + } + catch(const std::exception &ex) + { + throw uhd::io_error(str(boost::format("Radio ctrl (%s) no response packet - %s") % _name % ex.what())); + } + pkt = buff->cast<const boost::uint32_t *>(); + packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); + } + + //get buffer from response endpoint - or die in timeout + else + { + /* + * Couldn't get message with haste. + * Now check both possible queues for messages. + * Messages should come in on _resp_queue, + * but could end up in dump_queue. + * If we don't get a message --> Die in timeout. + */ + double accum_timeout = 0.0; + const double short_timeout = 0.005; // == 5ms + while(not ((_resp_queue.pop_with_haste(resp_buff)) + || (check_dump_queue(resp_buff)) + || (_resp_queue.pop_with_timed_wait(resp_buff, short_timeout)) + )){ + /* + * If a message couldn't be received within a given timeout + * --> throw AssertionError! + */ + accum_timeout += short_timeout; + UHD_ASSERT_THROW(accum_timeout < _timeout); + } + + pkt = resp_buff.data; + packet_info.num_packet_words32 = sizeof(resp_buff)/sizeof(boost::uint32_t); + } + + //parse the buffer + try + { + packet_info.link_type = _link_type; + if (_bige) vrt::chdr::if_hdr_unpack_be(pkt, packet_info); + else vrt::chdr::if_hdr_unpack_le(pkt, packet_info); + } + catch(const std::exception &ex) + { + UHD_MSG(error) << "[" << _name << "] Radio ctrl bad VITA packet: " << ex.what() << std::endl; + if (buff){ + UHD_MSG(status) << boost::format("%08X") % pkt[0] << std::endl; + UHD_MSG(status) << boost::format("%08X") % pkt[1] << std::endl; + UHD_MSG(status) << boost::format("%08X") % pkt[2] << std::endl; + UHD_MSG(status) << boost::format("%08X") % pkt[3] << std::endl; + } + else{ + UHD_MSG(status) << "buff is NULL" << std::endl; + } + } + + //check the buffer + try + { + UHD_ASSERT_THROW(packet_info.has_sid); + if (packet_info.sid != boost::uint32_t((_sid >> 16) | (_sid << 16))) { + throw uhd::io_error( + str( + boost::format("Expected SID: %s Received SID: %s") + % uhd::sid_t(_sid).reversed().to_pp_string_hex() + % uhd::sid_t(packet_info.sid).to_pp_string_hex() + ) + ); + } + + if (packet_info.packet_count != (seq_to_ack & 0xfff)) { + throw uhd::io_error( + str( + boost::format("Expected packet index: %d Received index: %d") + % packet_info.packet_count + % (seq_to_ack & 0xfff) + ) + ); + } + + UHD_ASSERT_THROW(packet_info.num_payload_words32 == 2); + //UHD_ASSERT_THROW(packet_info.packet_type == _packet_type); + } + catch(const std::exception &ex) + { + throw uhd::io_error(str(boost::format("Radio ctrl (%s) packet parse error - %s") % _name % ex.what())); + } + + //return the readback value + if (readback and _outstanding_seqs.empty()) + { + const boost::uint64_t hi = (_bige)? uhd::ntohx(pkt[packet_info.num_header_words32+0]) : uhd::wtohx(pkt[packet_info.num_header_words32+0]); + const boost::uint64_t lo = (_bige)? uhd::ntohx(pkt[packet_info.num_header_words32+1]) : uhd::wtohx(pkt[packet_info.num_header_words32+1]); + return ((hi << 32) | lo); + } + } + + return 0; + } + + /* + * If ctrl_core waits for a message that didn't arrive it can search for it in the dump queue. + * This actually happens during shutdown. + * handle_async_task can't access queue anymore thus it returns the corresponding message. + * msg_task class implements a dump_queue to store such messages. + * With check_dump_queue we can check if a message we are waiting for got stranded there. + * If a message got stuck we get it here and push it onto our own message_queue. + */ + bool check_dump_queue(resp_buff_type& b) { + const size_t min_buff_size = 8; // Same value as in b200_io_impl->handle_async_task + boost::uint32_t recv_sid = (((_sid)<<16)|((_sid)>>16)); + uhd::msg_task::msg_payload_t msg; + do{ + msg = _async_task->get_msg_from_dump_queue(recv_sid); + } + while(msg.size() < min_buff_size && msg.size() != 0); + + if(msg.size() >= min_buff_size) { + memcpy(b.data, &msg.front(), std::min(msg.size(), sizeof(b.data))); + return true; + } + return false; + } + + void push_response(const boost::uint32_t *buff) + { + resp_buff_type resp_buff; + std::memcpy(resp_buff.data, buff, sizeof(resp_buff)); + _resp_queue.push_with_haste(resp_buff); + } + + void hold_task(uhd::msg_task::sptr task) + { + _async_task = task; + } + + const vrt::if_packet_info_t::link_type_t _link_type; + const vrt::if_packet_info_t::packet_type_t _packet_type; + const bool _bige; + const uhd::transport::zero_copy_if::sptr _ctrl_xport; + const uhd::transport::zero_copy_if::sptr _resp_xport; + uhd::msg_task::sptr _async_task; + const boost::uint32_t _sid; + const std::string _name; + boost::mutex _mutex; + size_t _seq_out; + uhd::time_spec_t _time; + bool _use_time; + double _tick_rate; + double _timeout; + std::queue<size_t> _outstanding_seqs; + bounded_buffer<resp_buff_type> _resp_queue; + const size_t _resp_queue_size; + + const size_t _rb_address; +}; + +ctrl_iface::sptr ctrl_iface::make( + const bool big_endian, + zero_copy_if::sptr ctrl_xport, + zero_copy_if::sptr resp_xport, + const boost::uint32_t sid, + const std::string &name +) { + return sptr(new ctrl_iface_impl( + big_endian, ctrl_xport, resp_xport, sid, name + )); +} diff --git a/host/lib/rfnoc/ctrl_iface.hpp b/host/lib/rfnoc/ctrl_iface.hpp new file mode 100644 index 000000000..4141b6583 --- /dev/null +++ b/host/lib/rfnoc/ctrl_iface.hpp @@ -0,0 +1,68 @@ +// +// Copyright 2012-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_RFNOC_CTRL_IFACE_HPP +#define INCLUDED_LIBUHD_RFNOC_CTRL_IFACE_HPP + +#include <uhd/utils/msg_task.hpp> +#include <uhd/types/time_spec.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <uhd/types/wb_iface.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <string> + +namespace uhd { namespace rfnoc { + +/*! + * Provide access to peek, poke for the radio ctrl module + */ +class ctrl_iface : public uhd::timed_wb_iface +{ +public: + typedef boost::shared_ptr<ctrl_iface> sptr; + + virtual ~ctrl_iface(void) = 0; + + //! Make a new control object + static sptr make( + const bool big_endian, + uhd::transport::zero_copy_if::sptr ctrl_xport, + uhd::transport::zero_copy_if::sptr resp_xport, + const boost::uint32_t sid, + const std::string &name = "0" + ); + + //! Hold a ref to a task thats feeding push response + virtual void hold_task(uhd::msg_task::sptr task) = 0; + + //! Push a response externall (resp_xport is NULL) + virtual void push_response(const boost::uint32_t *buff) = 0; + + //! Set the command time that will activate + virtual void set_time(const uhd::time_spec_t &time) = 0; + + //! Get the command time that will activate + virtual uhd::time_spec_t get_time(void) = 0; + + //! Set the tick rate (converting time into ticks) + virtual void set_tick_rate(const double rate) = 0; +}; + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_CTRL_IFACE_HPP */ diff --git a/host/lib/rfnoc/ddc_block_ctrl_impl.cpp b/host/lib/rfnoc/ddc_block_ctrl_impl.cpp new file mode 100644 index 000000000..2aac22ca4 --- /dev/null +++ b/host/lib/rfnoc/ddc_block_ctrl_impl.cpp @@ -0,0 +1,281 @@ +// +// Copyright 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 "dsp_core_utils.hpp" +#include <uhd/rfnoc/ddc_block_ctrl.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/convert.hpp> +#include <uhd/types/ranges.hpp> +#include <boost/math/special_functions/round.hpp> +#include <cmath> + +using namespace uhd::rfnoc; + +// TODO move this to a central location +template <class T> T ceil_log2(T num){ + return std::ceil(std::log(num)/std::log(T(2))); +} + +// TODO remove this once we have actual lambdas +static double lambda_forward_prop(uhd::property_tree::sptr tree, uhd::fs_path prop, double value) +{ + return tree->access<double>(prop).set(value).get(); +} + +static double lambda_forward_prop(uhd::property_tree::sptr tree, uhd::fs_path prop) +{ + return tree->access<double>(prop).get(); +} + +class ddc_block_ctrl_impl : public ddc_block_ctrl +{ +public: + static const size_t NUM_HALFBANDS = 3; + static const size_t CIC_MAX_DECIM = 255; + + UHD_RFNOC_BLOCK_CONSTRUCTOR(ddc_block_ctrl) + { + // Argument/prop tree hooks + for (size_t chan = 0; chan < get_input_ports().size(); chan++) { + double default_freq = get_arg<double>("freq", chan); + _tree->access<double>(get_arg_path("freq/value", chan)) + .set_coercer(boost::bind(&ddc_block_ctrl_impl::set_freq, this, _1, chan)) + .set(default_freq); + ; + double default_output_rate = get_arg<double>("output_rate", chan); + _tree->access<double>(get_arg_path("output_rate/value", chan)) + .set_coercer(boost::bind(&ddc_block_ctrl_impl::set_output_rate, this, _1, chan)) + .set(default_output_rate) + ; + _tree->access<double>(get_arg_path("input_rate/value", chan)) + .add_coerced_subscriber(boost::bind(&ddc_block_ctrl_impl::set_input_rate, this, _1, chan)) + ; + + // Legacy properties (for backward compat w/ multi_usrp) + const uhd::fs_path dsp_base_path = _root_path / "legacy_api" / chan; + // Legacy properties + _tree->create<double>(dsp_base_path / "rate/value") + .set_coercer(boost::bind(&lambda_forward_prop, _tree, get_arg_path("output_rate/value", chan), _1)) + .set_publisher(boost::bind(&lambda_forward_prop, _tree, get_arg_path("output_rate/value", chan))) + ; + _tree->create<uhd::meta_range_t>(dsp_base_path / "rate/range") + .set_publisher(boost::bind(&ddc_block_ctrl_impl::get_output_rates, this)) + ; + _tree->create<double>(dsp_base_path / "freq/value") + .set_coercer(boost::bind(&lambda_forward_prop, _tree, get_arg_path("freq/value", chan), _1)) + .set_publisher(boost::bind(&lambda_forward_prop, _tree, get_arg_path("freq/value", chan))) + ; + _tree->create<uhd::meta_range_t>(dsp_base_path / "freq/range") + .set_publisher(boost::bind(&ddc_block_ctrl_impl::get_freq_range, this)) + ; + _tree->access<uhd::time_spec_t>("time/cmd") + .add_coerced_subscriber(boost::bind(&block_ctrl_base::set_command_time, this, _1, chan)) + ; + if (_tree->exists("tick_rate")) { + const double tick_rate = _tree->access<double>("tick_rate").get(); + set_command_tick_rate(tick_rate, chan); + _tree->access<double>("tick_rate") + .add_coerced_subscriber(boost::bind(&block_ctrl_base::set_command_tick_rate, this, _1, chan)) + ; + } + + // Rate 1:1 by default + sr_write("N", 1, chan); + sr_write("M", 1, chan); + sr_write("CONFIG", 1, chan); // Enable clear EOB + } + } // end ctor + virtual ~ddc_block_ctrl_impl() {}; + + double get_output_scale_factor(size_t port=ANY_PORT) + { + port = port == ANY_PORT ? 0 : port; + if (not (_rx_streamer_active.count(port) and _rx_streamer_active.at(port))) { + return SCALE_UNDEFINED; + } + return get_arg<double>("scalar_correction", port); + } + + double get_input_samp_rate(size_t port=ANY_PORT) + { + port = port == ANY_PORT ? 0 : port; + if (not (_tx_streamer_active.count(port) and _tx_streamer_active.at(port))) { + return RATE_UNDEFINED; + } + return get_arg<double>("input_rate", port); + } + + double get_output_samp_rate(size_t port=ANY_PORT) + { + port = port == ANY_PORT ? 0 : port; + if (not (_rx_streamer_active.count(port) and _rx_streamer_active.at(port))) { + return RATE_UNDEFINED; + } + return get_arg<double>("output_rate", port); + } + + + void issue_stream_cmd( + const uhd::stream_cmd_t &stream_cmd_, + const size_t chan + ) { + UHD_RFNOC_BLOCK_TRACE() << "ddc_block_ctrl_base::issue_stream_cmd()" << std::endl; + + if (list_upstream_nodes().count(chan) == 0) { + UHD_MSG(status) << "No upstream blocks." << std::endl; + return; + } + + uhd::stream_cmd_t stream_cmd = stream_cmd_; + if (stream_cmd.stream_mode == uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE or + stream_cmd.stream_mode == uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE) { + size_t decimation = get_arg<double>("input_rate", chan) / get_arg<double>("output_rate", chan); + stream_cmd.num_samps *= decimation; + } + + source_node_ctrl::sptr this_upstream_block_ctrl = + boost::dynamic_pointer_cast<source_node_ctrl>(list_upstream_nodes().at(chan).lock()); + if (this_upstream_block_ctrl) { + this_upstream_block_ctrl->issue_stream_cmd( + stream_cmd, + get_upstream_port(chan) + ); + } + } + +private: + + //! Set the CORDIC frequency shift the signal to \p requested_freq + double set_freq(const double requested_freq, const size_t chan) + { + const double input_rate = get_arg<double>("input_rate"); + double actual_freq; + int32_t freq_word; + get_freq_and_freq_word(requested_freq, input_rate, actual_freq, freq_word); + sr_write("CORDIC_FREQ", uint32_t(freq_word), chan); + return actual_freq; + } + + //! Return a range of valid frequencies the CORDIC can tune to + uhd::meta_range_t get_freq_range(void) + { + const double input_rate = get_arg<double>("input_rate"); + return uhd::meta_range_t( + -input_rate/2, + +input_rate/2, + input_rate/std::pow(2.0, 32) + ); + } + + // FIXME this misses a whole bunch of valid rates. Anything with CIC decim <= 255 + // is OK. + uhd::meta_range_t get_output_rates(void) + { + uhd::meta_range_t range; + const double input_rate = get_arg<double>("input_rate"); + for (int decim = 1024; decim > 512; decim -= 8){ + range.push_back(uhd::range_t(input_rate/decim)); + } + for (int decim = 512; decim > 256; decim -= 4){ + range.push_back(uhd::range_t(input_rate/decim)); + } + for (int decim = 256; decim > 128; decim -= 2){ + range.push_back(uhd::range_t(input_rate/decim)); + } + for (int decim = 128; decim >= 1; decim -= 1){ + range.push_back(uhd::range_t(input_rate/decim)); + } + return range; + } + + double set_output_rate(const int requested_rate, const size_t chan) + { + const double input_rate = get_arg<double>("input_rate"); + const size_t decim_rate = boost::math::iround(input_rate/this->get_output_rates().clip(requested_rate, true)); + size_t decim = decim_rate; + + // The FPGA knows which halfbands to enable for any given value of hb_enable. + uint32_t hb_enable = 0; + while ((decim % 2 == 0) and hb_enable < NUM_HALFBANDS) { + hb_enable++; + decim /= 2; + } + UHD_ASSERT_THROW(hb_enable <= NUM_HALFBANDS); + UHD_ASSERT_THROW(decim <= CIC_MAX_DECIM); + // What we can't cover with halfbands, we do with the CIC + sr_write("DECIM_WORD", (hb_enable << 8) | (decim & 0xff), chan); + + // Rate change = M/N + sr_write("N", std::pow(2.0, double(hb_enable)) * (decim & 0xff), chan); + sr_write("M", 1, chan); + + if (decim > 1 and hb_enable == 0) { + UHD_MSG(warning) << boost::format( + "The requested decimation is odd; the user should expect passband CIC rolloff.\n" + "Select an even decimation to ensure that a halfband filter is enabled.\n" + "Decimations factorable by 4 will enable 2 halfbands, those factorable by 8 will enable 3 halfbands.\n" + "decimation = dsp_rate/samp_rate -> %d = (%f MHz)/(%f MHz)\n" + ) % decim_rate % (input_rate/1e6) % (requested_rate/1e6); + } + + // Caclulate algorithmic gain of CIC for a given decimation. + // For Ettus CIC R=decim, M=1, N=4. Gain = (R * M) ^ N + const double rate_pow = std::pow(double(decim & 0xff), 4); + // Calculate compensation gain values for algorithmic gain of CORDIC and CIC taking into account + // gain compensation blocks already hardcoded in place in DDC (that provide simple 1/2^n gain compensation). + // CORDIC algorithmic gain limits asymptotically around 1.647 after many iterations. + static const double CORDIC_GAIN = 1.648; + // + // The polar rotation of [I,Q] = [1,1] by Pi/8 also yields max magnitude of SQRT(2) (~1.4142) however + // input to the CORDIC thats outside the unit circle can only be sourced from a saturated RF frontend. + // To provide additional dynamic range head room accordingly using scale factor applied at egress from DDC would + // cost us small signal performance, thus we do no provide compensation gain for a saturated front end and allow + // the signal to clip in the H/W as needed. If we wished to avoid the signal clipping in these circumstances then adjust code to read: + // _scaling_adjustment = std::pow(2, ceil_log2(rate_pow))/(CORDIC_GAIN*rate_pow*1.415); + const double scaling_adjustment = std::pow(2, ceil_log2(rate_pow))/(CORDIC_GAIN*rate_pow); + update_scalar(scaling_adjustment, chan); + return input_rate/decim_rate; + } + + //! Set frequency and decimation again + void set_input_rate(const double /* rate */, const size_t chan) + { + const double desired_freq = _tree->access<double>(get_arg_path("freq", chan) / "value").get_desired(); + set_arg<double>("freq", desired_freq, chan); + const double desired_output_rate = _tree->access<double>(get_arg_path("output_rate", chan) / "value").get_desired(); + set_arg<double>("output_rate", desired_output_rate, chan); + } + + // Calculate compensation gain values for algorithmic gain of CORDIC and CIC taking into account + // gain compensation blocks already hardcoded in place in DDC (that provide simple 1/2^n gain compensation). + // Further more factor in OTW format which adds further gain factor to weight output samples correctly. + void update_scalar(const double scalar, const size_t chan) + { + const double target_scalar = (1 << 15) * scalar; + const int32_t actual_scalar = boost::math::iround(target_scalar); + // Calculate the error introduced by using integer representation for the scalar, can be corrected in host later. + const double scalar_correction = + target_scalar / actual_scalar / double(1 << 15) // Rounding error, normalized to 1.0 + * get_arg<double>("fullscale"); // Scaling requested by host + set_arg<double>("scalar_correction", scalar_correction, chan); + // Write DDC with scaling correction for CIC and CORDIC that maximizes dynamic range in 32/16/12/8bits. + sr_write("SCALE_IQ", actual_scalar, chan); + } + +}; + +UHD_RFNOC_BLOCK_REGISTER(ddc_block_ctrl, "DDC"); diff --git a/host/lib/rfnoc/dma_fifo_block_ctrl_impl.cpp b/host/lib/rfnoc/dma_fifo_block_ctrl_impl.cpp new file mode 100644 index 000000000..5f476074b --- /dev/null +++ b/host/lib/rfnoc/dma_fifo_block_ctrl_impl.cpp @@ -0,0 +1,122 @@ +// +// Copyright 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 <uhd/rfnoc/dma_fifo_block_ctrl.hpp> +#include "dma_fifo_core_3000.hpp" +#include "wb_iface_adapter.hpp" +#include <uhd/convert.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/types/wb_iface.hpp> +#include <boost/make_shared.hpp> +#include <boost/thread/mutex.hpp> + +using namespace uhd; +using namespace uhd::rfnoc; + +//TODO (Ashish): This should come from the framework +static const double BUS_CLK_RATE = 166.67e6; + +class dma_fifo_block_ctrl_impl : public dma_fifo_block_ctrl +{ +public: + static const uint32_t DEFAULT_SIZE = 32*1024*1024; + + UHD_RFNOC_BLOCK_CONSTRUCTOR(dma_fifo_block_ctrl) + { + _perifs.resize(get_input_ports().size()); + for(size_t i = 0; i < _perifs.size(); i++) { + _perifs[i].ctrl = boost::make_shared<wb_iface_adapter>( + // poke32 functor + boost::bind( + static_cast< void (block_ctrl_base::*)(const boost::uint32_t, const boost::uint32_t, const size_t) >(&block_ctrl_base::sr_write), + this, _1, _2, i + ), + // peek32 functor + boost::bind( + static_cast< boost::uint32_t (block_ctrl_base::*)(const boost::uint32_t, const size_t) >(&block_ctrl_base::user_reg_read32), + this, + _1, i + ), + // peek64 functor + boost::bind( + static_cast< boost::uint64_t (block_ctrl_base::*)(const boost::uint32_t, const size_t) >(&block_ctrl_base::user_reg_read64), + this, + _1, i + ) + ); + static const uint32_t USER_SR_BASE = 128*4; + static const uint32_t USER_RB_BASE = 0; //Don't care + _perifs[i].base_addr = DEFAULT_SIZE*i; + _perifs[i].depth = DEFAULT_SIZE; + _perifs[i].core = dma_fifo_core_3000::make(_perifs[i].ctrl, USER_SR_BASE, USER_RB_BASE); + _perifs[i].core->resize(_perifs[i].base_addr, _perifs[i].depth); + UHD_MSG(status) << boost::format("[DMA FIFO] Running BIST for FIFO %d... ") % i; + if (_perifs[i].core->ext_bist_supported()) { + boost::uint32_t bisterr = _perifs[i].core->run_bist(); + if (bisterr != 0) { + throw uhd::runtime_error(str(boost::format("BIST failed! (code: %d)\n") % bisterr)); + } else { + double throughput = _perifs[i].core->get_bist_throughput(BUS_CLK_RATE); + UHD_MSG(status) << (boost::format("pass (Throughput: %.1fMB/s)") % (throughput/1e6)) << std::endl; + } + } else { + if (_perifs[i].core->run_bist() == 0) { + UHD_MSG(status) << "pass\n"; + } else { + throw uhd::runtime_error("BIST failed!\n"); + } + } + _tree->access<int>(get_arg_path("base_addr/value", i)) + .add_coerced_subscriber(boost::bind(&dma_fifo_block_ctrl_impl::resize, this, _1, boost::ref(_perifs[i].depth), i)) + .set(_perifs[i].base_addr) + ; + _tree->access<int>(get_arg_path("depth/value", i)) + .add_coerced_subscriber(boost::bind(&dma_fifo_block_ctrl_impl::resize, this, boost::ref(_perifs[i].base_addr), _1, i)) + .set(_perifs[i].depth) + ; + } + } + + void resize(const uint32_t base_addr, const uint32_t depth, const size_t chan) { + boost::lock_guard<boost::mutex> lock(_config_mutex); + _perifs[chan].base_addr = base_addr; + _perifs[chan].depth = depth; + _perifs[chan].core->resize(base_addr, depth); + } + + uint32_t get_base_addr(const size_t chan) const { + return _perifs[chan].base_addr; + } + + uint32_t get_depth(const size_t chan) const { + return _perifs[chan].depth; + } + +private: + struct fifo_perifs_t + { + wb_iface::sptr ctrl; + dma_fifo_core_3000::sptr core; + uint32_t base_addr; + uint32_t depth; + }; + std::vector<fifo_perifs_t> _perifs; + + boost::mutex _config_mutex; +}; + +UHD_RFNOC_BLOCK_REGISTER(dma_fifo_block_ctrl, "DmaFIFO"); diff --git a/host/lib/rfnoc/duc_block_ctrl_impl.cpp b/host/lib/rfnoc/duc_block_ctrl_impl.cpp new file mode 100644 index 000000000..0340ba0d6 --- /dev/null +++ b/host/lib/rfnoc/duc_block_ctrl_impl.cpp @@ -0,0 +1,268 @@ +// +// Copyright 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 "dsp_core_utils.hpp" +#include <uhd/rfnoc/duc_block_ctrl.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/convert.hpp> +#include <uhd/types/ranges.hpp> +#include <boost/math/special_functions/round.hpp> +#include <cmath> + +using namespace uhd::rfnoc; + +// TODO move this to a central location +template <class T> T ceil_log2(T num){ + return std::ceil(std::log(num)/std::log(T(2))); +} + +// TODO remove this once we have actual lambdas +static double lambda_forward_prop(uhd::property_tree::sptr tree, uhd::fs_path prop, double value) +{ + return tree->access<double>(prop).set(value).get(); +} + +static double lambda_forward_prop(uhd::property_tree::sptr tree, uhd::fs_path prop) +{ + return tree->access<double>(prop).get(); +} + +class duc_block_ctrl_impl : public duc_block_ctrl +{ +public: + static const size_t NUM_HALFBANDS = 2; + static const size_t CIC_MAX_INTERP = 128; + + UHD_RFNOC_BLOCK_CONSTRUCTOR(duc_block_ctrl) + { + // Argument/prop tree hooks + for (size_t chan = 0; chan < get_input_ports().size(); chan++) { + double default_freq = get_arg<double>("freq", chan); + _tree->access<double>(get_arg_path("freq/value", chan)) + .set_coercer(boost::bind(&duc_block_ctrl_impl::set_freq, this, _1, chan)) + .set(default_freq); + ; + double default_input_rate = get_arg<double>("input_rate", chan); + _tree->access<double>(get_arg_path("input_rate/value", chan)) + .set_coercer(boost::bind(&duc_block_ctrl_impl::set_input_rate, this, _1, chan)) + .set(default_input_rate) + ; + _tree->access<double>(get_arg_path("output_rate/value", chan)) + .add_coerced_subscriber(boost::bind(&duc_block_ctrl_impl::set_output_rate, this, _1, chan)) + ; + + // Legacy properties (for backward compat w/ multi_usrp) + const uhd::fs_path dsp_base_path = _root_path / "legacy_api" / chan; + // Legacy properties + _tree->create<double>(dsp_base_path / "rate/value") + .set_coercer(boost::bind(&lambda_forward_prop, _tree, get_arg_path("input_rate/value", chan), _1)) + .set_publisher(boost::bind(&lambda_forward_prop, _tree, get_arg_path("input_rate/value", chan))) + ; + _tree->create<uhd::meta_range_t>(dsp_base_path / "rate/range") + .set_publisher(boost::bind(&duc_block_ctrl_impl::get_input_rates, this)) + ; + _tree->create<double>(dsp_base_path / "freq/value") + .set_coercer(boost::bind(&lambda_forward_prop, _tree, get_arg_path("freq/value", chan), _1)) + .set_publisher(boost::bind(&lambda_forward_prop, _tree, get_arg_path("freq/value", chan))) + ; + _tree->create<uhd::meta_range_t>(dsp_base_path / "freq/range") + .set_publisher(boost::bind(&duc_block_ctrl_impl::get_freq_range, this)) + ; + _tree->access<uhd::time_spec_t>("time/cmd") + .add_coerced_subscriber(boost::bind(&block_ctrl_base::set_command_time, this, _1, chan)) + ; + if (_tree->exists("tick_rate")) { + const double tick_rate = _tree->access<double>("tick_rate").get(); + set_command_tick_rate(tick_rate, chan); + _tree->access<double>("tick_rate") + .add_coerced_subscriber(boost::bind(&block_ctrl_base::set_command_tick_rate, this, _1, chan)) + ; + } + + // Rate 1:1 by default + sr_write("N", 1, chan); + sr_write("M", 1, chan); + sr_write("CONFIG", 1, chan); // Enable clear EOB + } + } // end ctor + virtual ~duc_block_ctrl_impl() {}; + + double get_input_scale_factor(size_t port=ANY_PORT) + { + port = (port == ANY_PORT) ? 0 : port; + if (not (_tx_streamer_active.count(port) and _tx_streamer_active.at(port))) { + return SCALE_UNDEFINED; + } + return get_arg<double>("scalar_correction", port); + } + + double get_input_samp_rate(size_t port=ANY_PORT) + { + port = (port == ANY_PORT) ? 0 : port; + if (not (_tx_streamer_active.count(port) and _tx_streamer_active.at(port))) { + return RATE_UNDEFINED; + } + return get_arg<double>("input_rate", port); + } + + double get_output_samp_rate(size_t port=ANY_PORT) + { + port = (port == ANY_PORT) ? 0 : port; + if (not (_tx_streamer_active.count(port) and _tx_streamer_active.at(port))) { + return RATE_UNDEFINED; + } + return get_arg<double>("output_rate", port == ANY_PORT ? 0 : port); + } + + void issue_stream_cmd( + const uhd::stream_cmd_t &stream_cmd_, + const size_t chan + ) { + UHD_RFNOC_BLOCK_TRACE() << "duc_block_ctrl_base::issue_stream_cmd()" << std::endl; + + uhd::stream_cmd_t stream_cmd = stream_cmd_; + if (stream_cmd.stream_mode == uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE or + stream_cmd.stream_mode == uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE) { + size_t interpolation = get_arg<double>("output_rate", chan) / get_arg<double>("input_rate", chan); + stream_cmd.num_samps *= interpolation; + } + + BOOST_FOREACH(const node_ctrl_base::node_map_pair_t upstream_node, list_upstream_nodes()) { + source_node_ctrl::sptr this_upstream_block_ctrl = + boost::dynamic_pointer_cast<source_node_ctrl>(upstream_node.second.lock()); + this_upstream_block_ctrl->issue_stream_cmd(stream_cmd, chan); + } + } + +private: + + //! Set the CORDIC frequency shift the signal to \p requested_freq + double set_freq(const double requested_freq, const size_t chan) + { + const double output_rate = get_arg<double>("output_rate"); + double actual_freq; + int32_t freq_word; + get_freq_and_freq_word(requested_freq, output_rate, actual_freq, freq_word); + // Xilinx CORDIC uses a different format for the phase increment, hence the divide-by-four: + sr_write("CORDIC_FREQ", uint32_t(freq_word/4), chan); + return actual_freq; + } + + //! Return a range of valid frequencies the CORDIC can tune to + uhd::meta_range_t get_freq_range(void) + { + const double output_rate = get_arg<double>("output_rate"); + return uhd::meta_range_t( + -output_rate/2, + +output_rate/2, + output_rate/std::pow(2.0, 32) + ); + } + + uhd::meta_range_t get_input_rates(void) + { + uhd::meta_range_t range; + const double output_rate = get_arg<double>("output_rate"); + for (int rate = 512; rate > 256; rate -= 4){ + range.push_back(uhd::range_t(output_rate/rate)); + } + for (int rate = 256; rate > 128; rate -= 2){ + range.push_back(uhd::range_t(output_rate/rate)); + } + for (int rate = 128; rate >= 1; rate -= 1){ + range.push_back(uhd::range_t(output_rate/rate)); + } + return range; + } + + double set_input_rate(const int requested_rate, const size_t chan) + { + const double output_rate = get_arg<double>("output_rate", chan); + const size_t interp_rate = boost::math::iround(output_rate/get_input_rates().clip(requested_rate, true)); + size_t interp = interp_rate; + + uint32_t hb_enable = 0; + while ((interp % 2 == 0) and hb_enable < NUM_HALFBANDS) { + hb_enable++; + interp /= 2; + } + UHD_ASSERT_THROW(hb_enable <= NUM_HALFBANDS); + UHD_ASSERT_THROW(interp > 0 and interp <= CIC_MAX_INTERP); + // hacky hack: Unlike the DUC, the DUC actually simply has 2 + // flags to enable either halfband. + uint32_t hb_enable_word = hb_enable; + if (hb_enable == 2) { + hb_enable_word = 3; + } + hb_enable_word <<= 8; + // What we can't cover with halfbands, we do with the CIC + sr_write("INTERP_WORD", hb_enable_word | (interp & 0xff), chan); + + // Rate change = M/N + sr_write("N", 1, chan); + sr_write("M", std::pow(2.0, double(hb_enable)) * (interp & 0xff), chan); + + if (interp > 1 and hb_enable == 0) { + UHD_MSG(warning) << boost::format( + "The requested interpolation is odd; the user should expect passband CIC rolloff.\n" + "Select an even interpolation to ensure that a halfband filter is enabled.\n" + "interpolation = dsp_rate/samp_rate -> %d = (%f MHz)/(%f MHz)\n" + ) % interp_rate % (output_rate/1e6) % (requested_rate/1e6); + } + + // Calculate algorithmic gain of CIC for a given interpolation + // For Ettus CIC R=interp, M=1, N=4. Gain = (R * M) ^ (N - 1) + const int CIC_N = 4; + const double rate_pow = std::pow(double(interp & 0xff), CIC_N - 1); + + // Experimentally determined value to scale the output to [-1, 1] + // This must also encompass the CORDIC gain + static const double CONSTANT_GAIN = 1.1644; + + const double scaling_adjustment = std::pow(2, ceil_log2(rate_pow))/(CONSTANT_GAIN*rate_pow); + update_scalar(scaling_adjustment, chan); + return output_rate/interp_rate; + } + + //! Set frequency and interpolation again + void set_output_rate(const double /* rate */, const size_t chan) + { + const double desired_freq = _tree->access<double>(get_arg_path("freq", chan) / "value").get_desired(); + set_arg<double>("freq", desired_freq, chan); + const double desired_input_rate = _tree->access<double>(get_arg_path("input_rate", chan) / "value").get_desired(); + set_arg<double>("input_rate", desired_input_rate, chan); + } + + // Calculate compensation gain values for algorithmic gain of CORDIC and CIC taking into account + // gain compensation blocks already hardcoded in place in DUC (that provide simple 1/2^n gain compensation). + // Further more factor in OTW format which adds further gain factor to weight output samples correctly. + void update_scalar(const double scalar, const size_t chan) + { + const double target_scalar = (1 << 15) * scalar; + const int32_t actual_scalar = boost::math::iround(target_scalar); + // Calculate the error introduced by using integer representation for the scalar + const double scalar_correction = + actual_scalar / target_scalar * (double(1 << 15) - 1.0) // Rounding error, normalized to 1.0 + * get_arg<double>("fullscale"); // Scaling requested by host + set_arg<double>("scalar_correction", scalar_correction, chan); + // Write DUC with scaling correction for CIC and CORDIC that maximizes dynamic range in 32/16/12/8bits. + sr_write("SCALE_IQ", actual_scalar, chan); + } +}; + +UHD_RFNOC_BLOCK_REGISTER(duc_block_ctrl, "DUC"); + diff --git a/host/lib/rfnoc/graph_impl.cpp b/host/lib/rfnoc/graph_impl.cpp new file mode 100644 index 000000000..fdf4fdc78 --- /dev/null +++ b/host/lib/rfnoc/graph_impl.cpp @@ -0,0 +1,164 @@ +// +// Copyright 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 "graph_impl.hpp" +#include <uhd/rfnoc/source_block_ctrl_base.hpp> +#include <uhd/rfnoc/sink_block_ctrl_base.hpp> +#include <uhd/utils/msg.hpp> + +using namespace uhd::rfnoc; + +/**************************************************************************** + * Structors + ***************************************************************************/ +graph_impl::graph_impl( + const std::string &name, + boost::weak_ptr<uhd::device3> device_ptr + //async_msg_handler::sptr msg_handler +) : _name(name) + , _device_ptr(device_ptr) +{ + +} + + +/**************************************************************************** + * Connection API + ***************************************************************************/ +void graph_impl::connect( + const block_id_t &src_block, + size_t src_block_port, + const block_id_t &dst_block, + size_t dst_block_port, + const size_t pkt_size_ +) { + device3::sptr device_ptr = _device_ptr.lock(); + if (not device_ptr) { + throw uhd::runtime_error("Invalid device"); + } + + uhd::rfnoc::source_block_ctrl_base::sptr src = device_ptr->get_block_ctrl<rfnoc::source_block_ctrl_base>(src_block); + uhd::rfnoc::sink_block_ctrl_base::sptr dst = device_ptr->get_block_ctrl<rfnoc::sink_block_ctrl_base>(dst_block); + + /******************************************************************** + * 1. Draw the edges (logically connect the nodes) + ********************************************************************/ + size_t actual_src_block_port = src->connect_downstream( + boost::dynamic_pointer_cast<uhd::rfnoc::node_ctrl_base>(dst), + src_block_port + ); + if (src_block_port == uhd::rfnoc::ANY_PORT) { + src_block_port = actual_src_block_port; + } else if (src_block_port != actual_src_block_port) { + throw uhd::runtime_error(str( + boost::format("Can't connect to port %d on block %s.") + % src_block_port % src->unique_id() + )); + } + size_t actual_dst_block_port = dst->connect_upstream( + boost::dynamic_pointer_cast<uhd::rfnoc::node_ctrl_base>(src), + dst_block_port + ); + if (dst_block_port == uhd::rfnoc::ANY_PORT) { + dst_block_port = actual_dst_block_port; + } else if (dst_block_port != actual_dst_block_port) { + throw uhd::runtime_error(str( + boost::format("Can't connect to port %d on block %s.") + % dst_block_port % dst->unique_id() + )); + } + src->set_downstream_port(actual_src_block_port, actual_dst_block_port); + dst->set_upstream_port(actual_dst_block_port, actual_src_block_port); + // At this point, ports are locked and no one else can simply connect + // into them. + UHD_MSG(status) + << "[" << _name << "] Connecting " + << src_block << ":" << actual_src_block_port << " --> " + << dst_block << ":" << actual_dst_block_port << std::endl; + + /******************************************************************** + * 2. Check IO signatures match + ********************************************************************/ + if (not rfnoc::stream_sig_t::is_compatible( + src->get_output_signature(actual_src_block_port), + dst->get_input_signature(actual_dst_block_port) + )) { + throw uhd::runtime_error(str( + boost::format("Can't connect block %s to %s: IO signature mismatch\n(%s is incompatible with %s).") + % src->get_block_id().get() % dst->get_block_id().get() + % src->get_output_signature(actual_src_block_port) + % dst->get_input_signature(actual_dst_block_port) + )); + } + + /******************************************************************** + * 3. Configure the source block's destination + ********************************************************************/ + // Calculate SID + sid_t sid = dst->get_address(dst_block_port); + sid.set_src(src->get_address(src_block_port)); + + // Set SID on source block + src->set_destination(sid.get(), src_block_port); + + /******************************************************************** + * 4. Configure flow control + ********************************************************************/ + size_t pkt_size = (pkt_size_ != 0) ? pkt_size_ : src->get_output_signature(src_block_port).packet_size; + if (pkt_size == 0) { // Unspecified packet rate. Assume max packet size. + UHD_MSG(status) << "Assuming max packet size for " << src->get_block_id() << std::endl; + pkt_size = uhd::rfnoc::MAX_PACKET_SIZE; + } + // FC window (in packets) depends on FIFO size... ...and packet size. + size_t buf_size_pkts = dst->get_fifo_size(dst_block_port) / pkt_size; + if (buf_size_pkts == 0) { + throw uhd::runtime_error(str( + boost::format("Input FIFO for block %s is too small (%d kiB) for packets of size %d kiB\n" + "coming from block %s.") + % dst->get_block_id().get() % (dst->get_fifo_size(dst_block_port) / 1024) + % (pkt_size / 1024) % src->get_block_id().get() + )); + } + src->configure_flow_control_out(buf_size_pkts, src_block_port); + // On the same crossbar, use lots of FC packets + size_t pkts_per_ack = std::min( + uhd::rfnoc::DEFAULT_FC_XBAR_PKTS_PER_ACK, + buf_size_pkts - 1 + ); + // Over the network, use less or we'd flood the transport + if (sid.get_src_addr() != sid.get_dst_addr()) { + pkts_per_ack = std::max<size_t>(buf_size_pkts / uhd::rfnoc::DEFAULT_FC_TX_RESPONSE_FREQ, 1); + } + dst->configure_flow_control_in( + 0, // Default to not use cycles + pkts_per_ack, + dst_block_port + ); + + /******************************************************************** + * 5. Configure error policy + ********************************************************************/ + dst->set_error_policy("next_burst"); +} + +void graph_impl::connect( + const block_id_t &src_block, + const block_id_t &dst_block +) { + connect(src_block, ANY_PORT, dst_block, ANY_PORT); +} + diff --git a/host/lib/rfnoc/graph_impl.hpp b/host/lib/rfnoc/graph_impl.hpp new file mode 100644 index 000000000..12dbf6357 --- /dev/null +++ b/host/lib/rfnoc/graph_impl.hpp @@ -0,0 +1,76 @@ +// +// Copyright 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_RFNOC_GRAPH_IMPL_HPP +#define INCLUDED_LIBUHD_RFNOC_GRAPH_IMPL_HPP + +#include <uhd/rfnoc/graph.hpp> +#include <uhd/device3.hpp> + +namespace uhd { namespace rfnoc { + +class graph_impl : public graph +{ +public: + /*! + * \param name An optional name to describe this graph + * \param device_ptr Weak pointer to the originating device3 + * \param msg_handler Pointer to the async message handler + */ + graph_impl( + const std::string &name, + boost::weak_ptr<uhd::device3> device_ptr + //async_msg_handler::sptr msg_handler + ); + virtual ~graph_impl() {}; + + /************************************************************************ + * Connection API + ***********************************************************************/ + void connect( + const block_id_t &src_block, + size_t src_block_port, + const block_id_t &dst_block, + size_t dst_block_port, + const size_t pkt_size = 0 + ); + + void connect( + const block_id_t &src_block, + const block_id_t &dst_block + ); + + /************************************************************************ + * Utilities + ***********************************************************************/ + std::string get_name() const { return _name; } + + +private: + + //! Optional: A string to describe this graph + const std::string _name; + + //! Reference to the generating device object + const boost::weak_ptr<uhd::device3> _device_ptr; + +}; + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_GRAPH_IMPL_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/legacy_compat.cpp b/host/lib/rfnoc/legacy_compat.cpp new file mode 100644 index 000000000..915432f22 --- /dev/null +++ b/host/lib/rfnoc/legacy_compat.cpp @@ -0,0 +1,700 @@ +// +// Copyright 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 "legacy_compat.hpp" +#include <uhd/property_tree.hpp> +#include <uhd/rfnoc/radio_ctrl.hpp> +#include <uhd/rfnoc/ddc_block_ctrl.hpp> +#include <uhd/rfnoc/graph.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/stream.hpp> +#include <uhd/types/stream_cmd.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/transport/chdr.hpp> +#include <boost/make_shared.hpp> + +#define UHD_LEGACY_LOG() UHD_LOGV(never) + +using namespace uhd::rfnoc; +using uhd::usrp::subdev_spec_t; +using uhd::usrp::subdev_spec_pair_t; +using uhd::stream_cmd_t; + +/************************************************************************ + * Constants + ***********************************************************************/ +static const std::string RADIO_BLOCK_NAME = "Radio"; +static const std::string DFIFO_BLOCK_NAME = "DmaFIFO"; +static const std::string DDC_BLOCK_NAME = "DDC"; +static const std::string DUC_BLOCK_NAME = "DUC"; +static const size_t MAX_BYTES_PER_HEADER = + uhd::transport::vrt::chdr::max_if_hdr_words64 * sizeof(uint64_t); +static const size_t BYTES_PER_SAMPLE = 4; // We currently only support sc16 + +/************************************************************************ + * Static helpers + ***********************************************************************/ +static uhd::fs_path mb_root(const size_t mboard) +{ + return uhd::fs_path("/mboards") / mboard; +} + +size_t num_ports(const uhd::property_tree::sptr &tree, const std::string &block_name, const std::string &in_out) +{ + return tree->list( + uhd::fs_path("/mboards/0/xbar") / + str(boost::format("%s_0") % block_name) / + "ports" / in_out + ).size(); +} + +size_t calc_num_tx_chans_per_radio( + const uhd::property_tree::sptr &tree, + const size_t num_radios_per_board, + const bool has_ducs, + const bool has_dmafifo +) { + const size_t num_radio_ports = num_ports(tree, RADIO_BLOCK_NAME, "in"); + if (has_ducs) { + return std::min( + num_radio_ports, + num_ports(tree, DUC_BLOCK_NAME, "in") + ); + } + + if (not has_dmafifo) { + return num_radio_ports; + } + + const size_t num_dmafifo_ports_per_radio = num_ports(tree, DFIFO_BLOCK_NAME, "in") / num_radios_per_board; + UHD_ASSERT_THROW(num_dmafifo_ports_per_radio); + + return std::min( + num_radio_ports, + num_dmafifo_ports_per_radio + ); +} + +double lambda_const_double(const double d) +{ + return d; +} + +uhd::meta_range_t lambda_const_meta_range(const double start, const double stop, const double step) +{ + return uhd::meta_range_t(start, stop, step); +} +/************************************************************************ + * Class Definition + ***********************************************************************/ +class legacy_compat_impl : public legacy_compat +{ +public: + /************************************************************************ + * Structors and Initialization + ***********************************************************************/ + legacy_compat_impl( + uhd::device3::sptr device, + const uhd::device_addr_t &args + ) : _device(device), + _tree(device->get_tree()), + _has_ducs(not args.has_key("skip_duc") and not device->find_blocks(DUC_BLOCK_NAME).empty()), + _has_ddcs(not args.has_key("skip_ddc") and not device->find_blocks(DDC_BLOCK_NAME).empty()), + _has_dmafifo(not args.has_key("skip_dram") and not device->find_blocks(DFIFO_BLOCK_NAME).empty()), + _num_mboards(_tree->list("/mboards").size()), + _num_radios_per_board(device->find_blocks<radio_ctrl>("0/Radio").size()), // These might throw, maybe we catch that and provide a nicer error message. + _num_tx_chans_per_radio( + calc_num_tx_chans_per_radio(_tree, _num_radios_per_board, _has_ducs, not device->find_blocks(DFIFO_BLOCK_NAME).empty()) + ), + _num_rx_chans_per_radio(_has_ddcs ? + std::min(num_ports(_tree, RADIO_BLOCK_NAME, "out"), num_ports(_tree, DDC_BLOCK_NAME, "out")) + : num_ports(_tree, RADIO_BLOCK_NAME, "out")), + _spp(get_block_ctrl<radio_ctrl>(0, RADIO_BLOCK_NAME, 0)->get_arg<int>("spp")), + _rx_channel_map(_num_mboards, std::vector<radio_port_pair_t>(_num_radios_per_board)), + _tx_channel_map(_num_mboards, std::vector<radio_port_pair_t>(_num_radios_per_board)) + { + _device->clear(); + check_available_periphs(); // Throws if invalid configuration. + setup_prop_tree(); + connect_blocks(); + if (args.has_key("skip_ddc")) { + UHD_LEGACY_LOG() << "[legacy_compat] Skipping DDCs by user request." << std::endl; + } else if (not _has_ddcs) { + UHD_MSG(warning) + << "[legacy_compat] No DDCs detected. You will only be able to receive at the radio frontend rate." + << std::endl; + } + if (args.has_key("skip_duc")) { + UHD_LEGACY_LOG() << "[legacy_compat] Skipping DUCs by user request." << std::endl; + } else if (not _has_ducs) { + UHD_MSG(warning) << "[legacy_compat] No DUCs detected. You will only be able to transmit at the radio frontend rate." << std::endl; + } + if (args.has_key("skip_dram")) { + UHD_LEGACY_LOG() << "[legacy_compat] Skipping DRAM by user request." << std::endl; + } else if (not _has_dmafifo) { + UHD_MSG(warning) << "[legacy_compat] No DMA FIFO detected. You will only be able to transmit at slow rates." << std::endl; + } + + for (size_t mboard = 0; mboard < _num_mboards; mboard++) { + for (size_t radio = 0; radio < _num_radios_per_board; radio++) { + _rx_channel_map[mboard][radio].radio_index = radio; + _tx_channel_map[mboard][radio].radio_index = radio; + } + + const double tick_rate = _tree->access<double>(mb_root(mboard) / "tick_rate").get(); + update_tick_rate_on_blocks(tick_rate, mboard); + } + } + + /************************************************************************ + * API Calls + ***********************************************************************/ + uhd::fs_path rx_dsp_root(const size_t mboard_idx, const size_t chan) + { + // The DSP index is the same as the radio index + size_t dsp_index = _rx_channel_map[mboard_idx][chan].radio_index; + size_t port_index = _rx_channel_map[mboard_idx][chan].port_index; + + if (not _has_ddcs) { + return mb_root(mboard_idx) / "rx_dsps" / dsp_index / port_index; + } + + return mb_root(mboard_idx) / "xbar" / + str(boost::format("%s_%d") % DDC_BLOCK_NAME % dsp_index) / + "legacy_api" / port_index; + } + + uhd::fs_path tx_dsp_root(const size_t mboard_idx, const size_t chan) + { + // The DSP index is the same as the radio index + size_t dsp_index = _tx_channel_map[mboard_idx][chan].radio_index; + size_t port_index = _tx_channel_map[mboard_idx][chan].port_index; + + if (not _has_ducs) { + return mb_root(mboard_idx) / "tx_dsps" / dsp_index / port_index; + } + + return mb_root(mboard_idx) / "xbar" / + str(boost::format("%s_%d") % DUC_BLOCK_NAME % dsp_index) / + "legacy_api" / port_index; + } + + uhd::fs_path rx_fe_root(const size_t mboard_idx, const size_t chan) + { + size_t radio_index = _rx_channel_map[mboard_idx][chan].radio_index; + size_t port_index = _rx_channel_map[mboard_idx][chan].port_index; + return uhd::fs_path(str( + boost::format("/mboards/%d/xbar/%s_%d/rx_fe_corrections/%d/") + % mboard_idx % RADIO_BLOCK_NAME % radio_index % port_index + )); + } + + uhd::fs_path tx_fe_root(const size_t mboard_idx, const size_t chan) + { + size_t radio_index = _tx_channel_map[mboard_idx][chan].radio_index; + size_t port_index = _tx_channel_map[mboard_idx][chan].port_index; + return uhd::fs_path(str( + boost::format("/mboards/%d/xbar/%s_%d/tx_fe_corrections/%d/") + % mboard_idx % RADIO_BLOCK_NAME % radio_index % port_index + )); + } + + void issue_stream_cmd(const stream_cmd_t &stream_cmd, size_t mboard, size_t chan) + { + UHD_LEGACY_LOG() << "[legacy_compat] issue_stream_cmd() " << std::endl; + const size_t &radio_index = _rx_channel_map[mboard][chan].radio_index; + const size_t &port_index = _rx_channel_map[mboard][chan].port_index; + if (_has_ddcs) { + get_block_ctrl<ddc_block_ctrl>(mboard, DDC_BLOCK_NAME, radio_index)->issue_stream_cmd(stream_cmd, port_index); + } else { + get_block_ctrl<radio_ctrl>(mboard, RADIO_BLOCK_NAME, radio_index)->issue_stream_cmd(stream_cmd, port_index); + } + } + + //! Sets block_id<N> and block_port<N> in the streamer args, otherwise forwards the call + uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &args_) + { + uhd::stream_args_t args(args_); + if (args.otw_format.empty()) { + args.otw_format = "sc16"; + } + _update_stream_args_for_streaming<uhd::RX_DIRECTION>(args, _rx_channel_map); + UHD_LEGACY_LOG() << "[legacy_compat] rx stream args: " << args.args.to_string() << std::endl; + return _device->get_rx_stream(args); + } + + //! Sets block_id<N> and block_port<N> in the streamer args, otherwise forwards the call. + // If spp is in the args, update the radios. If it's not set, copy the value from the radios. + uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &args_) + { + uhd::stream_args_t args(args_); + if (args.otw_format.empty()) { + args.otw_format = "sc16"; + } + _update_stream_args_for_streaming<uhd::TX_DIRECTION>(args, _tx_channel_map); + UHD_LEGACY_LOG() << "[legacy_compat] tx stream args: " << args.args.to_string() << std::endl; + return _device->get_tx_stream(args); + } + + double get_tick_rate(const size_t mboard_idx=0) + { + return _tree->access<double>(mb_root(mboard_idx) / "tick_rate").get(); + } + + uhd::meta_range_t lambda_get_samp_rate_range( + const size_t mboard_idx, + const size_t radio_idx, + const size_t chan, + uhd::direction_t dir + ) { + radio_ctrl::sptr radio_sptr = get_block_ctrl<radio_ctrl>(mboard_idx, RADIO_BLOCK_NAME, radio_idx); + const double samp_rate = (dir == uhd::TX_DIRECTION) ? + radio_sptr->get_input_samp_rate(chan) : + radio_sptr->get_output_samp_rate(chan) + ; + + return uhd::meta_range_t(samp_rate, samp_rate, 0.0); + } + + void set_tick_rate(const double tick_rate, const size_t mboard_idx=0) + { + _tree->access<double>(mb_root(mboard_idx) / "tick_rate").set(tick_rate); + update_tick_rate_on_blocks(tick_rate, mboard_idx); + } + +private: // types + struct radio_port_pair_t { + radio_port_pair_t(const size_t radio=0, const size_t port=0) : radio_index(radio), port_index(port) {} + size_t radio_index; + size_t port_index; + }; + //! Map: _rx_channel_map[mboard_idx][chan_idx] => (Radio, Port) + // Container is not a std::map because we need to guarantee contiguous + // ports and correct order anyway. + typedef std::vector< std::vector<radio_port_pair_t> > chan_map_t; + +private: // methods + /************************************************************************ + * Private helpers + ***********************************************************************/ + std::string get_slot_name(const size_t radio_index) + { + return (radio_index == 0) ? "A" : "B"; + } + + size_t get_radio_index(const std::string slot_name) + { + return (slot_name == "A") ? 0 : 1; + } + + template <typename block_type> + inline typename block_type::sptr get_block_ctrl(const size_t mboard_idx, const std::string &name, const size_t block_count) + { + block_id_t block_id(mboard_idx, name, block_count); + return _device->get_block_ctrl<block_type>(block_id); + } + + template <uhd::direction_t dir> + void _update_stream_args_for_streaming( + uhd::stream_args_t &args, + chan_map_t &chan_map + ) { + const size_t args_spp = args.args.cast<size_t>("spp", _spp); + if (args.args.has_key("spp") and args_spp != _spp) { + for (size_t mboard = 0; mboard < _num_mboards; mboard++) { + for (size_t radio = 0; radio < _num_radios_per_board; radio++) { + get_block_ctrl<radio_ctrl>(mboard, RADIO_BLOCK_NAME, radio)->set_arg<int>("spp", args_spp); + } + } + _spp = args_spp; + } else if (dir == uhd::RX_DIRECTION) { + args.args["spp"] = str(boost::format("%d") % _spp); + } + if (args.channels.empty()) { + args.channels = std::vector<size_t>(1, 0); + } + for (size_t i = 0; i < args.channels.size(); i++) { + const size_t stream_arg_chan_idx = args.channels[i]; + // Determine which mboard, and on that mboard, which channel this is: + size_t mboard_idx = 0; + size_t this_mboard_chan_idx = stream_arg_chan_idx; + while (this_mboard_chan_idx >= chan_map[mboard_idx].size()) { + mboard_idx++; + this_mboard_chan_idx -= chan_map[mboard_idx].size(); + } + if (mboard_idx >= chan_map.size()) { + throw uhd::index_error(str( + boost::format("[legacy_compat]: %s channel %u out of range for given frontend configuration.") + % (dir == uhd::TX_DIRECTION ? "TX" : "RX") + % stream_arg_chan_idx + )); + } + // Map that mboard and channel to a block: + const size_t radio_index = chan_map[mboard_idx][this_mboard_chan_idx].radio_index; + size_t port_index = chan_map[mboard_idx][this_mboard_chan_idx].port_index; + const std::string block_name = _get_streamer_block_id_and_port<dir>(mboard_idx, radio_index, port_index); + args.args[str(boost::format("block_id%d") % stream_arg_chan_idx)] = block_name; + args.args[str(boost::format("block_port%d") % stream_arg_chan_idx)] = str(boost::format("%d") % port_index); + } + } + + template <uhd::direction_t dir> + std::string _get_streamer_block_id_and_port( + const size_t mboard_idx, + const size_t radio_index, + size_t &port_index + ) { + if (dir == uhd::TX_DIRECTION) { + if (_has_dmafifo) { + port_index = radio_index; + return block_id_t(mboard_idx, DFIFO_BLOCK_NAME, 0).to_string(); + } else { + if (_has_ducs) { + return block_id_t(mboard_idx, DUC_BLOCK_NAME, radio_index).to_string(); + } else { + return block_id_t(mboard_idx, RADIO_BLOCK_NAME, radio_index).to_string(); + } + } + } else { + if (_has_ddcs) { + return block_id_t(mboard_idx, DDC_BLOCK_NAME, radio_index).to_string(); + } else { + return block_id_t(mboard_idx, RADIO_BLOCK_NAME, radio_index).to_string(); + } + } + } + + /************************************************************************ + * Initialization + ***********************************************************************/ + /*! Check this device has all the required peripherals. + * + * Check rules: + * - Every mboard needs the same number of radios. + * - For every radio block, there must be DDC and a DUC block, + * with matching number of ports. + * + * \throw uhd::runtime_error if any of these checks fail. + */ + void check_available_periphs() + { + if (_num_radios_per_board == 0) { + throw uhd::runtime_error("For legacy APIs, all devices require at least one radio."); + } + block_id_t radio_block_id(0, RADIO_BLOCK_NAME); + block_id_t duc_block_id(0, DUC_BLOCK_NAME); + block_id_t ddc_block_id(0, DDC_BLOCK_NAME); + block_id_t fifo_block_id(0, DFIFO_BLOCK_NAME, 0); + for (size_t i = 0; i < _num_mboards; i++) { + radio_block_id.set_device_no(i); + duc_block_id.set_device_no(i); + ddc_block_id.set_device_no(i); + fifo_block_id.set_device_no(i); + for (size_t k = 0; k < _num_radios_per_board; k++) { + radio_block_id.set_block_count(k); + duc_block_id.set_block_count(k); + ddc_block_id.set_block_count(k); + // Only one FIFO per crossbar, so don't set block count for that block + if (not _device->has_block(radio_block_id) + or (_has_ducs and not _device->has_block(duc_block_id)) + or (_has_ddcs and not _device->has_block(ddc_block_id)) + or (_has_dmafifo and not _device->has_block(fifo_block_id)) + ) { + throw uhd::runtime_error("For legacy APIs, all devices require the same number of radios, DDCs and DUCs."); + } + + const size_t this_spp = get_block_ctrl<radio_ctrl>(i, RADIO_BLOCK_NAME, k)->get_arg<int>("spp"); + if (this_spp != _spp) { + throw uhd::runtime_error(str( + boost::format("[legacy compat] Radios have differing spp values: %s has %d, others have %d") + % radio_block_id.to_string() % this_spp % _spp + )); + } + } + } + } + + /*! Initialize properties in property tree to match legacy mode + */ + void setup_prop_tree() + { + for (size_t mboard_idx = 0; mboard_idx < _num_mboards; mboard_idx++) { + uhd::fs_path root = mb_root(mboard_idx); + // Subdev specs + if (_tree->exists(root / "tx_subdev_spec")) { + _tree->access<subdev_spec_t>(root / "tx_subdev_spec") + .add_coerced_subscriber(boost::bind(&legacy_compat_impl::set_subdev_spec, this, _1, mboard_idx, uhd::TX_DIRECTION)) + .update() + .set_publisher(boost::bind(&legacy_compat_impl::get_subdev_spec, this, mboard_idx, uhd::TX_DIRECTION)); + } else { + _tree->create<subdev_spec_t>(root / "tx_subdev_spec") + .add_coerced_subscriber(boost::bind(&legacy_compat_impl::set_subdev_spec, this, _1, mboard_idx, uhd::TX_DIRECTION)) + .set_publisher(boost::bind(&legacy_compat_impl::get_subdev_spec, this, mboard_idx, uhd::TX_DIRECTION)); + } + + if (_tree->exists(root / "rx_subdev_spec")) { + _tree->access<subdev_spec_t>(root / "rx_subdev_spec") + .add_coerced_subscriber(boost::bind(&legacy_compat_impl::set_subdev_spec, this, _1, mboard_idx, uhd::RX_DIRECTION)) + .update() + .set_publisher(boost::bind(&legacy_compat_impl::get_subdev_spec, this, mboard_idx, uhd::RX_DIRECTION)); + } else { + _tree->create<subdev_spec_t>(root / "rx_subdev_spec") + .add_coerced_subscriber(boost::bind(&legacy_compat_impl::set_subdev_spec, this, _1, mboard_idx, uhd::RX_DIRECTION)) + .set_publisher(boost::bind(&legacy_compat_impl::get_subdev_spec, this, mboard_idx, uhd::RX_DIRECTION)); + } + + if (not _has_ddcs) { + for (size_t radio_idx = 0; radio_idx < _num_radios_per_board; radio_idx++) { + for (size_t chan = 0; chan < _num_rx_chans_per_radio; chan++) { + const uhd::fs_path rx_dsp_base_path(mb_root(mboard_idx) / "rx_dsps" / radio_idx / chan); + _tree->create<double>(rx_dsp_base_path / "rate/value") + .set(0.0) + .set_publisher( + boost::bind( + &radio_ctrl::get_output_samp_rate, + get_block_ctrl<radio_ctrl>(mboard_idx, RADIO_BLOCK_NAME, radio_idx), + chan + ) + ) + ; + _tree->create<uhd::meta_range_t>(rx_dsp_base_path / "rate/range") + .set_publisher( + boost::bind( + &legacy_compat_impl::lambda_get_samp_rate_range, + this, + mboard_idx, radio_idx, chan, + uhd::RX_DIRECTION + ) + ) + ; + _tree->create<double>(rx_dsp_base_path / "freq/value") + .set_publisher(boost::bind(&lambda_const_double, 0.0)) + ; + _tree->create<uhd::meta_range_t>(rx_dsp_base_path / "freq/range") + .set_publisher(boost::bind(&lambda_const_meta_range, 0.0, 0.0, 0.0)) + ; + } + } + } + if (not _has_ducs) { + for (size_t radio_idx = 0; radio_idx < _num_radios_per_board; radio_idx++) { + for (size_t chan = 0; chan < _num_tx_chans_per_radio; chan++) { + const uhd::fs_path tx_dsp_base_path(mb_root(mboard_idx) / "tx_dsps" / radio_idx / chan); + _tree->create<double>(tx_dsp_base_path / "rate/value") + .set(0.0) + .set_publisher( + boost::bind( + &radio_ctrl::get_output_samp_rate, + get_block_ctrl<radio_ctrl>(mboard_idx, RADIO_BLOCK_NAME, radio_idx), + chan + ) + ) + ; + _tree->create<uhd::meta_range_t>(tx_dsp_base_path / "rate/range") + .set_publisher( + boost::bind( + &legacy_compat_impl::lambda_get_samp_rate_range, + this, + mboard_idx, radio_idx, chan, + uhd::TX_DIRECTION + ) + ) + ; + _tree->create<double>(tx_dsp_base_path / "freq/value") + .set_publisher(boost::bind(&lambda_const_double, 0.0)) + ; + _tree->create<uhd::meta_range_t>(tx_dsp_base_path / "freq/range") + .set_publisher(boost::bind(&lambda_const_meta_range, 0.0, 0.0, 0.0)) + ; + } + } + } + } + } + + /*! Default block connections. + * + * Tx connections: + * + * [Host] => DMA FIFO => DUC => Radio + * + * Note: There is only one DMA FIFO per crossbar, with twice the number of ports. + * + * Rx connections: + * + * Radio => DDC => [Host] + * + * Streamers are *not* generated here. + */ + void connect_blocks() + { + _graph = _device->create_graph("legacy"); + size_t bpp = _spp * BYTES_PER_SAMPLE + MAX_BYTES_PER_HEADER; + for (size_t mboard = 0; mboard < _num_mboards; mboard++) { + for (size_t radio = 0; radio < _num_radios_per_board; radio++) { + // Tx Channels + for (size_t chan = 0; chan < _num_tx_chans_per_radio; chan++) { + if (_has_ducs) { + _graph->connect( + block_id_t(mboard, DUC_BLOCK_NAME, radio), chan, + block_id_t(mboard, RADIO_BLOCK_NAME, radio), chan, + bpp + ); + if (_has_dmafifo) { + // We have DMA FIFO *and* DUCs + _graph->connect( + block_id_t(mboard, DFIFO_BLOCK_NAME, 0), radio, + block_id_t(mboard, DUC_BLOCK_NAME, radio), chan, + bpp + ); + } + } else if (_has_dmafifo) { + // We have DMA FIFO, *no* DUCs + _graph->connect( + block_id_t(mboard, DFIFO_BLOCK_NAME, 0), radio, + block_id_t(mboard, RADIO_BLOCK_NAME, radio), chan, + bpp + ); + } + } + // Rx Channels + for (size_t chan = 0; chan < _num_rx_chans_per_radio; chan++) { + if (_has_ddcs) { + _graph->connect( + block_id_t(mboard, RADIO_BLOCK_NAME, radio), chan, + block_id_t(mboard, DDC_BLOCK_NAME, radio), chan, + bpp + ); + } + } + } + } + } + + + /************************************************************************ + * Subdev translation + ***********************************************************************/ + /*! Subdev -> (Radio, Port) + * + * Example: Device is X300, subdev spec is 'A:0 B:0', we have 2 radios. + * Then we map to ((0, 0), (1, 0)). I.e., zero-th port on radio 0 and + * radio 1, respectively. + */ + void set_subdev_spec(const subdev_spec_t &spec, const size_t mboard, const uhd::direction_t dir) + { + UHD_ASSERT_THROW(mboard < _num_mboards); + chan_map_t &chan_map = (dir == uhd::TX_DIRECTION) ? _tx_channel_map : _rx_channel_map; + std::vector<radio_port_pair_t> new_mapping(spec.size()); + for (size_t i = 0; i < spec.size(); i++) { + const size_t new_radio_index = get_radio_index(spec[i].db_name); + radio_ctrl::sptr radio = get_block_ctrl<radio_ctrl>(mboard, "Radio", new_radio_index); + size_t new_port_index = radio->get_chan_from_dboard_fe(spec[i].sd_name, dir); + if (new_port_index >= radio->get_input_ports().size()) { + new_port_index = radio->get_input_ports().at(0); + } + radio_port_pair_t new_radio_port_pair(new_radio_index, new_port_index); + new_mapping[i] = new_radio_port_pair; + } + chan_map[mboard] = new_mapping; + } + + subdev_spec_t get_subdev_spec(const size_t mboard, const uhd::direction_t dir) + { + UHD_ASSERT_THROW(mboard < _num_mboards); + subdev_spec_t subdev_spec; + chan_map_t &chan_map = (dir == uhd::TX_DIRECTION) ? _tx_channel_map : _rx_channel_map; + for (size_t chan_idx = 0; chan_idx < chan_map[mboard].size(); chan_idx++) { + const size_t radio_index = chan_map[mboard][chan_idx].radio_index; + const size_t port_index = chan_map[mboard][chan_idx].port_index; + const std::string new_db_name = get_slot_name(radio_index); + const std::string new_sd_name = + get_block_ctrl<radio_ctrl>(mboard, "Radio", radio_index)->get_dboard_fe_from_chan(port_index, dir); + subdev_spec_pair_t new_pair(new_db_name, new_sd_name); + subdev_spec.push_back(new_pair); + } + + return subdev_spec; + } + + void update_tick_rate_on_blocks(const double tick_rate, const size_t mboard_idx) + { + block_id_t radio_block_id(mboard_idx, RADIO_BLOCK_NAME); + block_id_t duc_block_id(mboard_idx, DUC_BLOCK_NAME); + block_id_t ddc_block_id(mboard_idx, DDC_BLOCK_NAME); + + for (size_t radio = 0; radio < _num_radios_per_board; radio++) { + radio_block_id.set_block_count(radio); + duc_block_id.set_block_count(radio); + ddc_block_id.set_block_count(radio); + radio_ctrl::sptr radio_sptr = _device->get_block_ctrl<radio_ctrl>(radio_block_id); + radio_sptr->set_rate(tick_rate); + for (size_t chan = 0; chan < _num_rx_chans_per_radio and _has_ddcs; chan++) { + const double radio_output_rate = radio_sptr->get_output_samp_rate(chan); + _device->get_block_ctrl(ddc_block_id)->set_arg<double>("input_rate", radio_output_rate, chan); + } + for (size_t chan = 0; chan < _num_tx_chans_per_radio and _has_ducs; chan++) { + const double radio_input_rate = radio_sptr->get_input_samp_rate(chan); + _device->get_block_ctrl(duc_block_id)->set_arg<double>("output_rate", radio_input_rate, chan); + } + } + } + +private: // attributes + uhd::device3::sptr _device; + uhd::property_tree::sptr _tree; + + const bool _has_ducs; + const bool _has_ddcs; + const bool _has_dmafifo; + const size_t _num_mboards; + const size_t _num_radios_per_board; + const size_t _num_tx_chans_per_radio; + const size_t _num_rx_chans_per_radio; + size_t _spp; + + chan_map_t _rx_channel_map; + chan_map_t _tx_channel_map; + + graph::sptr _graph; +}; + +legacy_compat::sptr legacy_compat::make( + uhd::device3::sptr device, + const uhd::device_addr_t &args +) { + UHD_ASSERT_THROW(bool(device)); + static std::map<void *, boost::weak_ptr<legacy_compat> > legacy_cache; + + if (legacy_cache.count(device.get())) { + legacy_compat::sptr legacy_compat_copy = legacy_cache.at(device.get()).lock(); + if (not bool(legacy_compat_copy)) { + throw uhd::runtime_error("Reference to existing legacy compat object expired prematurely!"); + } + + UHD_LEGACY_LOG() << "[legacy_compat] Using existing legacy compat object for this device." << std::endl; + return legacy_compat_copy; + } + + legacy_compat::sptr new_legacy_compat = boost::make_shared<legacy_compat_impl>(device, args); + legacy_cache[device.get()] = new_legacy_compat; + return new_legacy_compat; +} + diff --git a/host/lib/rfnoc/legacy_compat.hpp b/host/lib/rfnoc/legacy_compat.hpp new file mode 100644 index 000000000..29be1bdc2 --- /dev/null +++ b/host/lib/rfnoc/legacy_compat.hpp @@ -0,0 +1,55 @@ +// +// Copyright 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_RFNOC_LEGACY_COMPAT_HPP +#define INCLUDED_RFNOC_LEGACY_COMPAT_HPP + +#include <uhd/device3.hpp> +#include <uhd/stream.hpp> + +namespace uhd { namespace rfnoc { + + /*! Legacy compatibility layer class. + */ + class legacy_compat + { + public: + typedef boost::shared_ptr<legacy_compat> sptr; + + virtual uhd::fs_path rx_dsp_root(const size_t mboard_idx, const size_t chan) = 0; + + virtual uhd::fs_path tx_dsp_root(const size_t mboard_idx, const size_t chan) = 0; + + virtual uhd::fs_path rx_fe_root(const size_t mboard_idx, const size_t chan) = 0; + + virtual uhd::fs_path tx_fe_root(const size_t mboard_idx, const size_t chan) = 0; + + virtual void issue_stream_cmd(const uhd::stream_cmd_t &stream_cmd, size_t mboard, size_t chan) = 0; + + virtual uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &args) = 0; + + virtual uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &args) = 0; + + static sptr make( + uhd::device3::sptr device, + const uhd::device_addr_t &args + ); + }; + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_RFNOC_LEGACY_COMPAT_HPP */ diff --git a/host/lib/rfnoc/nocscript/CMakeLists.txt b/host/lib/rfnoc/nocscript/CMakeLists.txt new file mode 100644 index 000000000..77fcc101c --- /dev/null +++ b/host/lib/rfnoc/nocscript/CMakeLists.txt @@ -0,0 +1,37 @@ +# +# Copyright 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/>. +# + +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_basic_funcs.py + ${CMAKE_CURRENT_BINARY_DIR}/basic_functions.hpp +) + +IF(ENABLE_MANUAL) + LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_basic_funcs.py + ${CMAKE_BINARY_DIR}/docs/nocscript_functions.dox + ) +ENDIF(ENABLE_MANUAL) + +LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/expression.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/function_table.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/parser.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/block_iface.cpp +) diff --git a/host/lib/rfnoc/nocscript/block_iface.cpp b/host/lib/rfnoc/nocscript/block_iface.cpp new file mode 100644 index 000000000..2034d3438 --- /dev/null +++ b/host/lib/rfnoc/nocscript/block_iface.cpp @@ -0,0 +1,255 @@ +// +// Copyright 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/>. +// + +#include "block_iface.hpp" +#include "function_table.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <boost/assign.hpp> +#include <boost/bind.hpp> +#include <boost/format.hpp> + +#define UHD_NOCSCRIPT_LOG() UHD_LOGV(never) + +using namespace uhd::rfnoc; +using namespace uhd::rfnoc::nocscript; + +block_iface::block_iface(block_ctrl_base *block_ptr) + : _block_ptr(block_ptr) +{ + function_table::sptr ft = function_table::make(); + + // Add the SR_WRITE() function + expression_function::argtype_list_type sr_write_args = boost::assign::list_of + (expression::TYPE_STRING) + (expression::TYPE_INT) + ; + ft->register_function( + "SR_WRITE", + boost::bind(&block_iface::_nocscript__sr_write, this, _1), + expression::TYPE_BOOL, + sr_write_args + ); + + // Add read access to arguments ($foo) + expression_function::argtype_list_type arg_set_args_wo_port = boost::assign::list_of + (expression::TYPE_STRING) + (expression::TYPE_INT) + ; + expression_function::argtype_list_type arg_set_args_w_port = boost::assign::list_of + (expression::TYPE_STRING) + (expression::TYPE_INT) + (expression::TYPE_INT) + ; +#define REGISTER_ARG_SETTER(noctype, setter_func) \ + arg_set_args_wo_port[1] = expression::noctype; \ + arg_set_args_w_port[1] = expression::noctype; \ + ft->register_function( \ + "SET_ARG", \ + boost::bind(&block_iface::setter_func, this, _1), \ + expression::TYPE_BOOL, \ + arg_set_args_wo_port \ + ); \ + ft->register_function( \ + "SET_ARG", \ + boost::bind(&block_iface::setter_func, this, _1), \ + expression::TYPE_BOOL, \ + arg_set_args_w_port \ + ); + REGISTER_ARG_SETTER(TYPE_INT, _nocscript__arg_set_int); + REGISTER_ARG_SETTER(TYPE_STRING, _nocscript__arg_set_string); + REGISTER_ARG_SETTER(TYPE_DOUBLE, _nocscript__arg_set_double); + REGISTER_ARG_SETTER(TYPE_INT_VECTOR, _nocscript__arg_set_intvec); + + + // Add read/write access to local variables + expression_function::argtype_list_type set_var_args = boost::assign::list_of + (expression::TYPE_STRING) + (expression::TYPE_INT) + ; + const expression_function::argtype_list_type get_var_args = boost::assign::list_of + (expression::TYPE_STRING) + ; +#define REGISTER_VAR_ACCESS(noctype, typestr) \ + set_var_args[1] = expression::noctype; \ + ft->register_function( \ + "SET_VAR", \ + boost::bind(&block_iface::_nocscript__var_set, this, _1), \ + expression::TYPE_BOOL, \ + set_var_args \ + ); \ + ft->register_function( \ + "GET_"#typestr, \ + boost::bind(&block_iface::_nocscript__var_get, this, _1), \ + expression::noctype, \ + get_var_args \ + ); + REGISTER_VAR_ACCESS(TYPE_INT, INT); + REGISTER_VAR_ACCESS(TYPE_STRING, STRING); + REGISTER_VAR_ACCESS(TYPE_DOUBLE, DOUBLE); + REGISTER_VAR_ACCESS(TYPE_INT_VECTOR, INT_VECTOR); + + // Create the parser + _parser = parser::make( + ft, + boost::bind(&block_iface::_nocscript__arg_get_type, this, _1), + boost::bind(&block_iface::_nocscript__arg_get_val, this, _1) + ); +} + + +void block_iface::run_and_check(const std::string &code, const std::string &error_message) +{ + boost::mutex::scoped_lock local_interpreter_lock(_lil_mutex); + + UHD_NOCSCRIPT_LOG() << "[NocScript] Executing and asserting code: " << code << std::endl; + expression::sptr e = _parser->create_expr_tree(code); + expression_literal result = e->eval(); + if (not result.to_bool()) { + if (error_message.empty()) { + throw uhd::runtime_error(str( + boost::format("[NocScript] Code returned false: %s") + % code + )); + } else { + throw uhd::runtime_error(str( + boost::format("[NocScript] Error: %s") + % error_message + )); + } + } + + _vars.clear(); // We go out of scope, and so do NocScript variables +} + + +expression_literal block_iface::_nocscript__sr_write(expression_container::expr_list_type args) +{ + const std::string reg_name = args[0]->eval().get_string(); + const boost::uint32_t reg_val = boost::uint32_t(args[1]->eval().get_int()); + bool result = true; + try { + UHD_NOCSCRIPT_LOG() << "[NocScript] Executing SR_WRITE() " << std::endl; + _block_ptr->sr_write(reg_name, reg_val); + } catch (const uhd::exception &e) { + UHD_MSG(error) << boost::format("[NocScript] Error while executing SR_WRITE(%s, 0x%X):\n%s") + % reg_name % reg_val % e.what() + << std::endl; + result = false; + } + + return expression_literal(result); +} + +expression::type_t block_iface::_nocscript__arg_get_type(const std::string &varname) +{ + const std::string var_type = _block_ptr->get_arg_type(varname); + if (var_type == "int") { + return expression::TYPE_INT; + } else if (var_type == "string") { + return expression::TYPE_STRING; + } else if (var_type == "double") { + return expression::TYPE_DOUBLE; + } else if (var_type == "int_vector") { + UHD_THROW_INVALID_CODE_PATH(); // TODO + } else { + UHD_THROW_INVALID_CODE_PATH(); + } +} + +expression_literal block_iface::_nocscript__arg_get_val(const std::string &varname) +{ + const std::string var_type = _block_ptr->get_arg_type(varname); + if (var_type == "int") { + return expression_literal(_block_ptr->get_arg<int>(varname)); + } else if (var_type == "string") { + return expression_literal(_block_ptr->get_arg<std::string>(varname)); + } else if (var_type == "double") { + return expression_literal(_block_ptr->get_arg<double>(varname)); + } else if (var_type == "int_vector") { + UHD_THROW_INVALID_CODE_PATH(); // TODO + } else { + UHD_THROW_INVALID_CODE_PATH(); + } +} + +expression_literal block_iface::_nocscript__arg_set_int(const expression_container::expr_list_type &args) +{ + const std::string var_name = args[0]->eval().get_string(); + const int val = args[1]->eval().get_int(); + size_t port = 0; + if (args.size() == 3) { + port = size_t(args[2]->eval().get_int()); + } + UHD_NOCSCRIPT_LOG() << "[NocScript] Setting $" << var_name << std::endl; + _block_ptr->set_arg<int>(var_name, val, port); + return expression_literal(true); +} + +expression_literal block_iface::_nocscript__arg_set_string(const expression_container::expr_list_type &args) +{ + const std::string var_name = args[0]->eval().get_string(); + const std::string val = args[1]->eval().get_string(); + size_t port = 0; + if (args.size() == 3) { + port = size_t(args[2]->eval().get_int()); + } + UHD_NOCSCRIPT_LOG() << "[NocScript] Setting $" << var_name << std::endl; + _block_ptr->set_arg<std::string>(var_name, val, port); + return expression_literal(true); +} + +expression_literal block_iface::_nocscript__arg_set_double(const expression_container::expr_list_type &args) +{ + const std::string var_name = args[0]->eval().get_string(); + const double val = args[1]->eval().get_double(); + size_t port = 0; + if (args.size() == 3) { + port = size_t(args[2]->eval().get_int()); + } + UHD_NOCSCRIPT_LOG() << "[NocScript] Setting $" << var_name << std::endl; + _block_ptr->set_arg<double>(var_name, val, port); + return expression_literal(true); +} + +expression_literal block_iface::_nocscript__arg_set_intvec(const expression_container::expr_list_type &) +{ + UHD_THROW_INVALID_CODE_PATH(); +} + +block_iface::sptr block_iface::make(uhd::rfnoc::block_ctrl_base* block_ptr) +{ + return sptr(new block_iface(block_ptr)); +} + +expression_literal block_iface::_nocscript__var_get(const expression_container::expr_list_type &args) +{ + expression_literal expr = _vars[args[0]->eval().get_string()]; + //std::cout << "[NocScript] Getting var " << args[0]->eval().get_string() << " == " << expr << std::endl; + //std::cout << "[NocScript] Type " << expr.infer_type() << std::endl; + //return _vars[args[0]->eval().get_string()]; + return expr; +} + +expression_literal block_iface::_nocscript__var_set(const expression_container::expr_list_type &args) +{ + _vars[args[0]->eval().get_string()] = args[1]->eval(); + //std::cout << "[NocScript] Set var " << args[0]->eval().get_string() << " to " << _vars[args[0]->eval().get_string()] << std::endl; + //std::cout << "[NocScript] Type " << _vars[args[0]->eval().get_string()].infer_type() << std::endl; + return expression_literal(true); +} + diff --git a/host/lib/rfnoc/nocscript/block_iface.hpp b/host/lib/rfnoc/nocscript/block_iface.hpp new file mode 100644 index 000000000..6354409f2 --- /dev/null +++ b/host/lib/rfnoc/nocscript/block_iface.hpp @@ -0,0 +1,94 @@ +// +// Copyright 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/>. +// + +#include "expression.hpp" +#include "parser.hpp" +#include <uhd/rfnoc/block_ctrl_base.hpp> +#include <boost/thread/mutex.hpp> + +#ifndef INCLUDED_LIBUHD_NOCSCRIPT_BLOCK_IFACE_HPP +#define INCLUDED_LIBUHD_NOCSCRIPT_BLOCK_IFACE_HPP + +namespace uhd { namespace rfnoc { namespace nocscript { + +/*! NocScript / Block interface class. + * + * This class only exists as a member of an rfnoc::block_ctrl_base class. + * It should never be instantiated anywhere else. It is used to execute + * NocScript function calls that require access to the original block + * controller class. + */ +class block_iface { + + public: + typedef boost::shared_ptr<block_iface> sptr; + + static sptr make(uhd::rfnoc::block_ctrl_base* block_ptr); + + block_iface(uhd::rfnoc::block_ctrl_base* block_ptr); + + /*! Execute \p code and make sure it returns 'true'. + * + * \param code Must be a valid NocScript expression that returns a boolean value. + * If it returns false, this is interpreted as failure. + * \param error_message If the expression fails, this error message is printed. + * \throws uhd::runtime_error if the expression returns false. + * \throws uhd::syntax_error if the expression is invalid. + */ + void run_and_check(const std::string &code, const std::string &error_message=""); + + private: + //! For the local interpreter lock (lil) + boost::mutex _lil_mutex; + + //! Wrapper for block_ctrl_base::sr_write, so we can call it from within NocScript + expression_literal _nocscript__sr_write(expression_container::expr_list_type); + + //! Argument type getter that can be used within NocScript + expression::type_t _nocscript__arg_get_type(const std::string &argname); + + //! Argument value getter that can be used within NocScript + expression_literal _nocscript__arg_get_val(const std::string &argname); + + //! Argument value setters: + expression_literal _nocscript__arg_set_int(const expression_container::expr_list_type &); + expression_literal _nocscript__arg_set_string(const expression_container::expr_list_type &); + expression_literal _nocscript__arg_set_double(const expression_container::expr_list_type &); + expression_literal _nocscript__arg_set_intvec(const expression_container::expr_list_type &); + + //! Variable value getter + expression_literal _nocscript__var_get(const expression_container::expr_list_type &); + + //! Variable value setter + expression_literal _nocscript__var_set(const expression_container::expr_list_type &); + + //! Raw pointer to the block class. Note that since block_iface may + // only live as a member of a block_ctrl_base, we don't really need + // the reference counting. + uhd::rfnoc::block_ctrl_base* _block_ptr; + + //! Pointer to the parser object + parser::sptr _parser; + + //! Container for scoped variables + std::map<std::string, expression_literal> _vars; +}; + +}}} /* namespace uhd::rfnoc::nocscript */ + +#endif /* INCLUDED_LIBUHD_NOCSCRIPT_BLOCK_IFACE_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/nocscript/expression.cpp b/host/lib/rfnoc/nocscript/expression.cpp new file mode 100644 index 000000000..38d6e2128 --- /dev/null +++ b/host/lib/rfnoc/nocscript/expression.cpp @@ -0,0 +1,413 @@ +// +// Copyright 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/>. +// + +#include "expression.hpp" +#include "function_table.hpp" +#include <uhd/utils/cast.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/assign.hpp> +#include <boost/algorithm/string.hpp> + +using namespace uhd::rfnoc::nocscript; + +std::map<expression::type_t, std::string> expression::type_repr = boost::assign::map_list_of + (TYPE_INT, "INT") + (TYPE_DOUBLE, "DOUBLE") + (TYPE_STRING, "STRING") + (TYPE_BOOL, "BOOL") + (TYPE_INT_VECTOR, "INT_VECTOR") +; + +/******************************************************************** + * Literal expressions (constants) + *******************************************************************/ +expression_literal::expression_literal( + const std::string token_val, + expression::type_t type +) : _bool_val(false) + , _int_val(0) + , _double_val(0.0) + , _val(token_val) + , _type(type) +{ + switch (_type) { + case expression::TYPE_STRING: + // Remove the leading and trailing quotes: + _val = _val.substr(1, _val.size()-2); + break; + + case expression::TYPE_INT: + if (_val.substr(0, 2) == "0x") { + _int_val = uhd::cast::hexstr_cast<int>(_val); + } else { + _int_val = boost::lexical_cast<int>(_val); + } + break; + + case expression::TYPE_DOUBLE: + _double_val = boost::lexical_cast<double>(_val); + break; + + case expression::TYPE_BOOL: + if (boost::to_upper_copy(_val) == "TRUE") { + _bool_val = true; + } else { + // lexical cast to bool is too picky + _bool_val = bool(boost::lexical_cast<int>(_val)); + } + break; + + case expression::TYPE_INT_VECTOR: + { + std::string str_vec = _val.substr(1, _val.size()-2); + std::vector<std::string> subtoken_list; + boost::split(subtoken_list, str_vec, boost::is_any_of(", "), boost::token_compress_on); + BOOST_FOREACH(const std::string &t, subtoken_list) { + _int_vector_val.push_back(boost::lexical_cast<int>(t)); + } + break; + } + + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +expression_literal::expression_literal(bool b) + : _bool_val(b) + , _int_val(0) + , _double_val(0.0) + , _val("") + , _type(expression::TYPE_BOOL) +{ + // nop +} + +expression_literal::expression_literal(int i) + : _bool_val(false) + , _int_val(i) + , _double_val(0.0) + , _val("") + , _type(expression::TYPE_INT) +{ + // nop +} + +expression_literal::expression_literal(double d) + : _bool_val(false) + , _int_val(0) + , _double_val(d) + , _val("") + , _type(expression::TYPE_DOUBLE) +{ + // nop +} + +expression_literal::expression_literal(const std::string &s) + : _bool_val(false) + , _int_val(0) + , _double_val(0.0) + , _val(s) + , _type(expression::TYPE_STRING) +{ + // nop +} + +expression_literal::expression_literal(const std::vector<int> v) + : _bool_val(false) + , _int_val(0) + , _double_val(0.0) + , _int_vector_val(v) + , _val("") + , _type(expression::TYPE_INT_VECTOR) +{ + // nop +} + +bool expression_literal::to_bool() const +{ + switch (_type) { + case TYPE_INT: + return bool(boost::lexical_cast<int>(_val)); + case TYPE_STRING: + return not _val.empty(); + case TYPE_DOUBLE: + return bool(boost::lexical_cast<double>(_val)); + case TYPE_BOOL: + return _bool_val; + case TYPE_INT_VECTOR: + return not _int_vector_val.empty(); + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +int expression_literal::get_int() const +{ + if (_type != TYPE_INT) { + throw uhd::type_error("Cannot call get_int() on non-int value."); + } + + return _int_val; +} + +double expression_literal::get_double() const +{ + if (_type != TYPE_DOUBLE) { + throw uhd::type_error("Cannot call get_double() on non-double value."); + } + + return _double_val; +} + +std::string expression_literal::get_string() const +{ + if (_type != TYPE_STRING) { + throw uhd::type_error("Cannot call get_string() on non-string value."); + } + + return _val; +} + +bool expression_literal::get_bool() const +{ + if (_type != TYPE_BOOL) { + throw uhd::type_error("Cannot call get_bool() on non-boolean value."); + } + + return _bool_val; +} + +std::vector<int> expression_literal::get_int_vector() const +{ + if (_type != TYPE_INT_VECTOR) { + throw uhd::type_error("Cannot call get_bool() on non-boolean value."); + } + + return _int_vector_val; +} + +std::string expression_literal::repr() const +{ + switch (_type) { + case TYPE_INT: + return boost::lexical_cast<std::string>(_int_val); + case TYPE_STRING: + return _val; + case TYPE_DOUBLE: + return boost::lexical_cast<std::string>(_double_val); + case TYPE_BOOL: + return _bool_val ? "TRUE" : "FALSE"; + case TYPE_INT_VECTOR: + { + std::stringstream sstr; + sstr << "["; + for (size_t i = 0; i < _int_vector_val.size(); i++) { + if (i > 0) { + sstr << ", "; + } + sstr << _int_vector_val[i]; + } + sstr << "]"; + return sstr.str(); + } + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +bool expression_literal::operator==(const expression_literal &rhs) const +{ + if (rhs.infer_type() != _type) { + return false; + } + + switch (_type) { + case TYPE_INT: + return get_int() == rhs.get_int(); + case TYPE_STRING: + return get_string() == rhs.get_string(); + case TYPE_DOUBLE: + return get_double() == rhs.get_double(); + case TYPE_BOOL: + return get_bool() == rhs.get_bool(); + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +/******************************************************************** + * Containers + *******************************************************************/ +expression_container::sptr expression_container::make() +{ + return sptr(new expression_container); +} + +expression::type_t expression_container::infer_type() const +{ + if (_combiner == COMBINE_OR or _combiner == COMBINE_AND) { + return TYPE_BOOL; + } + + if (_sub_exprs.empty()) { + return TYPE_BOOL; + } + + return _sub_exprs.back()->infer_type(); +} + +void expression_container::add(expression::sptr new_expr) +{ + _sub_exprs.push_back(new_expr); +} + +bool expression_container::empty() const +{ + return _sub_exprs.empty(); +} + +void expression_container::set_combiner_safe(const combiner_type c) +{ + if (_combiner == COMBINE_NOTSET) { + _combiner = c; + return; + } + + throw uhd::syntax_error("Attempting to override combiner type"); +} + +expression_literal expression_container::eval() +{ + if (_sub_exprs.empty()) { + return expression_literal(true); + } + + expression_literal ret_val; + BOOST_FOREACH(const expression::sptr &sub_expr, _sub_exprs) { + ret_val = sub_expr->eval(); + if (_combiner == COMBINE_AND and ret_val.to_bool() == false) { + return ret_val; + } + if (_combiner == COMBINE_OR and ret_val.to_bool() == true) { + return ret_val; + } + // For ALL, we return the last one, so just overwrite it + } + return ret_val; +} + +/******************************************************************** + * Functions + *******************************************************************/ +std::string expression_function::to_string(const std::string &name, const argtype_list_type &types) +{ + std::string s = name; + int arg_count = 0; + BOOST_FOREACH(const expression::type_t type, types) { + if (arg_count == 0) { + s += "("; + } else { + s += ", "; + } + s += type_repr[type]; + arg_count++; + } + s += ")"; + + return s; +} + +expression_function::expression_function( + const std::string &name, + const function_table::sptr func_table +) : _name(name) + , _func_table(func_table) +{ + _combiner = COMBINE_ALL; + if (not _func_table->function_exists(_name)) { + throw uhd::syntax_error(str( + boost::format("Unknown function: %s") + % _name + )); + } +} + +void expression_function::add(expression::sptr new_expr) +{ + expression_container::add(new_expr); + _arg_types.push_back(new_expr->infer_type()); +} + +expression::type_t expression_function::infer_type() const +{ + return _func_table->get_type(_name, _arg_types); +} + +expression_literal expression_function::eval() +{ + return _func_table->eval(_name, _arg_types, _sub_exprs); +} + + +std::string expression_function::repr() const +{ + return to_string(_name, _arg_types); +} + +expression_function::sptr expression_function::make( + const std::string &name, + const function_table::sptr func_table +) { + return sptr(new expression_function(name, func_table)); +} + +/******************************************************************** + * Variables + *******************************************************************/ +expression_variable::expression_variable( + const std::string &token_val, + type_getter_type type_getter, + value_getter_type value_getter +) : _type_getter(type_getter) + , _value_getter(value_getter) +{ + // We can assume this is true because otherwise, it's not a valid token: + UHD_ASSERT_THROW(not token_val.empty() and token_val[0] == '$'); + + _varname = token_val.substr(1); +} + +expression::type_t expression_variable::infer_type() const +{ + return _type_getter(_varname); +} + +expression_literal expression_variable::eval() +{ + return _value_getter(_varname); +} + +expression_variable::sptr expression_variable::make( + const std::string &token_val, + type_getter_type type_getter, + value_getter_type value_getter +) { + return sptr(new expression_variable(token_val, type_getter, value_getter)); +} + diff --git a/host/lib/rfnoc/nocscript/expression.hpp b/host/lib/rfnoc/nocscript/expression.hpp new file mode 100644 index 000000000..83fc5bcbc --- /dev/null +++ b/host/lib/rfnoc/nocscript/expression.hpp @@ -0,0 +1,380 @@ +// +// Copyright 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/>. +// + +#include <uhd/exception.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/function.hpp> +#include <boost/make_shared.hpp> +#include <vector> +#include <map> + +#ifndef INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_EXPR_HPP +#define INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_EXPR_HPP + +namespace uhd { namespace rfnoc { namespace nocscript { + +// Forward declaration for expression::eval() +class expression_literal; + +/*! Virtual base class for Noc-Script expressions. + */ +class expression +{ + public: + typedef boost::shared_ptr<expression> sptr; + + //! All the possible return types for expressions within Noc-Script + enum type_t { + TYPE_INT, + TYPE_DOUBLE, + TYPE_STRING, + TYPE_BOOL, + TYPE_INT_VECTOR + }; + + // TODO make this a const and fix the [] usage + static std::map<type_t, std::string> type_repr; + + //! Returns the type of this expression without evaluating it + virtual type_t infer_type() const = 0; + + //! Evaluate current expression and return its return value + virtual expression_literal eval() = 0; +}; + +/*! Literal (constant) expression class + * + * A literal is any value that is literally given in the NoC-Script + * source code, such as '5', '"FOO"', or '2.3'. + */ +class expression_literal : public expression +{ + public: + typedef boost::shared_ptr<expression_literal> sptr; + + template <typename expr_type> + static sptr make(expr_type x) { return boost::make_shared<expression_literal>(x); }; + + /*! Generate the literal expression from its token string representation. + * This includes markup, e.g. a string would still have the quotes, and + * a hex value would still have leading 0x. + */ + expression_literal( + const std::string token_val, + expression::type_t type + ); + + //! Create a boolean literal expression from a C++ bool. + expression_literal(bool b=false); + //! Create an integer literal expression from a C++ int. + expression_literal(int i); + //! Create a double literal expression from a C++ double. + expression_literal(double d); + //! Create a string literal expression from a C++ string. + expression_literal(const std::string &s); + //! Create an int vector literal expression from a C++ vector<int>. + expression_literal(std::vector<int> v); + + expression::type_t infer_type() const + { + return _type; + } + + //! Literals aren't evaluated as such, so the evaluation + // simply returns a copy of itself. + expression_literal eval() + { + return *this; // TODO make sure this is copy + } + + /*! A 'type cast' to bool. Cast rules are similar to most + * scripting languages: + * - Integers and doubles are false if zero, true otherwise + * - Strings are false if empty, true otherwise + * - Vectors are false if empty, true otherwise + */ + bool to_bool() const; + + /*! Convenience function to typecast to C++ int + * + * Note that the current type must be TYPE_INT. + * + * \return C++ int representation of current literal + * \throws uhd::type_error if type didn't match + */ + int get_int() const; + + /*! Convenience function to typecast to C++ double + * + * Note that the current type must be TYPE_DOUBLE. + * + * \return C++ double representation of current literal + * \throws uhd::type_error if type didn't match + */ + double get_double() const; + + /*! Convenience function to typecast to C++ std::string. + * + * Note that the current type must be TYPE_STRING. + * + * \return String representation of current literal. + * \throws uhd::type_error if type didn't match. + */ + std::string get_string() const; + + /*! Convenience function to typecast to C++ int vector. + * + * Note that the current type must be TYPE_INT_VECTOR. + * + * \return String representation of current literal. + * \throws uhd::type_error if type didn't match. + */ + std::vector<int> get_int_vector() const; + + /*! Convenience function to typecast to C++ bool. + * + * Note that the current type must be TYPE_BOOL. + * See also expression_literal::to_bool() for a type-cast + * style function. + * + * \return bool representation of current literal. + * \throws uhd::type_error if type didn't match. + */ + bool get_bool() const; + + //! String representation + std::string repr() const; + + bool operator==(const expression_literal &rhs) const; + + private: + //! For TYPE_BOOL + bool _bool_val; + + //! For TYPE_INT + int _int_val; + + //! For TYPE_DOUBLE + double _double_val; + + //! For TYPE_INT_VECTOR + std::vector<int> _int_vector_val; + + //! Store the token value + std::string _val; + + //! Current expression type + expression::type_t _type; +}; + +UHD_INLINE std::ostream& operator<< (std::ostream& out, const expression_literal &l) +{ + out << l.repr(); + return out; +} + +UHD_INLINE std::ostream& operator<< (std::ostream& out, const expression_literal::sptr &l) +{ + out << l->repr(); + return out; +} + +/*! Contains multiple (sub-)expressions. + */ +class expression_container : public expression +{ + public: + typedef boost::shared_ptr<expression_container> sptr; + typedef std::vector<expression::sptr> expr_list_type; + + //! Return an sptr to an empty container + static sptr make(); + + //! List of valid combination types (see expression_container::eval()). + enum combiner_type { + COMBINE_ALL, + COMBINE_AND, + COMBINE_OR, + COMBINE_NOTSET + }; + + //! Create an empty container + expression_container() : _combiner(COMBINE_NOTSET) {}; + + /*! Type-deduction rules for containers are: + * - If the combination type is COMBINE_ALL or COMBINE_AND, + * return value must be TYPE_BOOL + * - In all other cases, we return the last expression return + * value, and hence its type is relevant + */ + expression::type_t infer_type() const; + + /*! Add another expression container to this container. + */ + virtual void add(expression::sptr new_expr); + + virtual bool empty() const; + + void set_combiner_safe(const combiner_type c); + + void set_combiner(const combiner_type c) { _combiner = c; }; + + combiner_type get_combiner() const { return _combiner; }; + + /*! Evaluate a container by evaluating its sub-expressions. + * + * If a container contains multiple sub-expressions, the rules + * for evaluating them depend on the combiner_type: + * - COMBINE_ALL: Run all the sub-expressions and return the last + * expression's return value + * - COMBINE_AND: Run sub-expressions, in order, until one of them + * returns false. Following expressions are not evaluated (like + * most C++ compilers). + * - COMBINE_OR: Run sub-expressions, in order, until one of them + * returns true. Following expressions are not evaluated. + * + * In the special case where no sub-expressions are contained, always + * returns true. + */ + virtual expression_literal eval(); + + protected: + //! Store all the sub-expressions, in order + expr_list_type _sub_exprs; + combiner_type _combiner; +}; + +// Forward declaration: +class function_table; +/*! A function call is a special type of container. + * + * All arguments are sub-expressions. The combiner type is + * always COMBINE_ALL in this case (changing the combiner type + * does not affect anything). + * + * The actual function maps to a C++ function available through + * a uhd::rfnoc::nocscript::function_table object. + * + * The recommended to use this is: + * 1. Create a function object giving its name (e.g. ADD) + * 2. Use the add() method to add all the function arguments + * in the right order (left to right). + * 3. Once step 2 is complete, the function object can be used. + * Call infer_type() to get the return value, if required. + * 4. Calling eval() will call into the function table. The + * argument expressions are evaluated, if so required, inside + * the function (lazy evalulation). Functions do not need + * to evaluate arguments. + */ +class expression_function : public expression_container +{ + public: + typedef boost::shared_ptr<expression_function> sptr; + typedef std::vector<expression::type_t> argtype_list_type; + + //! Return an sptr to a function object without args + static sptr make( + const std::string &name, + const boost::shared_ptr<function_table> func_table + ); + + static std::string to_string(const std::string &name, const argtype_list_type &types); + + expression_function( + const std::string &name, + const boost::shared_ptr<function_table> func_table + ); + + //! Add an argument expression + virtual void add(expression::sptr new_expr); + + /*! Looks up the function type in the function table. + * + * Note that this will only work after all arguments have been + * added, as they are also used to look up a function's type in the + * function table. + */ + expression::type_t infer_type() const; + + /*! Evaluate all arguments, then the function itself. + */ + expression_literal eval(); + + //! String representation + std::string repr() const; + + private: + std::string _name; + const boost::shared_ptr<function_table> _func_table; + std::vector<expression::type_t> _arg_types; +}; + + +/*! Variable expression + * + * Variables are like literals, only their type and value aren't known + * at parse-time. Instead, we provide a function object to look up + * variable's types and value. + */ +class expression_variable : public expression +{ + public: + typedef boost::shared_ptr<expression_variable> sptr; + typedef boost::function<expression::type_t(const std::string &)> type_getter_type; + typedef boost::function<expression_literal(const std::string &)> value_getter_type; + + static sptr make( + const std::string &token_val, + type_getter_type type_getter, + value_getter_type value_getter + ); + + /*! Create a variable object from its token value + * (e.g. '$spp', i.e. including the '$' symbol). The variable + * does not have to exist at this point. + */ + expression_variable( + const std::string &token_val, + type_getter_type type_getter, + value_getter_type value_getter + ); + + /*! Looks up the variable type in the variable table. + * + * \throws Depending on \p type_getter, this may throw when the variable does not exist. + * Recommended behaviour is to throw uhd::syntax_error. + */ + expression::type_t infer_type() const; + + /*! Look up a variable's value in the variable table. + * + * \throws Depending on \p value_getter, this may throw when the variable does not exist. + * Recommended behaviour is to throw uhd::syntax_error. + */ + expression_literal eval(); + + private: + std::string _varname; + type_getter_type _type_getter; + value_getter_type _value_getter; +}; + +}}} /* namespace uhd::rfnoc::nocscript */ + +#endif /* INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_EXPR_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/nocscript/function_table.cpp b/host/lib/rfnoc/nocscript/function_table.cpp new file mode 100644 index 000000000..bebceb8dc --- /dev/null +++ b/host/lib/rfnoc/nocscript/function_table.cpp @@ -0,0 +1,113 @@ +// +// Copyright 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/>. +// + +#include "function_table.hpp" +#include "basic_functions.hpp" +#include <boost/bind.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <map> + +using namespace uhd::rfnoc::nocscript; + +class function_table_impl : public function_table +{ + public: + struct function_info { + expression::type_t return_type; + function_ptr function; + + function_info() {}; + function_info(const expression::type_t return_type_, const function_ptr &function_) + : return_type(return_type_), function(function_) + {}; + }; + // Should be an unordered_map... sigh, we'll get to C++11 someday. + typedef std::map<std::string, std::map<expression_function::argtype_list_type, function_info> > table_type; + + /************************************************************************ + * Structors + ***********************************************************************/ + function_table_impl() + { + _REGISTER_ALL_FUNCS(); + } + + ~function_table_impl() {}; + + + /************************************************************************ + * Interface implementation + ***********************************************************************/ + bool function_exists(const std::string &name) const { + return bool(_table.count(name)); + } + + bool function_exists( + const std::string &name, + const expression_function::argtype_list_type &arg_types + ) const { + table_type::const_iterator it = _table.find(name); + return (it != _table.end()) and bool(it->second.count(arg_types)); + } + + expression::type_t get_type( + const std::string &name, + const expression_function::argtype_list_type &arg_types + ) const { + table_type::const_iterator it = _table.find(name); + if (it == _table.end() or (it->second.find(arg_types) == it->second.end())) { + throw uhd::syntax_error(str( + boost::format("Unable to retrieve return value for function %s") + % expression_function::to_string(name, arg_types) + )); + } + return it->second.find(arg_types)->second.return_type; + } + + expression_literal eval( + const std::string &name, + const expression_function::argtype_list_type &arg_types, + expression_container::expr_list_type &arguments + ) { + if (not function_exists(name, arg_types)) { + throw uhd::syntax_error(str( + boost::format("Cannot eval() function %s, not a known signature") + % expression_function::to_string(name, arg_types) + )); + } + + return _table[name][arg_types].function(arguments); + } + + void register_function( + const std::string &name, + const function_table::function_ptr &ptr, + const expression::type_t return_type, + const expression_function::argtype_list_type &sig + ) { + _table[name][sig] = function_info(return_type, ptr); + } + + private: + table_type _table; +}; + +function_table::sptr function_table::make() +{ + return sptr(new function_table_impl()); +} diff --git a/host/lib/rfnoc/nocscript/function_table.hpp b/host/lib/rfnoc/nocscript/function_table.hpp new file mode 100644 index 000000000..6c715308e --- /dev/null +++ b/host/lib/rfnoc/nocscript/function_table.hpp @@ -0,0 +1,93 @@ +// +// Copyright 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/>. +// + +#include "expression.hpp" +#include <boost/shared_ptr.hpp> +#include <boost/function.hpp> +#include <vector> + +#ifndef INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_FUNCTABLE_HPP +#define INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_FUNCTABLE_HPP + +namespace uhd { namespace rfnoc { namespace nocscript { + +class function_table +{ + public: + typedef boost::shared_ptr<function_table> sptr; + typedef boost::function<expression_literal(expression_container::expr_list_type&)> function_ptr; + + static sptr make(); + virtual ~function_table() {}; + + /*! Check if any function with a given name exists + * + * \returns True, if any function with name \p name is registered. + */ + virtual bool function_exists(const std::string &name) const = 0; + + /*! Check if a function with a given name and list of argument types exists + * + * \returns True, if such a function is registered. + */ + virtual bool function_exists( + const std::string &name, + const expression_function::argtype_list_type &arg_types + ) const = 0; + + /*! Get the return type of a function with given name and argument type list + * + * \returns The function's return type + * \throws uhd::syntax_error if no such function is registered + */ + virtual expression::type_t get_type( + const std::string &name, + const expression_function::argtype_list_type &arg_types + ) const = 0; + + /*! Calls the function \p name with the argument list \p arguments + * + * \param arg_types A list of types for each argument + * \param arguments An expression list of the arguments + * \returns The return value of the called function + * \throws uhd::syntax_error if no such function is found + */ + virtual expression_literal eval( + const std::string &name, + const expression_function::argtype_list_type &arg_types, + expression_container::expr_list_type &arguments + ) = 0; + + /*! Register a new function + * + * \param name Name of the function (e.g. 'ADD') + * \param ptr Function object + * \param return_type The function's return value + * \param sig The function signature (list of argument types) + */ + virtual void register_function( + const std::string &name, + const function_ptr &ptr, + const expression::type_t return_type, + const expression_function::argtype_list_type &sig + ) = 0; +}; + +}}} /* namespace uhd::rfnoc::nocscript */ + +#endif /* INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_FUNCTABLE_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/nocscript/gen_basic_funcs.py b/host/lib/rfnoc/nocscript/gen_basic_funcs.py new file mode 100755 index 000000000..702b3e884 --- /dev/null +++ b/host/lib/rfnoc/nocscript/gen_basic_funcs.py @@ -0,0 +1,474 @@ +#!/usr/bin/env python +""" +Generate the function list for the basic NocScript functions +""" + +import re +import os +import sys +from mako.template import Template + +############################################################################# +# This is the interesting part: Add new functions in here +# +# Notes: +# - Lines starting with # are considered comments, and will be removed from +# the output +# - C++ comments will be copied onto the generated file if inside functions +# - Docstrings start with //! and are required +# - Function signature is RETURN_TYPE NAME(ARG_TYPE1, ARG_TYPE2, ...) +# - Function body is valid C++ +# - If your function requires special includes, put them in INCLUDE_LIST +# - End of functions is delimited by s/^}/, so take care with the indents! +# - Use these substitutions: +# - ${RETURN}(...): Create a valid return value +# - ${args[n]}: Access the n-th argument +# +INCLUDE_LIST = """ +#include <boost/math/special_functions/round.hpp> +#include <boost/thread/thread.hpp> +""" +FUNCTION_LIST = """ +CATEGORY: Math Functions +//! Returns x + y +INT ADD(INT, INT) +{ + ${RETURN}(${args[0]} + ${args[1]}); +} + +//! Returns x + y +DOUBLE ADD(DOUBLE, DOUBLE) +{ + ${RETURN}(${args[0]} + ${args[1]}); +} + +//! Returns x * y +DOUBLE MULT(DOUBLE, DOUBLE) +{ + ${RETURN}(${args[0]} * ${args[1]}); +} + +//! Returns x * y +INT MULT(INT, INT) +{ + ${RETURN}(${args[0]} * ${args[1]}); +} + +//! Returns x / y +DOUBLE DIV(DOUBLE, DOUBLE) +{ + ${RETURN}(${args[0]} / ${args[1]}); +} + +//! Returns true if x <= y (Less or Equal) +BOOL LE(INT, INT) +{ + ${RETURN}(bool(${args[0]} <= ${args[1]})); +} + +//! Returns true if x <= y (Less or Equal) +BOOL LE(DOUBLE, DOUBLE) +{ + ${RETURN}(bool(${args[0]} <= ${args[1]})); +} + +//! Returns true if x >= y (Greater or Equal) +BOOL GE(INT, INT) +{ + ${RETURN}(bool(${args[0]} >= ${args[1]})); +} + +//! Returns true if x >= y (Greater or Equal) +BOOL GE(DOUBLE, DOUBLE) +{ + ${RETURN}(bool(${args[0]} >= ${args[1]})); +} + +//! Returns true if x < y (Less Than) +BOOL LT(INT, INT) +{ + ${RETURN}(bool(${args[0]} < ${args[1]})); +} + +//! Returns true if x > y (Greater Than) +BOOL GT(INT, INT) +{ + ${RETURN}(bool(${args[0]} > ${args[1]})); +} + +//! Returns true if x < y (Less Than) +BOOL LT(DOUBLE, DOUBLE) +{ + ${RETURN}(bool(${args[0]} < ${args[1]})); +} + +//! Returns true if x > y (Greater Than) +BOOL GT(DOUBLE, DOUBLE) +{ + ${RETURN}(bool(${args[0]} > ${args[1]})); +} + +//! Round x and return it as an integer +INT IROUND(DOUBLE) +{ + ${RETURN}(int(boost::math::iround(${args[0]}))); +} + +//! Returns true if x is a power of 2 +BOOL IS_PWR_OF_2(INT) +{ + if (${args[0]} < 0) return ${FALSE}; + int i = ${args[0]}; + while ( (i & 1) == 0 and (i > 1) ) { + i >>= 1; + } + ${RETURN}(bool(i == 1)); +} + +//! Returns floor(log2(x)). +INT LOG2(INT) +{ + if (${args[0]} < 0) { + throw uhd::runtime_error(str( + boost::format("In NocScript function ${func_name}: Cannot calculate log2() of negative number.") + )); + } + + int power_value = ${args[0]}; + int log2_value = 0; + while ( (power_value & 1) == 0 and (power_value > 1) ) { + power_value >>= 1; + log2_value++; + } + ${RETURN}(log2_value); +} + +//! Returns x % y +INT MODULO(INT, INT) +{ + ${RETURN}(${args[0]} % ${args[1]}); +} + +//! Returns true if x == y +BOOL EQUAL(INT, INT) +{ + ${RETURN}(bool(${args[0]} == ${args[1]})); +} + +//! Returns true if x == y +BOOL EQUAL(DOUBLE, DOUBLE) +{ + ${RETURN}(bool(${args[0]} == ${args[1]})); +} + +//! Returns true if x == y +BOOL EQUAL(STRING, STRING) +{ + ${RETURN}(bool(${args[0]} == ${args[1]})); +} + +CATEGORY: Bitwise Operations +//! Returns x >> y +INT SHIFT_RIGHT(INT, INT) +{ + ${RETURN}(${args[0]} >> ${args[1]}); +} + +//! Returns x << y +INT SHIFT_LEFT(INT, INT) +{ + ${RETURN}(${args[0]} << ${args[1]}); +} + +//! Returns x & y +INT BITWISE_AND(INT, INT) +{ + ${RETURN}(${args[0]} & ${args[1]}); +} + +//! Returns x | y +INT BITWISE_OR(INT, INT) +{ + ${RETURN}(${args[0]} | ${args[1]}); +} + +//! Returns x ^ y +INT BITWISE_XOR(INT, INT) +{ + ${RETURN}(${args[0]} ^ ${args[1]}); +} + +CATEGORY: Boolean Logic +//! Returns x xor y. +BOOL XOR(BOOL, BOOL) +{ + ${RETURN}(${args[0]} xor ${args[1]}); +} + +//! Returns !x +BOOL NOT(BOOL) +{ + ${RETURN}(not ${args[0]}); +} + +//! Always returns true +BOOL TRUE() +{ + return ${TRUE}; +} + +//! Always returns false +BOOL FALSE() +{ + return ${FALSE}; +} + +CATEGORY: Conditional Execution +//! Executes x, if true, execute y. Returns true if x is true. +BOOL IF(BOOL, BOOL) +{ + if (${args[0]}) { + ${args[1]}; + ${RETURN}(true); + } + ${RETURN}(false); +} + +//! Executes x, if true, execute y, otherwise, execute z. Returns true if x is true. +BOOL IF_ELSE(BOOL, BOOL, BOOL) +{ + if (${args[0]}) { + ${args[1]}; + ${RETURN}(true); + } else { + ${args[2]}; + } + ${RETURN}(false); +} + +CATEGORY: Execution Control +//! Sleep for x seconds. Fractions are allowed. Millisecond accuracy. +BOOL SLEEP(DOUBLE) +{ + int ms = ${args[0]} / 1000; + boost::this_thread::sleep(boost::posix_time::milliseconds(ms)); + ${RETURN}(true); +} +""" +# End of interesting part. The rest will take this and turn into a C++ +# header file. +############################################################################# + +HEADER = """<% import time %>// +/////////////////////////////////////////////////////////////////////// +// This file was generated by ${file} on ${time.strftime("%c")} +/////////////////////////////////////////////////////////////////////// +// Copyright 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/>. +// + +/**************************************************************************** + * This file is autogenerated! Any manual changes in here will be + * overwritten by calling nocscript_gen_basic_funcs.py! + ***************************************************************************/ + +#include "expression.hpp" +#include "function_table.hpp" +#include <uhd/exception.hpp> +#include <boost/format.hpp> +#include <boost/assign/list_of.hpp> +${INCLUDE_LIST} + +#ifndef INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_BASICFUNCS_HPP +#define INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_BASICFUNCS_HPP + +namespace uhd { namespace rfnoc { namespace nocscript { +""" + +# Not a Mako template: +FOOTER=""" +}}} /* namespace uhd::rfnoc::nocscript */ + +#endif /* INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_BASICFUNCS_HPP */ +""" + +# Not a Mako template: +FUNC_TEMPLATE = """ +expression_literal {NAME}(expression_container::expr_list_type &{ARGS}) +{BODY} +""" + +REGISTER_MACRO_TEMPLATE = """#define _REGISTER_ALL_FUNCS()${registry} +""" + +REGISTER_COMMANDS_TEMPLATE = """ + % if len(arglist): + expression_function::argtype_list_type ${func_name}_args = boost::assign::list_of + % for this_type in arglist: + (expression::TYPE_${this_type}) + % endfor + ; + % else: + expression_function::argtype_list_type ${func_name}_args; + % endif + register_function( + "${name}", + boost::bind(&${func_name}, _1), + expression::TYPE_${retval}, + ${func_name}_args + );""" + +DOXY_TEMPLATE = """/*! \page page_nocscript_funcs NocScript Function Reference +% for cat, func_by_name in func_list_tree.iteritems(): +- ${cat} +% for func_name, func_info_list in func_by_name.iteritems(): + - ${func_name}: ${func_info_list[0]['docstring']} +% for func_info in func_info_list: + - ${func_info['arglist']} -> ${func_info['retval']} +% endfor +% endfor +% endfor + +*/ +""" + +def parse_tmpl(_tmpl_text, **kwargs): + return Template(_tmpl_text).render(**kwargs) + +def make_cxx_func_name(func_dict): + """ + Creates a unique C++ function name from a function description + """ + return "{name}__{retval}__{arglist}".format( + name=func_dict['name'], + retval=func_dict['retval'], + arglist="_".join(func_dict['arglist']) + ) + +def make_cxx_func_body(func_dict): + """ + Formats the function body properly + """ + type_lookup_methods = { + 'INT': 'get_int', + 'DOUBLE': 'get_double', + 'BOOL': 'get_bool', + 'STRING': 'get_string', + } + args_lookup = [] + for idx, arg_type in enumerate(func_dict['arglist']): + args_lookup.append("args[{idx}]->eval().{getter}()".format(idx=idx, getter=type_lookup_methods[arg_type])) + return parse_tmpl( + func_dict['body'], + args=args_lookup, + FALSE='expression_literal(false)', + TRUE='expression_literal(true)', + RETURN='return expression_literal', + **func_dict + ) + +def prep_function_list(): + """ + - Remove all comments + - Split the function list into individual functions + - Split the functions into return value, name, argument list and body + """ + comment_remove_re = re.compile(r'^\s*#.*$', flags=re.MULTILINE) + func_list_wo_comments = comment_remove_re.sub('', FUNCTION_LIST) + func_splitter_re = re.compile(r'(?<=^})\s*$', flags=re.MULTILINE) + func_list_split = func_splitter_re.split(func_list_wo_comments) + func_list_split = [x.strip() for x in func_list_split if len(x.strip())] + func_list = [] + last_category = '' + for func in func_list_split: + split_regex = r'(^CATEGORY: (?P<cat>[^\n]*)\s*)?' \ + r'//!(?P<docstring>[^\n]*)\s*' + \ + r'(?P<retval>[A-Z][A-Z0-9_]*)\s+' + \ + r'(?P<funcname>[A-Z][A-Z0-9_]*)\s*\((?P<arglist>[^\)]*)\)\s*' + \ + r'(?P<funcbody>^{.*)' + split_re = re.compile(split_regex, flags=re.MULTILINE|re.DOTALL) + mo = split_re.match(func) + if mo.group('cat'): + last_category = mo.group('cat').strip() + func_dict = { + 'docstring': mo.group('docstring').strip(), + 'name': mo.group('funcname'), + 'retval': mo.group('retval'), + 'arglist': [x.strip() for x in mo.group('arglist').split(',') if len(x.strip())], + 'body': mo.group('funcbody'), + 'category': last_category, + } + func_dict['func_name'] = make_cxx_func_name(func_dict) + func_list.append(func_dict) + return func_list + +def write_function_header(output_filename): + """ + Create the .hpp file that defines all the NocScript functions in C++. + """ + func_list = prep_function_list() + # Step 1: Write the prototypes + func_prototypes = '' + registry_commands = '' + for func in func_list: + func_prototypes += FUNC_TEMPLATE.format( + NAME=func['func_name'], + BODY=make_cxx_func_body(func), + ARGS="args" if len(func['arglist']) else "" + ) + registry_commands += parse_tmpl( + REGISTER_COMMANDS_TEMPLATE, + **func + ) + # Step 2: Write the registry process + register_func = parse_tmpl(REGISTER_MACRO_TEMPLATE, registry=registry_commands) + register_func = register_func.replace('\n', ' \\\n') + + # Final step: Join parts and write to file + full_file = "\n".join(( + parse_tmpl(HEADER, file = os.path.basename(__file__), INCLUDE_LIST=INCLUDE_LIST), + func_prototypes, + register_func, + FOOTER, + )) + open(output_filename, 'w').write(full_file) + +def write_manual_file(output_filename): + """ + Write the Doxygen file for the NocScript functions. + """ + func_list = prep_function_list() + func_list_tree = {} + for func in func_list: + if not func_list_tree.has_key(func['category']): + func_list_tree[func['category']] = {} + if not func_list_tree[func['category']].has_key(func['name']): + func_list_tree[func['category']][func['name']] = [] + func_list_tree[func['category']][func['name']].append(func) + open(output_filename, 'w').write(parse_tmpl(DOXY_TEMPLATE, func_list_tree=func_list_tree)) + + +def main(): + if len(sys.argv) < 2: + print("No output file specified!") + exit(1) + outfile = sys.argv[1] + if os.path.splitext(outfile)[1] == '.dox': + write_manual_file(outfile) + else: + write_function_header(outfile) + +if __name__ == "__main__": + main() diff --git a/host/lib/rfnoc/nocscript/parser.cpp b/host/lib/rfnoc/nocscript/parser.cpp new file mode 100644 index 000000000..bb7ed6cdf --- /dev/null +++ b/host/lib/rfnoc/nocscript/parser.cpp @@ -0,0 +1,363 @@ +// +// Copyright 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/>. +// + +#include "parser.hpp" +#include <uhd/utils/cast.hpp> +#include <boost/spirit/include/lex_lexertl.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <boost/assign.hpp> +#include <boost/make_shared.hpp> +#include <sstream> +#include <stack> + +using namespace uhd::rfnoc::nocscript; +namespace lex = boost::spirit::lex; + +class parser_impl : public parser +{ + public: + /****************************************************************** + * Structors TODO make them protected + *****************************************************************/ + parser_impl( + function_table::sptr ftable, + expression_variable::type_getter_type var_type_getter, + expression_variable::value_getter_type var_value_getter + ) : _ftable(ftable) + , _var_type_getter(var_type_getter) + , _var_value_getter(var_value_getter) + { + // nop + } + + ~parser_impl() {}; + + + /****************************************************************** + * Parsing + *****************************************************************/ + //! List of parser tokens + enum token_ids + { + ID_WHITESPACE = lex::min_token_id + 42, + ID_KEYWORD, + ID_ARG_SEP, + ID_PARENS_OPEN, + ID_PARENS_CLOSE, + ID_VARIABLE, + ID_LITERAL_DOUBLE, + ID_LITERAL_INT, + ID_LITERAL_HEX, + ID_LITERAL_STR, + ID_LITERAL_VECTOR_INT + }; + + //! The Lexer object used for NocScript + template <typename Lexer> + struct ns_lexer : lex::lexer<Lexer> + { + ns_lexer() + { + this->self.add + ("\\s+", ID_WHITESPACE) + (",", ID_ARG_SEP) + ("[A-Z][A-Z0-9_]*", ID_KEYWORD) + ("\\(", ID_PARENS_OPEN) + ("\\)", ID_PARENS_CLOSE) + ("\\$[a-z][a-z0-9_]*", ID_VARIABLE) + ("-?\\d+\\.\\d+", ID_LITERAL_DOUBLE) + ("-?\\d+", ID_LITERAL_INT) + ("0x[0-9A-F]+", ID_LITERAL_HEX) + ("\\\"[^\\\"]*\\\"", ID_LITERAL_STR) + ("'[^']*'", ID_LITERAL_STR) // both work + ("\\[[0-9]\\]", ID_LITERAL_VECTOR_INT) + ; + } + }; + + private: + struct grammar_props + { + function_table::sptr ftable; + expression_variable::type_getter_type var_type_getter; + expression_variable::value_getter_type var_value_getter; + + //! Store the last keyword + std::string function_name; + std::string error; + std::stack<expression_container::sptr> expr_stack; + + grammar_props( + function_table::sptr ftable_, + expression_variable::type_getter_type var_type_getter_, + expression_variable::value_getter_type var_value_getter_ + ) : ftable(ftable_), var_type_getter(var_type_getter_), var_value_getter(var_value_getter_), + function_name("") + { + UHD_ASSERT_THROW(expr_stack.empty()); + // Push an empty container to the stack to hold the result + expr_stack.push(expression_container::make()); + } + + expression::sptr get_result() + { + UHD_ASSERT_THROW(expr_stack.size() == 1); + return expr_stack.top(); + } + }; + + //! This isn't strictly a grammar, as it also includes semantic + // actions etc. I'm not going to spend ages thinking of a better + // name at this point. + struct grammar + { + // Implementation detail specific to boost::bind (see Boost::Spirit + // examples) + typedef bool result_type; + + static const int VALID_COMMA = 0x1; + static const int VALID_PARENS_OPEN = 0x2; + static const int VALID_PARENS_CLOSE = 0x4; + static const int VALID_EXPRESSION = 0x8 + 0x02; + static const int VALID_OPERATOR = 0x10; + + // !This function operator gets called for each of the matched tokens. + template <typename Token> + bool operator()(Token const& t, grammar_props &P, int &next_valid_state) const + { + //! This is totally not how Boost::Spirit is meant to be used, + // as there's token types etc. But for now let's just convert + // every token to a string, and then handle it as such. + std::stringstream sstr; + sstr << t.value(); + std::string val = sstr.str(); + //std::cout << "VAL: " << val << std::endl; + //std::cout << "Next valid states:\n" + //<< boost::format("VALID_COMMA [%s]\n") % ((next_valid_state & 0x1) ? "x" : " ") + //<< boost::format("VALID_PARENS_OPEN [%s]\n") % ((next_valid_state & 0x2) ? "x" : " ") + //<< boost::format("VALID_PARENS_CLOSE [%s]\n") % ((next_valid_state & 0x4) ? "x" : " ") + //<< boost::format("VALID_EXPRESSION [%s]\n") % ((next_valid_state & (0x8 + 0x02)) ? "x" : " ") + //<< boost::format("VALID_OPERATOR [%s]\n") % ((next_valid_state & 0x10) ? "x" : " ") + //<< std::endl; + + switch (t.id()) { + + case ID_WHITESPACE: + // Ignore + break; + + case ID_KEYWORD: + // Ambiguous, could be an operator (AND, OR) or a function name (ADD, MULT...). + // So first, check which it is: + if (val == "AND" or val == "OR") { + if (not (next_valid_state & VALID_OPERATOR)) { + P.error = str(boost::format("Unexpected operator: %s") % val); + return false; + } + next_valid_state = VALID_EXPRESSION; + try { + if (val == "AND") { + P.expr_stack.top()->set_combiner_safe(expression_container::COMBINE_AND); + } else if (val == "OR") { + P.expr_stack.top()->set_combiner_safe(expression_container::COMBINE_OR); + } + } catch (const uhd::syntax_error &e) { + P.error = str(boost::format("Operator %s is mixing operator types within this container.") % val); + } + // Right now, we can't have multiple operator types within a container. + // We might be able to change that, if there's enough demand. Either + // we keep track of multiple operators, or we open a new container. + // In the latter case, we'd need a way of keeping track of those containers, + // so it's a bit tricky. + break; + } + // If it's not a keyword, it has to be a function, so check the + // function table: + if (not (next_valid_state & VALID_EXPRESSION)) { + P.error = str(boost::format("Unexpected expression: %s") % val); + return false; + } + if (not P.ftable->function_exists(val)) { + P.error = str(boost::format("Unknown function: %s") % val); + return false; + } + P.function_name = val; + next_valid_state = VALID_PARENS_OPEN; + break; + + // Every () creates a new container, either a raw container or + // a function. + case ID_PARENS_OPEN: + if (not (next_valid_state & VALID_PARENS_OPEN)) { + P.error = str(boost::format("Unexpected parentheses.")); + return false; + } + if (not P.function_name.empty()) { + // We've already checked the function name exists + P.expr_stack.push(expression_function::make(P.function_name, P.ftable)); + P.function_name.clear(); + } else { + P.expr_stack.push(expression_container::make()); + } + // Push another empty container to hold the first element/argument + // in this container: + P.expr_stack.push(expression_container::make()); + next_valid_state = VALID_EXPRESSION | VALID_PARENS_CLOSE; + break; + + case ID_PARENS_CLOSE: + { + if (not (next_valid_state & VALID_PARENS_CLOSE)) { + P.error = str(boost::format("Unexpected parentheses.")); + return false; + } + if (P.expr_stack.size() < 2) { + P.error = str(boost::format("Unbalanced closing parentheses.")); + return false; + } + // First pop the last expression inside the parentheses, + // if it's not empty, add it to the top container (this also avoids + // adding arguments to functions if none were provided): + expression_container::sptr c = P.expr_stack.top(); + P.expr_stack.pop(); + if (not c->empty()) { + P.expr_stack.top()->add(c); + } + // At the end of (), either a function or container is complete, + // so pop that and add it to its top container: + expression_container::sptr c2 = P.expr_stack.top(); + P.expr_stack.pop(); + P.expr_stack.top()->add(c2); + next_valid_state = VALID_OPERATOR | VALID_COMMA | VALID_PARENS_CLOSE; + } + break; + + case ID_ARG_SEP: + { + if (not (next_valid_state & VALID_COMMA)) { + P.error = str(boost::format("Unexpected comma.")); + return false; + } + next_valid_state = VALID_EXPRESSION; + // If stack size is 1, we're on the base container, which means we + // simply string stuff. + if (P.expr_stack.size() == 1) { + break; + } + // Otherwise, a ',' always means we add the previous expression to + // the current container: + expression_container::sptr c = P.expr_stack.top(); + P.expr_stack.pop(); + P.expr_stack.top()->add(c); + // It also means another expression is following, so create another + // empty container for that: + P.expr_stack.push(expression_container::make()); + } + break; + + // All the atomic expressions just get added to the current container: + + case ID_VARIABLE: + { + if (not (next_valid_state & VALID_EXPRESSION)) { + P.error = str(boost::format("Unexpected expression.")); + return false; + } + expression_variable::sptr v = expression_variable::make(val, P.var_type_getter, P.var_value_getter); + P.expr_stack.top()->add(v); + next_valid_state = VALID_OPERATOR | VALID_COMMA | VALID_PARENS_CLOSE; + } + break; + + default: + // If we get here, we assume it's a literal expression + { + if (not (next_valid_state & VALID_EXPRESSION)) { + P.error = str(boost::format("Unexpected expression.")); + return false; + } + expression::type_t token_type; + switch (t.id()) { // A map lookup would be more elegant, but we'd need a nicer C++ for that + case ID_LITERAL_DOUBLE: token_type = expression::TYPE_DOUBLE; break; + case ID_LITERAL_INT: token_type = expression::TYPE_INT; break; + case ID_LITERAL_HEX: token_type = expression::TYPE_INT; break; + case ID_LITERAL_STR: token_type = expression::TYPE_STRING; break; + case ID_LITERAL_VECTOR_INT: token_type = expression::TYPE_INT_VECTOR; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + P.expr_stack.top()->add(boost::make_shared<expression_literal>(val, token_type)); + next_valid_state = VALID_OPERATOR | VALID_COMMA | VALID_PARENS_CLOSE; + break; + } + + } // end switch + return true; + } + }; + + public: + expression::sptr create_expr_tree(const std::string &code) + { + // Create empty stack and keyword states + grammar_props P(_ftable, _var_type_getter, _var_value_getter); + int next_valid_state = grammar::VALID_EXPRESSION; + + // Create a lexer instance + ns_lexer<lex::lexertl::lexer<> > lexer_functor; + + // Tokenize the string + char const* first = code.c_str(); + char const* last = &first[code.size()]; + bool r = lex::tokenize( + first, last, // Iterators + lexer_functor, // Lexer + boost::bind(grammar(), _1, boost::ref(P), boost::ref(next_valid_state)) // Function object + ); + + // Check the parsing worked: + if (not r or P.expr_stack.size() != 1) { + std::string rest(first, last); + throw uhd::syntax_error(str( + boost::format("Parsing stopped at: %s\nError message: %s") + % rest % P.error + )); + } + + // Clear stack and return result + return P.get_result(); + } + + private: + + function_table::sptr _ftable; + expression_variable::type_getter_type _var_type_getter; + expression_variable::value_getter_type _var_value_getter; +}; + +parser::sptr parser::make( + function_table::sptr ftable, + expression_variable::type_getter_type var_type_getter, + expression_variable::value_getter_type var_value_getter +) { + return sptr(new parser_impl( + ftable, + var_type_getter, + var_value_getter + )); +} + diff --git a/host/lib/rfnoc/nocscript/parser.hpp b/host/lib/rfnoc/nocscript/parser.hpp new file mode 100644 index 000000000..32fecd2c0 --- /dev/null +++ b/host/lib/rfnoc/nocscript/parser.hpp @@ -0,0 +1,50 @@ +// +// Copyright 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/>. +// + +#include "expression.hpp" +#include "function_table.hpp" +#include <boost/shared_ptr.hpp> + +#ifndef INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_PARSER_HPP +#define INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_PARSER_HPP + +namespace uhd { namespace rfnoc { namespace nocscript { + +class parser +{ + public: + typedef boost::shared_ptr<parser> sptr; + + static sptr make( + function_table::sptr ftable, + expression_variable::type_getter_type var_type_getter, + expression_variable::value_getter_type var_value_getter + ); + + /*! The main parsing call: Turn a string of code into an expression tree. + * + * Evaluating the returned object will execute the code. + * + * \throws uhd::syntax_error if \p code contains syntax errors + */ + virtual expression::sptr create_expr_tree(const std::string &code) = 0; +}; + +}}} /* namespace uhd::rfnoc::nocscript */ + +#endif /* INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_PARSER_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/node_ctrl_base.cpp b/host/lib/rfnoc/node_ctrl_base.cpp new file mode 100644 index 000000000..6e19d276a --- /dev/null +++ b/host/lib/rfnoc/node_ctrl_base.cpp @@ -0,0 +1,103 @@ +// +// 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 <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/utils/msg.hpp> + +using namespace uhd::rfnoc; + +std::string node_ctrl_base::unique_id() const +{ + // Most instantiations will override this, so we don't need anything + // more elegant here. + return str(boost::format("%08X") % size_t(this)); +} + +void node_ctrl_base::clear() +{ + UHD_RFNOC_BLOCK_TRACE() << "node_ctrl_base::clear() " << std::endl; + // Reset connections: + _upstream_nodes.clear(); + _downstream_nodes.clear(); +} + +void node_ctrl_base::_register_downstream_node( + node_ctrl_base::sptr, + size_t +) { + throw uhd::runtime_error("Attempting to register a downstream block on a non-source node."); +} + +void node_ctrl_base::_register_upstream_node( + node_ctrl_base::sptr, + size_t +) { + throw uhd::runtime_error("Attempting to register an upstream block on a non-sink node."); +} + +void node_ctrl_base::set_downstream_port( + const size_t this_port, + const size_t remote_port +) { + if (not _downstream_nodes.count(this_port) and remote_port != ANY_PORT) { + throw uhd::value_error(str( + boost::format("[%s] Cannot set remote downstream port: Port %d not connected.") + % unique_id() % this_port + )); + } + _downstream_ports[this_port] = remote_port; +} + +size_t node_ctrl_base::get_downstream_port(const size_t this_port) +{ + if (not _downstream_ports.count(this_port) + or not _downstream_nodes.count(this_port) + or _downstream_ports[this_port] == ANY_PORT) { + throw uhd::value_error(str( + boost::format("[%s] Cannot retrieve remote downstream port: Port %d not connected.") + % unique_id() % this_port + )); + } + return _downstream_ports[this_port]; +} + +void node_ctrl_base::set_upstream_port( + const size_t this_port, + const size_t remote_port +) { + if (not _upstream_nodes.count(this_port) and remote_port != ANY_PORT) { + throw uhd::value_error(str( + boost::format("[%s] Cannot set remote upstream port: Port %d not connected.") + % unique_id() % this_port + )); + } + _upstream_ports[this_port] = remote_port; +} + +size_t node_ctrl_base::get_upstream_port(const size_t this_port) +{ + if (not _upstream_ports.count(this_port) + or not _upstream_nodes.count(this_port) + or _upstream_ports[this_port] == ANY_PORT) { + throw uhd::value_error(str( + boost::format("[%s] Cannot retrieve remote upstream port: Port %d not connected.") + % unique_id() % this_port + )); + } + return _upstream_ports[this_port]; +} + diff --git a/host/lib/rfnoc/radio_ctrl_impl.cpp b/host/lib/rfnoc/radio_ctrl_impl.cpp new file mode 100644 index 000000000..6ed09ecdd --- /dev/null +++ b/host/lib/rfnoc/radio_ctrl_impl.cpp @@ -0,0 +1,364 @@ +// +// 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/>. +// + +#include "wb_iface_adapter.hpp" +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <uhd/convert.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/direction.hpp> +#include "radio_ctrl_impl.hpp" +#include "../../transport/super_recv_packet_handler.hpp" + +using namespace uhd; +using namespace uhd::rfnoc; + +static const size_t BYTES_PER_SAMPLE = 4; + +/**************************************************************************** + * Structors and init + ***************************************************************************/ +// Note: block_ctrl_base must be called before this, but has to be called by +// the derived class because of virtual inheritance +radio_ctrl_impl::radio_ctrl_impl() : + _tick_rate(rfnoc::rate_node_ctrl::RATE_UNDEFINED) +{ + _num_rx_channels = get_output_ports().size(); + _num_tx_channels = get_input_ports().size(); + _continuous_streaming = std::vector<bool>(2, false); + + for (size_t i = 0; i < _num_rx_channels; i++) { + _rx_streamer_active[i] = false; + } + for (size_t i = 0; i < _num_tx_channels; i++) { + _tx_streamer_active[i] = false; + } + + ///////////////////////////////////////////////////////////////////////// + // Setup peripherals + ///////////////////////////////////////////////////////////////////////// + for (size_t i = 0; i < _get_num_radios(); i++) { + _register_loopback_self_test(i); + _perifs[i].ctrl = boost::make_shared<wb_iface_adapter>( + // poke32 functor + boost::bind( + static_cast< void (block_ctrl_base::*)(const boost::uint32_t, const boost::uint32_t, const size_t) >(&block_ctrl_base::sr_write), + this, _1, _2, i + ), + // peek32 functor + boost::bind( + static_cast< boost::uint32_t (block_ctrl_base::*)(const boost::uint32_t, const size_t) >(&block_ctrl_base::user_reg_read32), + this, + _1, i + ), + // peek64 functor + boost::bind( + static_cast< boost::uint64_t (block_ctrl_base::*)(const boost::uint32_t, const size_t) >(&block_ctrl_base::user_reg_read64), + this, + _1, i + ), + // get_time functor + boost::bind( + static_cast< time_spec_t (block_ctrl_base::*)(const size_t) >(&block_ctrl_base::get_command_time), + this, i + ), + // set_time functor + boost::bind( + static_cast< void (block_ctrl_base::*)(const time_spec_t&, const size_t) >(&block_ctrl_base::set_command_time), + this, + _1, i + ) + ); + + // FIXME there's currently no way to set the underflow policy + + if (i == 0) { + time_core_3000::readback_bases_type time64_rb_bases; + time64_rb_bases.rb_now = regs::RB_TIME_NOW; + time64_rb_bases.rb_pps = regs::RB_TIME_PPS; + _time64 = time_core_3000::make(_perifs[i].ctrl, regs::sr_addr(regs::TIME), time64_rb_bases); + this->set_time_now(0.0); + } + + //Reset the RX control engine + sr_write(regs::RX_CTRL_HALT, 1, i); + } + + //////////////////////////////////////////////////////////////////// + // Register the time keeper + //////////////////////////////////////////////////////////////////// + if (not _tree->exists(fs_path("time") / "now")) { + _tree->create<time_spec_t>(fs_path("time") / "now") + .set_publisher(boost::bind(&radio_ctrl_impl::get_time_now, this)) + ; + } + if (not _tree->exists(fs_path("time") / "pps")) { + _tree->create<time_spec_t>(fs_path("time") / "pps") + .set_publisher(boost::bind(&radio_ctrl_impl::get_time_last_pps, this)) + ; + } + if (not _tree->exists(fs_path("time") / "cmd")) { + _tree->create<time_spec_t>(fs_path("time") / "cmd"); + } + _tree->access<time_spec_t>(fs_path("time") / "now") + .add_coerced_subscriber(boost::bind(&radio_ctrl_impl::set_time_now, this, _1)) + ; + _tree->access<time_spec_t>(fs_path("time") / "pps") + .add_coerced_subscriber(boost::bind(&radio_ctrl_impl::set_time_next_pps, this, _1)) + ; + for (size_t i = 0; i < _get_num_radios(); i++) { + _tree->access<time_spec_t>("time/cmd") + .add_coerced_subscriber(boost::bind(&block_ctrl_base::set_command_tick_rate, this, boost::ref(_tick_rate), i)) + .add_coerced_subscriber(boost::bind(&block_ctrl_base::set_command_time, this, _1, i)) + ; + } + // spp gets created in the XML file + _tree->access<int>(get_arg_path("spp") / "value") + .add_coerced_subscriber(boost::bind(&radio_ctrl_impl::_update_spp, this, _1)) + .update() + ; +} + +void radio_ctrl_impl::_register_loopback_self_test(size_t chan) +{ + UHD_MSG(status) << "[RFNoC Radio] 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); + sr_write(regs::TEST, boost::uint32_t(hash), chan); + boost::uint32_t result = user_reg_read32(regs::RB_TEST, chan); + if (result != boost::uint32_t(hash)) { + UHD_MSG(status) << "fail" << std::endl; + UHD_MSG(status) << boost::format("expected: %x result: %x") % boost::uint32_t(hash) % result << std::endl; + return; // exit on any failure + } + } + UHD_MSG(status) << "pass" << std::endl; +} + +/**************************************************************************** + * API calls + ***************************************************************************/ +double radio_ctrl_impl::set_rate(double rate) +{ + boost::mutex::scoped_lock lock(_mutex); + _tick_rate = rate; + _time64->set_tick_rate(_tick_rate); + _time64->self_test(); + return _tick_rate; +} + +void radio_ctrl_impl::set_tx_antenna(const std::string &ant, const size_t chan) +{ + _tx_antenna[chan] = ant; +} + +void radio_ctrl_impl::set_rx_antenna(const std::string &ant, const size_t chan) +{ + _rx_antenna[chan] = ant; +} + +double radio_ctrl_impl::set_tx_frequency(const double freq, const size_t chan) +{ + return _tx_freq[chan] = freq; +} + +double radio_ctrl_impl::set_rx_frequency(const double freq, const size_t chan) +{ + return _rx_freq[chan] = freq; +} + +double radio_ctrl_impl::set_tx_gain(const double gain, const size_t chan) +{ + return _tx_gain[chan] = gain; +} + +double radio_ctrl_impl::set_rx_gain(const double gain, const size_t chan) +{ + return _rx_gain[chan] = gain; +} + +void radio_ctrl_impl::set_time_sync(const uhd::time_spec_t &time) +{ + _time64->set_time_sync(time); +} + +double radio_ctrl_impl::get_rate() const +{ + return _tick_rate; +} + +std::string radio_ctrl_impl::get_tx_antenna(const size_t chan) /* const */ +{ + return _tx_antenna[chan]; +} + +std::string radio_ctrl_impl::get_rx_antenna(const size_t chan) /* const */ +{ + return _rx_antenna[chan]; +} + +double radio_ctrl_impl::get_tx_frequency(const size_t chan) /* const */ +{ + return _tx_freq[chan]; +} + +double radio_ctrl_impl::get_rx_frequency(const size_t chan) /* const */ +{ + return _rx_freq[chan]; +} + +double radio_ctrl_impl::get_tx_gain(const size_t chan) /* const */ +{ + return _tx_gain[chan]; +} + +double radio_ctrl_impl::get_rx_gain(const size_t chan) /* const */ +{ + return _rx_gain[chan]; +} + +/*********************************************************************** + * RX Streamer-related methods (from source_block_ctrl_base) + **********************************************************************/ +//! Pass stream commands to the radio +void radio_ctrl_impl::issue_stream_cmd(const uhd::stream_cmd_t &stream_cmd, const size_t chan) +{ + boost::mutex::scoped_lock lock(_mutex); + UHD_RFNOC_BLOCK_TRACE() << "radio_ctrl_impl::issue_stream_cmd() " << chan << " " << char(stream_cmd.stream_mode) << std::endl; + UHD_ASSERT_THROW(stream_cmd.num_samps <= 0x0fffffff); + _continuous_streaming[chan] = (stream_cmd.stream_mode == stream_cmd_t::STREAM_MODE_START_CONTINUOUS); + + //setup the mode to instruction flags + typedef boost::tuple<bool, bool, bool, bool> inst_t; + static const uhd::dict<stream_cmd_t::stream_mode_t, inst_t> mode_to_inst = boost::assign::map_list_of + //reload, chain, samps, stop + (stream_cmd_t::STREAM_MODE_START_CONTINUOUS, inst_t(true, true, false, false)) + (stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS, inst_t(false, false, false, true)) + (stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE, inst_t(false, false, true, false)) + (stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE, inst_t(false, true, true, false)) + ; + + //setup the instruction flag values + bool inst_reload, inst_chain, inst_samps, inst_stop; + boost::tie(inst_reload, inst_chain, inst_samps, inst_stop) = mode_to_inst[stream_cmd.stream_mode]; + + //calculate the word from flags and length + boost::uint32_t cmd_word = 0; + cmd_word |= boost::uint32_t((stream_cmd.stream_now)? 1 : 0) << 31; + cmd_word |= boost::uint32_t((inst_chain)? 1 : 0) << 30; + cmd_word |= boost::uint32_t((inst_reload)? 1 : 0) << 29; + cmd_word |= boost::uint32_t((inst_stop)? 1 : 0) << 28; + cmd_word |= (inst_samps)? stream_cmd.num_samps : ((inst_stop)? 0 : 1); + + //issue the stream command + const boost::uint64_t ticks = (stream_cmd.stream_now)? 0 : stream_cmd.time_spec.to_ticks(get_rate()); + sr_write(regs::RX_CTRL_CMD, cmd_word, chan); + sr_write(regs::RX_CTRL_TIME_HI, boost::uint32_t(ticks >> 32), chan); + sr_write(regs::RX_CTRL_TIME_LO, boost::uint32_t(ticks >> 0), chan); //latches the command +} + +std::vector<size_t> radio_ctrl_impl::get_active_rx_ports() +{ + std::vector<size_t> active_rx_ports; + typedef std::map<size_t, bool> map_t; + BOOST_FOREACH(map_t::value_type &m, _rx_streamer_active) { + if (m.second) { + active_rx_ports.push_back(m.first); + } + } + return active_rx_ports; +} + +/*********************************************************************** + * Radio controls (radio_ctrl specific) + **********************************************************************/ +void radio_ctrl_impl::set_rx_streamer(bool active, const size_t port) +{ + UHD_RFNOC_BLOCK_TRACE() << "radio_ctrl_impl::set_rx_streamer() " << port << " -> " << active << std::endl; + if (port > _num_rx_channels) { + throw uhd::value_error(str( + boost::format("[%s] Can't (un)register RX streamer on port %d (invalid port)") + % unique_id() % port + )); + } + _rx_streamer_active[port] = active; + if (not check_radio_config()) { + throw std::runtime_error(str( + boost::format("[%s]: Invalid radio configuration.") + % unique_id() + )); + } +} + +void radio_ctrl_impl::set_tx_streamer(bool active, const size_t port) +{ + UHD_RFNOC_BLOCK_TRACE() << "radio_ctrl_impl::set_tx_streamer() " << port << " -> " << active << std::endl; + if (port > _num_tx_channels) { + throw uhd::value_error(str( + boost::format("[%s] Can't (un)register TX streamer on port %d (invalid port)") + % unique_id() % port + )); + } + _tx_streamer_active[port] = active; + if (not check_radio_config()) { + throw std::runtime_error(str( + boost::format("[%s]: Invalid radio configuration.") + % unique_id() + )); + } +} + +// Subscribers to block args: +// TODO move to nocscript +void radio_ctrl_impl::_update_spp(int spp) +{ + boost::mutex::scoped_lock lock(_mutex); + UHD_RFNOC_BLOCK_TRACE() << "radio_ctrl_impl::_update_spp(): Requested spp: " << spp << std::endl; + if (spp == 0) { + spp = DEFAULT_PACKET_SIZE / BYTES_PER_SAMPLE; + } + UHD_RFNOC_BLOCK_TRACE() << "radio_ctrl_impl::_update_spp(): Setting spp to: " << spp << std::endl; + for (size_t i = 0; i < _num_rx_channels; i++) { + sr_write(regs::RX_CTRL_MAXLEN, uint32_t(spp), i); + } +} + +void radio_ctrl_impl::set_time_now(const time_spec_t &time_spec) +{ + _time64->set_time_now(time_spec); +} + +void radio_ctrl_impl::set_time_next_pps(const time_spec_t &time_spec) +{ + _time64->set_time_next_pps(time_spec); +} + + +time_spec_t radio_ctrl_impl::get_time_now() +{ + return _time64->get_time_now(); +} + +time_spec_t radio_ctrl_impl::get_time_last_pps() +{ + return _time64->get_time_last_pps(); +} + diff --git a/host/lib/rfnoc/radio_ctrl_impl.hpp b/host/lib/rfnoc/radio_ctrl_impl.hpp new file mode 100644 index 000000000..4224ec5c4 --- /dev/null +++ b/host/lib/rfnoc/radio_ctrl_impl.hpp @@ -0,0 +1,209 @@ +// +// 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/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_RADIO_CTRL_IMPL_HPP +#define INCLUDED_LIBUHD_RFNOC_RADIO_CTRL_IMPL_HPP + +#include "rx_vita_core_3000.hpp" +#include "tx_vita_core_3000.hpp" +#include "time_core_3000.hpp" +#include "gpio_atr_3000.hpp" +#include <uhd/rfnoc/radio_ctrl.hpp> +#include <uhd/types/direction.hpp> +#include <boost/thread.hpp> + +//! Shorthand for radio block constructor +#define UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR_DECL(CLASS_NAME) \ + CLASS_NAME##_impl(const make_args_t &make_args); + +#define UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR(CLASS_NAME) \ + CLASS_NAME##_impl::CLASS_NAME##_impl( \ + const make_args_t &make_args \ + ) : block_ctrl_base(make_args), radio_ctrl_impl() + +namespace uhd { + namespace rfnoc { + +/*! \brief Provide access to a radio. + * + */ +class radio_ctrl_impl : public radio_ctrl +{ +public: + /************************************************************************ + * Structors + ***********************************************************************/ + radio_ctrl_impl(); + virtual ~radio_ctrl_impl() {}; + + /************************************************************************ + * Public Radio API calls + ***********************************************************************/ + virtual double set_rate(double rate); + virtual void set_tx_antenna(const std::string &ant, const size_t chan); + virtual void set_rx_antenna(const std::string &ant, const size_t chan); + virtual double set_tx_frequency(const double freq, const size_t chan); + virtual double set_rx_frequency(const double freq, const size_t chan); + virtual double set_tx_gain(const double gain, const size_t chan); + virtual double set_rx_gain(const double gain, const size_t chan); + virtual void set_time_sync(const uhd::time_spec_t &time); + + virtual double get_rate() const; + virtual std::string get_tx_antenna(const size_t chan) /* const */; + virtual std::string get_rx_antenna(const size_t chan) /* const */; + virtual double get_tx_frequency(const size_t) /* const */; + virtual double get_rx_frequency(const size_t) /* const */; + virtual double get_tx_gain(const size_t) /* const */; + virtual double get_rx_gain(const size_t) /* const */; + + void set_time_now(const time_spec_t &time_spec); + void set_time_next_pps(const time_spec_t &time_spec); + time_spec_t get_time_now(); + time_spec_t get_time_last_pps(); + + /*********************************************************************** + * Block control API calls + **********************************************************************/ + void set_rx_streamer(bool active, const size_t port); + void set_tx_streamer(bool active, const size_t port); + + void issue_stream_cmd(const uhd::stream_cmd_t &stream_cmd, const size_t port); + + virtual double get_input_samp_rate(size_t /* port */) { return get_rate(); } + virtual double get_output_samp_rate(size_t /* port */) { return get_rate(); } + double _get_tick_rate() { return get_rate(); } + + std::vector<size_t> get_active_rx_ports(); + bool in_continuous_streaming_mode(const size_t chan) { return _continuous_streaming.at(chan); } + void rx_ctrl_clear_cmds(const size_t port) { sr_write(regs::RX_CTRL_CLEAR_CMDS, 0, port); } + +protected: // TODO see what's protected and what's private + void _register_loopback_self_test(size_t chan); + + /*********************************************************************** + * Registers + **********************************************************************/ + struct regs { + static inline boost::uint32_t sr_addr(const boost::uint32_t offset) + { + return offset * 4; + } + + static const uint32_t BASE = 128; + + // defined in radio_core_regs.vh + static const uint32_t TIME = 128; // time hi - 128, time lo - 129, ctrl - 130 + static const uint32_t CLEAR_CMDS = 131; // Any write to this reg clears the command FIFO + static const uint32_t LOOPBACK = 132; + static const uint32_t TEST = 133; + static const uint32_t CODEC_IDLE = 134; + static const uint32_t TX_CTRL_ERROR_POLICY = 144; + static const uint32_t RX_CTRL_CMD = 152; + static const uint32_t RX_CTRL_TIME_HI = 153; + static const uint32_t RX_CTRL_TIME_LO = 154; + static const uint32_t RX_CTRL_HALT = 155; + static const uint32_t RX_CTRL_MAXLEN = 156; + static const uint32_t RX_CTRL_CLEAR_CMDS = 157; + static const uint32_t MISC_OUTS = 160; + static const uint32_t DACSYNC = 161; + static const uint32_t SPI = 168; + static const uint32_t LEDS = 176; + static const uint32_t FP_GPIO = 184; + static const uint32_t GPIO = 192; + // NOTE: Upper 32 registers (224-255) are reserved for the output settings bus for use with + // device specific front end control + + // frontend control: needs rethinking TODO + //static const uint32_t TX_FRONT = BASE + 96; + //static const uint32_t RX_FRONT = BASE + 112; + //static const uint32_t READBACK = BASE + 127; + + static const uint32_t RB_TIME_NOW = 0; + static const uint32_t RB_TIME_PPS = 1; + static const uint32_t RB_TEST = 2; + static const uint32_t RB_CODEC_READBACK = 3; + static const uint32_t RB_RADIO_NUM = 4; + static const uint32_t RB_MISC_IO = 16; + static const uint32_t RB_SPI = 17; + static const uint32_t RB_LEDS = 18; + static const uint32_t RB_DB_GPIO = 19; + static const uint32_t RB_FP_GPIO = 20; + }; + + /*********************************************************************** + * Block control API calls + **********************************************************************/ + void _update_spp(int spp); + + inline size_t _get_num_radios() const { + return std::max(_num_rx_channels, _num_tx_channels); + } + + inline timed_wb_iface::sptr _get_ctrl(size_t radio_num) const { + return _perifs.at(radio_num).ctrl; + } + + inline bool _is_streamer_active(uhd::direction_t dir, const size_t chan) const { + switch (dir) { + case uhd::TX_DIRECTION: + return _tx_streamer_active.at(chan); + case uhd::RX_DIRECTION: + return _rx_streamer_active.at(chan); + case uhd::DX_DIRECTION: + return _rx_streamer_active.at(chan) and _tx_streamer_active.at(chan); + default: + return false; + } + } + + virtual bool check_radio_config() { return true; }; + + //! There is always only one time core per radio + time_core_3000::sptr _time64; + + boost::mutex _mutex; + +private: + /************************************************************************ + * Peripherals + ***********************************************************************/ + //! Stores pointers to all streaming-related radio cores + struct radio_perifs_t + { + timed_wb_iface::sptr ctrl; + }; + std::map<size_t, radio_perifs_t> _perifs; + + size_t _num_tx_channels; + size_t _num_rx_channels; + + // Cached values + double _tick_rate; + std::map<size_t, std::string> _tx_antenna; + std::map<size_t, std::string> _rx_antenna; + std::map<size_t, double> _tx_freq; + std::map<size_t, double> _rx_freq; + std::map<size_t, double> _tx_gain; + std::map<size_t, double> _rx_gain; + + std::vector<bool> _continuous_streaming; +}; /* class radio_ctrl_impl */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_RADIO_CTRL_IMPL_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/rate_node_ctrl.cpp b/host/lib/rfnoc/rate_node_ctrl.cpp new file mode 100644 index 000000000..5e3117e23 --- /dev/null +++ b/host/lib/rfnoc/rate_node_ctrl.cpp @@ -0,0 +1,67 @@ +// +// 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/>. +// + +#include <uhd/rfnoc/rate_node_ctrl.hpp> +#include <boost/bind.hpp> + +using namespace uhd::rfnoc; + +const double rate_node_ctrl::RATE_UNDEFINED = -1.0; + +static double _get_input_samp_rate(rate_node_ctrl::sptr node, size_t port) +{ + return node->get_input_samp_rate(port); +} + +static double _get_output_samp_rate(rate_node_ctrl::sptr node, size_t port) +{ + return node->get_output_samp_rate(port); +} + + +// FIXME add recursion limiters (i.e. list of explored nodes) +double rate_node_ctrl::get_input_samp_rate( + size_t /* port */ +) { + try { + return find_downstream_unique_property<rate_node_ctrl, double>( + boost::bind(_get_input_samp_rate, _1, _2), + RATE_UNDEFINED + ); + } catch (const uhd::runtime_error &ex) { + throw uhd::runtime_error(str( + boost::format("Multiple sampling rates downstream of %s: %s.") + % unique_id() % ex.what() + )); + } +} + +double rate_node_ctrl::get_output_samp_rate( + size_t /* port */ +) { + try { + return find_upstream_unique_property<rate_node_ctrl, double>( + boost::bind(_get_output_samp_rate, _1, _2), + RATE_UNDEFINED + ); + } catch (const uhd::runtime_error &ex) { + throw uhd::runtime_error(str( + boost::format("Multiple sampling rates upstream of %s: %s.") + % unique_id() % ex.what() + )); + } +} diff --git a/host/lib/rfnoc/rx_stream_terminator.cpp b/host/lib/rfnoc/rx_stream_terminator.cpp new file mode 100644 index 000000000..b2a2d5a64 --- /dev/null +++ b/host/lib/rfnoc/rx_stream_terminator.cpp @@ -0,0 +1,131 @@ +// +// 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 "rx_stream_terminator.hpp" +#include "radio_ctrl_impl.hpp" +#include "../transport/super_recv_packet_handler.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/rfnoc/source_node_ctrl.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> + +using namespace uhd::rfnoc; + +size_t rx_stream_terminator::_count = 0; + +rx_stream_terminator::rx_stream_terminator() : + _term_index(_count), + _samp_rate(rate_node_ctrl::RATE_UNDEFINED), + _tick_rate(tick_node_ctrl::RATE_UNDEFINED) +{ + _count++; +} + +std::string rx_stream_terminator::unique_id() const +{ + return str(boost::format("RX Terminator %d") % _term_index); +} + +void rx_stream_terminator::set_tx_streamer(bool, const size_t) +{ + /* nop */ +} + +void rx_stream_terminator::set_rx_streamer(bool active, const size_t) +{ + // TODO this is identical to source_node_ctrl::set_rx_streamer() -> factor out + UHD_RFNOC_BLOCK_TRACE() << "rx_stream_terminator::set_rx_streamer() " << active << std::endl; + BOOST_FOREACH(const node_ctrl_base::node_map_pair_t upstream_node, _upstream_nodes) { + source_node_ctrl::sptr curr_upstream_block_ctrl = + boost::dynamic_pointer_cast<source_node_ctrl>(upstream_node.second.lock()); + if (curr_upstream_block_ctrl) { + curr_upstream_block_ctrl->set_rx_streamer( + active, + get_upstream_port(upstream_node.first) + ); + } + } +} + +void rx_stream_terminator::handle_overrun(boost::weak_ptr<uhd::rx_streamer> streamer, const size_t) +{ + std::vector<boost::shared_ptr<uhd::rfnoc::radio_ctrl_impl> > upstream_radio_nodes = + find_upstream_node<uhd::rfnoc::radio_ctrl_impl>(); + const size_t n_radios = upstream_radio_nodes.size(); + if (n_radios == 0) { + return; + } + + UHD_RFNOC_BLOCK_TRACE() << "rx_stream_terminator::handle_overrun()" << std::endl; + boost::shared_ptr<uhd::transport::sph::recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<uhd::transport::sph::recv_packet_streamer>(streamer.lock()); + if (not my_streamer) return; //If the rx_streamer has expired then overflow handling makes no sense. + + bool in_continuous_streaming_mode = true; + int num_channels = 0; + BOOST_FOREACH(const boost::shared_ptr<uhd::rfnoc::radio_ctrl_impl> &node, upstream_radio_nodes) { + num_channels += node->get_active_rx_ports().size(); + BOOST_FOREACH(const size_t port, node->get_active_rx_ports()) { + in_continuous_streaming_mode = in_continuous_streaming_mode && node->in_continuous_streaming_mode(port); + } + } + if (num_channels == 0) { + return; + } + + if (num_channels == 1 and in_continuous_streaming_mode) { + std::vector<size_t> active_rx_ports = upstream_radio_nodes[0]->get_active_rx_ports(); + if (active_rx_ports.empty()) { + return; + } + const size_t port = active_rx_ports[0]; + upstream_radio_nodes[0]->issue_stream_cmd(stream_cmd_t::STREAM_MODE_START_CONTINUOUS, port); + return; + } + + ///////////////////////////////////////////////////////////// + // MIMO overflow recovery time + ///////////////////////////////////////////////////////////// + BOOST_FOREACH(const boost::shared_ptr<uhd::rfnoc::radio_ctrl_impl> &node, upstream_radio_nodes) { + BOOST_FOREACH(const size_t port, node->get_active_rx_ports()) { + // check all the ports on all the radios + node->rx_ctrl_clear_cmds(port); + node->issue_stream_cmd(stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS, port); + } + } + //flush transports + my_streamer->flush_all(0.001); // TODO flushing will probably have to go away. + //restart streaming on all channels + 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 = upstream_radio_nodes[0]->get_time_now() + time_spec_t(0.05); + + BOOST_FOREACH(const boost::shared_ptr<uhd::rfnoc::radio_ctrl_impl> &node, upstream_radio_nodes) { + BOOST_FOREACH(const size_t port, node->get_active_rx_ports()) { + node->issue_stream_cmd(stream_cmd, port); + } + } + } +} + +rx_stream_terminator::~rx_stream_terminator() +{ + UHD_RFNOC_BLOCK_TRACE() << "rx_stream_terminator::~rx_stream_terminator() " << std::endl; + set_rx_streamer(false, 0); +} + diff --git a/host/lib/rfnoc/rx_stream_terminator.hpp b/host/lib/rfnoc/rx_stream_terminator.hpp new file mode 100644 index 000000000..5159cd34a --- /dev/null +++ b/host/lib/rfnoc/rx_stream_terminator.hpp @@ -0,0 +1,86 @@ +// +// 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/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_TERMINATOR_RECV_HPP +#define INCLUDED_LIBUHD_RFNOC_TERMINATOR_RECV_HPP + +#include <uhd/rfnoc/sink_node_ctrl.hpp> +#include <uhd/rfnoc/rate_node_ctrl.hpp> +#include <uhd/rfnoc/tick_node_ctrl.hpp> +#include <uhd/rfnoc/scalar_node_ctrl.hpp> +#include <uhd/rfnoc/terminator_node_ctrl.hpp> +#include <uhd/rfnoc/block_ctrl_base.hpp> // For the block macros + +namespace uhd { + namespace rfnoc { + +/*! \brief Terminator node for Rx streamers. + * + * This node is only used by rx_streamers. It terminates the flow graph + * inside the streamer and does not have a counterpart on the FPGA. + */ +class rx_stream_terminator : + public sink_node_ctrl, + public rate_node_ctrl, + public tick_node_ctrl, + public scalar_node_ctrl, + public terminator_node_ctrl +{ +public: + UHD_RFNOC_BLOCK_OBJECT(rx_stream_terminator) + + static sptr make() + { + return sptr(new rx_stream_terminator); + } + + // If this is called, then by a send terminator at the other end + // of a flow graph. + double get_input_samp_rate(size_t) { return _samp_rate; }; + + // Same for the scaling factor + double get_input_scale_factor(size_t) { return scalar_node_ctrl::SCALE_UNDEFINED; }; + + std::string unique_id() const; + + void set_rx_streamer(bool active, const size_t port); + + void set_tx_streamer(bool active, const size_t port); + + virtual ~rx_stream_terminator(); + + void handle_overrun(boost::weak_ptr<uhd::rx_streamer>, const size_t); + +protected: + rx_stream_terminator(); + + virtual double _get_tick_rate() { return _tick_rate; }; + +private: + //! Every terminator has a unique index + const size_t _term_index; + static size_t _count; + + double _samp_rate; + double _tick_rate; + +}; /* class rx_stream_terminator */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_TERMINATOR_RECV_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/scalar_node_ctrl.cpp b/host/lib/rfnoc/scalar_node_ctrl.cpp new file mode 100644 index 000000000..56cc4dcf2 --- /dev/null +++ b/host/lib/rfnoc/scalar_node_ctrl.cpp @@ -0,0 +1,66 @@ +// +// 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/>. +// + +#include <uhd/rfnoc/scalar_node_ctrl.hpp> +#include <boost/bind.hpp> + +using namespace uhd::rfnoc; + +const double scalar_node_ctrl::SCALE_UNDEFINED = -1.0; + +static double _get_input_factor(scalar_node_ctrl::sptr node, size_t port) +{ + return node->get_input_scale_factor(port); +} + +static double _get_output_factor(scalar_node_ctrl::sptr node, size_t port) +{ + return node->get_output_scale_factor(port); +} + +// FIXME add recursion limiters (i.e. list of explored nodes) +double scalar_node_ctrl::get_input_scale_factor( + size_t /* port */ +) { + try { + return find_downstream_unique_property<scalar_node_ctrl, double>( + boost::bind(_get_input_factor, _1, _2), + SCALE_UNDEFINED + ); + } catch (const uhd::runtime_error &ex) { + throw uhd::runtime_error(str( + boost::format("Multiple scaling factors rates downstream of %s: %s.") + % unique_id() % ex.what() + )); + } +} + +double scalar_node_ctrl::get_output_scale_factor( + size_t /* port */ +) { + try { + return find_upstream_unique_property<scalar_node_ctrl, double>( + boost::bind(_get_output_factor, _1, _2), + SCALE_UNDEFINED + ); + } catch (const uhd::runtime_error &ex) { + throw uhd::runtime_error(str( + boost::format("Multiple scaling factors rates upstream of %s: %s.") + % unique_id() % ex.what() + )); + } +} diff --git a/host/lib/rfnoc/sink_block_ctrl_base.cpp b/host/lib/rfnoc/sink_block_ctrl_base.cpp new file mode 100644 index 000000000..56755a269 --- /dev/null +++ b/host/lib/rfnoc/sink_block_ctrl_base.cpp @@ -0,0 +1,111 @@ +// +// 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 "utils.hpp" +#include <uhd/rfnoc/sink_block_ctrl_base.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <uhd/utils/msg.hpp> + +using namespace uhd; +using namespace uhd::rfnoc; + + +/*********************************************************************** + * Stream signatures + **********************************************************************/ +stream_sig_t sink_block_ctrl_base::get_input_signature(size_t block_port) const +{ + if (not _tree->exists(_root_path / "ports" / "in" / block_port)) { + throw uhd::runtime_error(str( + boost::format("Invalid port number %d for block %s") + % block_port % unique_id() + )); + } + + return _resolve_port_def( + _tree->access<blockdef::port_t>(_root_path / "ports" / "in" / block_port).get() + ); +} + +std::vector<size_t> sink_block_ctrl_base::get_input_ports() const +{ + std::vector<size_t> input_ports; + input_ports.reserve(_tree->list(_root_path / "ports" / "in").size()); + BOOST_FOREACH(const std::string port, _tree->list(_root_path / "ports" / "in")) { + input_ports.push_back(boost::lexical_cast<size_t>(port)); + } + return input_ports; +} + +/*********************************************************************** + * FPGA Configuration + **********************************************************************/ +size_t sink_block_ctrl_base::get_fifo_size(size_t block_port) const { + if (_tree->exists(_root_path / "input_buffer_size" / str(boost::format("%d") % block_port))) { + return _tree->access<size_t>(_root_path / "input_buffer_size" / str(boost::format("%d") % block_port)).get(); + } + return 0; +} + +void sink_block_ctrl_base::configure_flow_control_in( + size_t cycles, + size_t packets, + size_t block_port +) { + UHD_RFNOC_BLOCK_TRACE() << boost::format("sink_block_ctrl_base::configure_flow_control_in(cycles=%d, packets=%d)") % cycles % packets << std::endl; + boost::uint32_t cycles_word = 0; + if (cycles) { + cycles_word = (1<<31) | cycles; + } + sr_write(SR_FLOW_CTRL_CYCS_PER_ACK, cycles_word, block_port); + + boost::uint32_t packets_word = 0; + if (packets) { + packets_word = (1<<31) | packets; + } + sr_write(SR_FLOW_CTRL_PKTS_PER_ACK, packets_word, block_port); +} + +void sink_block_ctrl_base::set_error_policy( + const std::string &policy +) { + if (policy == "next_packet") + { + sr_write(SR_ERROR_POLICY, (1 << 1) | 1); + } + else if (policy == "next_burst") + { + sr_write(SR_ERROR_POLICY, (1 << 2) | 1); + } + else if (policy == "wait") + { + sr_write(SR_ERROR_POLICY, 1); + } + else throw uhd::value_error("Block input cannot handle requested error policy: " + policy); +} + +/*********************************************************************** + * Hooks + **********************************************************************/ +size_t sink_block_ctrl_base::_request_input_port( + const size_t suggested_port, + const uhd::device_addr_t & +) const { + const std::set<size_t> valid_input_ports = utils::str_list_to_set<size_t>(_tree->list(_root_path / "ports" / "in")); + return utils::node_map_find_first_free(_upstream_nodes, suggested_port, valid_input_ports); +} +// vim: sw=4 et: diff --git a/host/lib/rfnoc/sink_node_ctrl.cpp b/host/lib/rfnoc/sink_node_ctrl.cpp new file mode 100644 index 000000000..8398641fd --- /dev/null +++ b/host/lib/rfnoc/sink_node_ctrl.cpp @@ -0,0 +1,92 @@ +// +// 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 "utils.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/rfnoc/sink_node_ctrl.hpp> +#include <uhd/rfnoc/source_node_ctrl.hpp> + +using namespace uhd::rfnoc; + +size_t sink_node_ctrl::connect_upstream( + node_ctrl_base::sptr upstream_node, + size_t port, + const uhd::device_addr_t &args +) { + boost::mutex::scoped_lock lock(_input_mutex); + port = _request_input_port(port, args); + _register_upstream_node(upstream_node, port); + return port; +} + +void sink_node_ctrl::set_tx_streamer(bool active, const size_t port) +{ + UHD_RFNOC_BLOCK_TRACE() << "sink_node_ctrl::set_tx_streamer() " << active << " " << port << std::endl; + + /* Enable all downstream connections: + BOOST_FOREACH(const node_ctrl_base::node_map_pair_t downstream_node, list_downstream_nodes()) { + sptr curr_downstream_block_ctrl = + boost::dynamic_pointer_cast<sink_node_ctrl>(downstream_node.second.lock()); + if (curr_downstream_block_ctrl) { + curr_downstream_block_ctrl->set_tx_streamer( + active, + get_downstream_port(downstream_node.first) + ); + } + } + */ + + // Only enable 1:1 + if (list_downstream_nodes().count(port)) { + sink_node_ctrl::sptr this_downstream_block_ctrl = + boost::dynamic_pointer_cast<sink_node_ctrl>(list_downstream_nodes().at(port).lock()); + if (this_downstream_block_ctrl) { + this_downstream_block_ctrl->set_tx_streamer( + active, + get_downstream_port(port) + ); + } + } + + _tx_streamer_active[port] = active; +} + +size_t sink_node_ctrl::_request_input_port( + const size_t suggested_port, + const uhd::device_addr_t & +) const { + return utils::node_map_find_first_free(_upstream_nodes, suggested_port); +} + +void sink_node_ctrl::_register_upstream_node( + node_ctrl_base::sptr upstream_node, + size_t port +) { + // Do all the checks: + if (port == ANY_PORT) { + throw uhd::type_error("Invalid input port number."); + } + if (_upstream_nodes.count(port) and not _upstream_nodes[port].expired()) { + throw uhd::runtime_error(str(boost::format("On node %s, input port %d is already connected.") % unique_id() % port)); + } + if (not boost::dynamic_pointer_cast<source_node_ctrl>(upstream_node)) { + throw uhd::type_error("Attempting to register a non-source block as upstream."); + } + // Alles klar, Herr Kommissar :) + + _upstream_nodes[port] = boost::weak_ptr<node_ctrl_base>(upstream_node); +} diff --git a/host/lib/rfnoc/source_block_ctrl_base.cpp b/host/lib/rfnoc/source_block_ctrl_base.cpp new file mode 100644 index 000000000..72845ad64 --- /dev/null +++ b/host/lib/rfnoc/source_block_ctrl_base.cpp @@ -0,0 +1,137 @@ +// +// 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 "utils.hpp" +#include <uhd/rfnoc/source_block_ctrl_base.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/rfnoc/constants.hpp> + +using namespace uhd; +using namespace uhd::rfnoc; + +/*********************************************************************** + * Streaming operations + **********************************************************************/ +void source_block_ctrl_base::issue_stream_cmd( + const uhd::stream_cmd_t &stream_cmd, + const size_t chan +) { + UHD_RFNOC_BLOCK_TRACE() << "source_block_ctrl_base::issue_stream_cmd()" << std::endl; + if (_upstream_nodes.empty()) { + UHD_MSG(warning) << "issue_stream_cmd() not implemented for " << get_block_id() << std::endl; + return; + } + + BOOST_FOREACH(const node_ctrl_base::node_map_pair_t upstream_node, _upstream_nodes) { + source_node_ctrl::sptr this_upstream_block_ctrl = + boost::dynamic_pointer_cast<source_node_ctrl>(upstream_node.second.lock()); + this_upstream_block_ctrl->issue_stream_cmd(stream_cmd, chan); + } +} + +/*********************************************************************** + * Stream signatures + **********************************************************************/ +stream_sig_t source_block_ctrl_base::get_output_signature(size_t block_port) const +{ + if (not _tree->exists(_root_path / "ports" / "out" / block_port)) { + throw uhd::runtime_error(str( + boost::format("Invalid port number %d for block %s") + % block_port % unique_id() + )); + } + + return _resolve_port_def( + _tree->access<blockdef::port_t>(_root_path / "ports" / "out" / block_port).get() + ); +} + +std::vector<size_t> source_block_ctrl_base::get_output_ports() const +{ + std::vector<size_t> output_ports; + output_ports.reserve(_tree->list(_root_path / "ports" / "out").size()); + BOOST_FOREACH(const std::string port, _tree->list(_root_path / "ports" / "out")) { + output_ports.push_back(boost::lexical_cast<size_t>(port)); + } + return output_ports; +} + +/*********************************************************************** + * FPGA Configuration + **********************************************************************/ +void source_block_ctrl_base::set_destination( + boost::uint32_t next_address, + size_t output_block_port +) { + UHD_RFNOC_BLOCK_TRACE() << "source_block_ctrl_base::set_destination() " << uhd::sid_t(next_address) << std::endl; + sid_t new_sid(next_address); + new_sid.set_src(get_address(output_block_port)); + UHD_RFNOC_BLOCK_TRACE() << " Setting SID: " << new_sid << std::endl << " "; + sr_write(SR_NEXT_DST_SID, (1<<16) | next_address, output_block_port); +} + +void source_block_ctrl_base::configure_flow_control_out( + size_t buf_size_pkts, + size_t block_port, + UHD_UNUSED(const uhd::sid_t &sid) +) { + UHD_RFNOC_BLOCK_TRACE() << "source_block_ctrl_base::configure_flow_control_out() buf_size_pkts==" << buf_size_pkts << std::endl; + if (buf_size_pkts < 2) { + throw uhd::runtime_error(str( + boost::format("Invalid window size %d for block %s. Window size must at least be 2.") + % buf_size_pkts % unique_id() + )); + } + + //Disable the window and let all upstream data flush out + //We need to do this every time the window is changed because + //a) We don't know what state the flow-control module was left in + // in the previous run (it should still be enabled) + //b) Changing the window size where data is buffered upstream may + // result in stale packets entering the stream. + sr_write(SR_FLOW_CTRL_WINDOW_EN, 0, block_port); + + //Wait for data to flush out. + //In the FPGA we are guaranteed that all buffered packets are more-or-less consecutive. + //1ms@200MHz = 200,000 cycles of "flush time". + //200k cycles = 200k * 8 bytes (64 bits) = 1.6MB of data that can be flushed. + //Typically in the FPGA we have buffering in the order of kilobytes so waiting for 1MB + //to flush is more than enough time. + //TODO: Enhancement. We should get feedback from the FPGA about when the source_flow_control + // module is done flushing. + boost::this_thread::sleep(boost::posix_time::milliseconds(1)); + + //Resize the FC window. + //Precondition: No data can be buffered upstream. + sr_write(SR_FLOW_CTRL_WINDOW_SIZE, buf_size_pkts, block_port); + + //Enable the FC window. + //Precondition: The window size must be set. + sr_write(SR_FLOW_CTRL_WINDOW_EN, (buf_size_pkts != 0), block_port); +} + +/*********************************************************************** + * Hooks + **********************************************************************/ +size_t source_block_ctrl_base::_request_output_port( + const size_t suggested_port, + const uhd::device_addr_t & +) const { + const std::set<size_t> valid_output_ports = utils::str_list_to_set<size_t>(_tree->list(_root_path / "ports" / "out")); + return utils::node_map_find_first_free(_downstream_nodes, suggested_port, valid_output_ports); +} +// vim: sw=4 et: diff --git a/host/lib/rfnoc/source_node_ctrl.cpp b/host/lib/rfnoc/source_node_ctrl.cpp new file mode 100644 index 000000000..c97c72354 --- /dev/null +++ b/host/lib/rfnoc/source_node_ctrl.cpp @@ -0,0 +1,96 @@ +// +// 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 "utils.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/rfnoc/source_node_ctrl.hpp> +#include <uhd/rfnoc/sink_node_ctrl.hpp> + +using namespace uhd::rfnoc; + +size_t source_node_ctrl::connect_downstream( + node_ctrl_base::sptr downstream_node, + size_t port, + const uhd::device_addr_t &args +) { + boost::mutex::scoped_lock lock(_output_mutex); + port = _request_output_port(port, args); + _register_downstream_node(downstream_node, port); + return port; +} + +void source_node_ctrl::set_rx_streamer(bool active, const size_t port) +{ + UHD_RFNOC_BLOCK_TRACE() << "source_node_ctrl::set_rx_streamer() " << port << " -> " << active << std::endl; + + /* This will enable all upstream blocks: + BOOST_FOREACH(const node_ctrl_base::node_map_pair_t upstream_node, list_upstream_nodes()) { + sptr curr_upstream_block_ctrl = + boost::dynamic_pointer_cast<source_node_ctrl>(upstream_node.second.lock()); + if (curr_upstream_block_ctrl) { + curr_upstream_block_ctrl->set_rx_streamer( + active, + get_upstream_port(upstream_node.first) + ); + } + } + */ + + // This only enables 1:1 (if output 1 is enabled, enable what's connected to input 1) + if (list_upstream_nodes().count(port)) { + source_node_ctrl::sptr this_upstream_block_ctrl = + boost::dynamic_pointer_cast<source_node_ctrl>(list_upstream_nodes().at(port).lock()); + if (this_upstream_block_ctrl) { + this_upstream_block_ctrl->set_rx_streamer( + active, + get_upstream_port(port) + ); + } + } + + _rx_streamer_active[port] = active; +} + +size_t source_node_ctrl::_request_output_port( + const size_t suggested_port, + const uhd::device_addr_t & +) const { + return utils::node_map_find_first_free(_downstream_nodes, suggested_port); +} + +void source_node_ctrl::_register_downstream_node( + node_ctrl_base::sptr downstream_node, + size_t port +) { + // Do all the checks: + if (port == ANY_PORT) { + throw uhd::type_error(str( + boost::format("[%s] Invalid output port number (ANY).") + % unique_id() + )); + } + if (_downstream_nodes.count(port) and not _downstream_nodes[port].expired()) { + throw uhd::runtime_error(str(boost::format("On node %s, output port %d is already connected.") % unique_id() % port)); + } + if (not boost::dynamic_pointer_cast<sink_node_ctrl>(downstream_node)) { + throw uhd::type_error("Attempting to register a non-sink block as downstream."); + } + // Alles klar, Herr Kommissar :) + + _downstream_nodes[port] = boost::weak_ptr<node_ctrl_base>(downstream_node); +} + diff --git a/host/lib/rfnoc/stream_sig.cpp b/host/lib/rfnoc/stream_sig.cpp new file mode 100644 index 000000000..3d953bcb2 --- /dev/null +++ b/host/lib/rfnoc/stream_sig.cpp @@ -0,0 +1,85 @@ +// +// 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 <uhd/rfnoc/stream_sig.hpp> +#include <uhd/convert.hpp> +#include <boost/format.hpp> + +using namespace uhd::rfnoc; + +stream_sig_t::stream_sig_t() : + item_type(""), + vlen(0), + packet_size(0), + is_bursty(false) +{ + // nop +} + +std::string stream_sig_t::to_string() +{ + return str( + boost::format( + "%s,vlen=%d,packet_size=%d" + ) % item_type % vlen % packet_size + ); +} + +std::string stream_sig_t::to_pp_string() +{ + return str( + boost::format( + "Data type: %s | Vector Length: %d | Packet size: %d" + ) % item_type % vlen % packet_size + ); +} + +size_t stream_sig_t::get_bytes_per_item() const +{ + if (item_type == "") { + return 0; + } + + return uhd::convert::get_bytes_per_item(item_type); +} + +bool stream_sig_t::is_compatible(const stream_sig_t &output_sig, const stream_sig_t &input_sig) +{ + /// Item types: + if (not (input_sig.item_type.empty() or output_sig.item_type.empty()) + and input_sig.item_type != output_sig.item_type) { + return false; + } + + /// Vector lengths + if (output_sig.vlen and input_sig.vlen) { + if (input_sig.vlen != output_sig.vlen) { + return false; + } + } + + /// Packet sizes + if (output_sig.packet_size and input_sig.packet_size) { + if (input_sig.packet_size != output_sig.packet_size) { + return false; + } + } + + // You may pass + return true; +} +// vim: sw=4 et: diff --git a/host/lib/rfnoc/tick_node_ctrl.cpp b/host/lib/rfnoc/tick_node_ctrl.cpp new file mode 100644 index 000000000..fa5c7b6a1 --- /dev/null +++ b/host/lib/rfnoc/tick_node_ctrl.cpp @@ -0,0 +1,75 @@ +// +// 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/>. +// + +#include <uhd/rfnoc/tick_node_ctrl.hpp> + +using namespace uhd::rfnoc; + +const double tick_node_ctrl::RATE_UNDEFINED = 0; + +double tick_node_ctrl::get_tick_rate( + const std::set< node_ctrl_base::sptr > &_explored_nodes +) { + // First, see if we've implemented _get_tick_rate() + { + double my_tick_rate = _get_tick_rate(); + if (my_tick_rate != RATE_UNDEFINED) { + return my_tick_rate; + } + } + + // If not, we ask all our neighbours for the tick rate. + // This will fail if we get different values. + std::set< node_ctrl_base::sptr > explored_nodes(_explored_nodes); + explored_nodes.insert(shared_from_this()); + // Here, we need all up- and downstream nodes + std::vector< sptr > neighbouring_tick_nodes = find_downstream_node<tick_node_ctrl>(); + { + std::vector< sptr > upstream_neighbouring_tick_nodes = find_upstream_node<tick_node_ctrl>(); + neighbouring_tick_nodes.insert( + neighbouring_tick_nodes.end(), + upstream_neighbouring_tick_nodes.begin(), + upstream_neighbouring_tick_nodes.end() + ); + } // neighbouring_tick_nodes is now initialized + double ret_val = RATE_UNDEFINED; + BOOST_FOREACH(const sptr &node, neighbouring_tick_nodes) { + if (_explored_nodes.count(node)) { + continue; + } + double tick_rate = node->get_tick_rate(explored_nodes); + if (tick_rate == RATE_UNDEFINED) { + continue; + } + if (ret_val == RATE_UNDEFINED) { + ret_val = tick_rate; + // TODO: Remember name of this node so we can make the throw message more descriptive. + continue; + } + if (tick_rate != ret_val) { + throw uhd::runtime_error( + str( + // TODO add node names + boost::format("Conflicting tick rates: One neighbouring block specifies %d MHz, another %d MHz.") + % tick_rate % ret_val + ) + ); + } + } + return ret_val; +} + diff --git a/host/lib/rfnoc/tx_stream_terminator.cpp b/host/lib/rfnoc/tx_stream_terminator.cpp new file mode 100644 index 000000000..2746fc4d8 --- /dev/null +++ b/host/lib/rfnoc/tx_stream_terminator.cpp @@ -0,0 +1,65 @@ +// +// 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/>. +// + +#include "tx_stream_terminator.hpp" +#include <boost/format.hpp> +#include <uhd/rfnoc/sink_node_ctrl.hpp> + +using namespace uhd::rfnoc; + +size_t tx_stream_terminator::_count = 0; + +tx_stream_terminator::tx_stream_terminator() : + _term_index(_count), + _samp_rate(rate_node_ctrl::RATE_UNDEFINED), + _tick_rate(tick_node_ctrl::RATE_UNDEFINED) +{ + _count++; +} + +std::string tx_stream_terminator::unique_id() const +{ + return str(boost::format("TX Terminator %d") % _term_index); +} + +void tx_stream_terminator::set_rx_streamer(bool, const size_t) +{ + /* nop */ +} + +void tx_stream_terminator::set_tx_streamer(bool active, const size_t /* port */) +{ + // TODO this is identical to sink_node_ctrl::set_tx_streamer() -> factor out + UHD_RFNOC_BLOCK_TRACE() << "tx_stream_terminator::set_tx_streamer() " << active << std::endl; + BOOST_FOREACH(const node_ctrl_base::node_map_pair_t downstream_node, _downstream_nodes) { + sink_node_ctrl::sptr curr_downstream_block_ctrl = + boost::dynamic_pointer_cast<sink_node_ctrl>(downstream_node.second.lock()); + if (curr_downstream_block_ctrl) { + curr_downstream_block_ctrl->set_tx_streamer( + active, + get_downstream_port(downstream_node.first) + ); + } + } +} + +tx_stream_terminator::~tx_stream_terminator() +{ + UHD_RFNOC_BLOCK_TRACE() << "tx_stream_terminator::~tx_stream_terminator() " << std::endl; + set_tx_streamer(false, 0); +} + diff --git a/host/lib/rfnoc/tx_stream_terminator.hpp b/host/lib/rfnoc/tx_stream_terminator.hpp new file mode 100644 index 000000000..169d7cd6a --- /dev/null +++ b/host/lib/rfnoc/tx_stream_terminator.hpp @@ -0,0 +1,90 @@ +// +// 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/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_TERMINATOR_SEND_HPP +#define INCLUDED_LIBUHD_RFNOC_TERMINATOR_SEND_HPP + +#include <uhd/rfnoc/source_node_ctrl.hpp> +#include <uhd/rfnoc/rate_node_ctrl.hpp> +#include <uhd/rfnoc/tick_node_ctrl.hpp> +#include <uhd/rfnoc/scalar_node_ctrl.hpp> +#include <uhd/rfnoc/terminator_node_ctrl.hpp> +#include <uhd/rfnoc/block_ctrl_base.hpp> // For the block macros +#include <uhd/utils/msg.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Terminator node for Tx streamers. + * + * This node is only used by tx_streamers. It terminates the flow graph + * inside the streamer and does not have a counterpart on the FPGA. + */ +class tx_stream_terminator : + public source_node_ctrl, + public rate_node_ctrl, + public tick_node_ctrl, + public scalar_node_ctrl, + public terminator_node_ctrl +{ +public: + UHD_RFNOC_BLOCK_OBJECT(tx_stream_terminator) + + static sptr make() + { + return sptr(new tx_stream_terminator); + } + + void issue_stream_cmd(const uhd::stream_cmd_t &, const size_t) + { + UHD_RFNOC_BLOCK_TRACE() << "tx_stream_terminator::issue_stream_cmd()" << std::endl; + } + + // If this is called, then by a send terminator at the other end + // of a flow graph. + double get_output_samp_rate(size_t) { return _samp_rate; }; + + // Same for the scaling factor + double get_output_scale_factor(size_t) { return scalar_node_ctrl::SCALE_UNDEFINED; }; + + std::string unique_id() const; + + void set_rx_streamer(bool active, const size_t port); + + void set_tx_streamer(bool active, const size_t port); + + virtual ~tx_stream_terminator(); + +protected: + tx_stream_terminator(); + + virtual double _get_tick_rate() { return _tick_rate; }; + +private: + //! Every terminator has a unique index + const size_t _term_index; + static size_t _count; + + double _samp_rate; + double _tick_rate; + +}; /* class tx_stream_terminator */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_TERMINATOR_SEND_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/utils.hpp b/host/lib/rfnoc/utils.hpp new file mode 100644 index 000000000..ecd3d7cfb --- /dev/null +++ b/host/lib/rfnoc/utils.hpp @@ -0,0 +1,77 @@ +// +// 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/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_UTILS_HPP +#define INCLUDED_LIBUHD_RFNOC_UTILS_HPP + +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <boost/lexical_cast.hpp> +#include <set> + +namespace uhd { namespace rfnoc { namespace utils { + + /*! If \p suggested_port equals ANY_PORT, return the first available + * port number on \p nodes. Otherwise, return \p suggested_port. + * + * If \p allowed_ports is given, another condition is that the port + * number must be listed in here. + * If \p allowed_ports is not specified or empty, the assumption is + * that all ports are valid. + * + * On failure, ANY_PORT is returned. + */ + static size_t node_map_find_first_free( + node_ctrl_base::node_map_t nodes, + const size_t suggested_port, + const std::set<size_t> allowed_ports=std::set<size_t>() + ) { + size_t port = suggested_port; + if (port == ANY_PORT) { + if (allowed_ports.empty()) { + port = 0; + while (nodes.count(port) and (port != ANY_PORT)) { + port++; + } + } else { + BOOST_FOREACH(const size_t allowed_port, allowed_ports) { + if (not nodes.count(port)) { + return allowed_port; + } + return ANY_PORT; + } + } + } else { + if (not (allowed_ports.empty() or allowed_ports.count(port))) { + return ANY_PORT; + } + } + return port; + } + + template <typename T> + static std::set<T> str_list_to_set(const std::vector<std::string> &list) { + std::set<T> return_set; + BOOST_FOREACH(const std::string &S, list) { + return_set.insert(boost::lexical_cast<T>(S)); + } + return return_set; + } + +}}}; /* namespace uhd::rfnoc::utils */ + +#endif /* INCLUDED_LIBUHD_RFNOC_UTILS_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/wb_iface_adapter.cpp b/host/lib/rfnoc/wb_iface_adapter.cpp new file mode 100644 index 000000000..6688fe86b --- /dev/null +++ b/host/lib/rfnoc/wb_iface_adapter.cpp @@ -0,0 +1,71 @@ +// +// Copyright 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 "wb_iface_adapter.hpp" + +using namespace uhd::rfnoc; + +wb_iface_adapter::wb_iface_adapter( + const poke32_type &poke32_functor_, + const peek32_type &peek32_functor_, + const peek64_type &peek64_functor_, + const gettime_type &gettime_functor_, + const settime_type &settime_functor_ +) : poke32_functor(poke32_functor_) + , peek32_functor(peek32_functor_) + , peek64_functor(peek64_functor_) + , gettime_functor(gettime_functor_) + , settime_functor(settime_functor_) +{ + // nop +} + +wb_iface_adapter::wb_iface_adapter( + const poke32_type &poke32_functor_, + const peek32_type &peek32_functor_, + const peek64_type &peek64_functor_ +) : poke32_functor(poke32_functor_) + , peek32_functor(peek32_functor_) + , peek64_functor(peek64_functor_) +{ + // nop +} + +void wb_iface_adapter::poke32(const wb_addr_type addr, const boost::uint32_t data) +{ + poke32_functor(addr / 4, data); // FIXME remove the requirement for /4 +} + +boost::uint32_t wb_iface_adapter::peek32(const wb_addr_type addr) +{ + return peek32_functor(addr); +} + +boost::uint64_t wb_iface_adapter::peek64(const wb_addr_type addr) +{ + return peek64_functor(addr); +} + +uhd::time_spec_t wb_iface_adapter::get_time(void) +{ + return gettime_functor(); +} + +void wb_iface_adapter::set_time(const uhd::time_spec_t& t) +{ + settime_functor(t); +} diff --git a/host/lib/rfnoc/wb_iface_adapter.hpp b/host/lib/rfnoc/wb_iface_adapter.hpp new file mode 100644 index 000000000..04623d203 --- /dev/null +++ b/host/lib/rfnoc/wb_iface_adapter.hpp @@ -0,0 +1,70 @@ +// +// Copyright 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_RFNOC_WB_IFACE_ADAPTER_HPP +#define INCLUDED_RFNOC_WB_IFACE_ADAPTER_HPP + +#include <uhd/config.hpp> +#include <uhd/types/wb_iface.hpp> +#include <boost/function.hpp> + +namespace uhd { + namespace rfnoc { + +class UHD_API wb_iface_adapter : public uhd::timed_wb_iface +{ +public: + typedef boost::shared_ptr<wb_iface_adapter> sptr; + typedef boost::function<void(wb_addr_type, boost::uint32_t)> poke32_type; + typedef boost::function<boost::uint32_t(wb_addr_type)> peek32_type; + typedef boost::function<boost::uint64_t(wb_addr_type)> peek64_type; + typedef boost::function<time_spec_t(void)> gettime_type; + typedef boost::function<void(const time_spec_t&)> settime_type; + + wb_iface_adapter( + const poke32_type &, + const peek32_type &, + const peek64_type &, + const gettime_type &, + const settime_type & + ); + + wb_iface_adapter( + const poke32_type &, + const peek32_type &, + const peek64_type & + ); + + virtual ~wb_iface_adapter(void) {}; + + virtual void poke32(const wb_addr_type addr, const boost::uint32_t data); + virtual boost::uint32_t peek32(const wb_addr_type addr); + virtual boost::uint64_t peek64(const wb_addr_type addr); + virtual time_spec_t get_time(void); + virtual void set_time(const time_spec_t& t); + +private: + const poke32_type poke32_functor; + const peek32_type peek32_functor; + const peek64_type peek64_functor; + const gettime_type gettime_functor; + const settime_type settime_functor; +}; + +}} // namespace uhd::rfnoc + +#endif /* INCLUDED_RFNOC_WB_IFACE_ADAPTER_HPP */ diff --git a/host/lib/rfnoc/xports.hpp b/host/lib/rfnoc/xports.hpp new file mode 100644 index 000000000..7872f2e1b --- /dev/null +++ b/host/lib/rfnoc/xports.hpp @@ -0,0 +1,36 @@ +// +// Copyright 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 <uhd/types/sid.hpp> +#include <uhd/transport/zero_copy.hpp> + +namespace uhd { + + /*! Holds all necessary items for a bidirectional link + */ + 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; + uhd::sid_t send_sid; + uhd::sid_t recv_sid; + }; + +}; + |