aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib/rfnoc
diff options
context:
space:
mode:
Diffstat (limited to 'host/lib/rfnoc')
-rw-r--r--host/lib/rfnoc/CMakeLists.txt53
-rw-r--r--host/lib/rfnoc/block_ctrl_base.cpp587
-rw-r--r--host/lib/rfnoc/block_ctrl_base_factory.cpp96
-rw-r--r--host/lib/rfnoc/block_ctrl_impl.cpp33
-rw-r--r--host/lib/rfnoc/block_id.cpp150
-rw-r--r--host/lib/rfnoc/blockdef_xml_impl.cpp442
-rw-r--r--host/lib/rfnoc/ctrl_iface.cpp376
-rw-r--r--host/lib/rfnoc/ctrl_iface.hpp68
-rw-r--r--host/lib/rfnoc/ddc_block_ctrl_impl.cpp281
-rw-r--r--host/lib/rfnoc/dma_fifo_block_ctrl_impl.cpp122
-rw-r--r--host/lib/rfnoc/duc_block_ctrl_impl.cpp268
-rw-r--r--host/lib/rfnoc/graph_impl.cpp164
-rw-r--r--host/lib/rfnoc/graph_impl.hpp76
-rw-r--r--host/lib/rfnoc/legacy_compat.cpp700
-rw-r--r--host/lib/rfnoc/legacy_compat.hpp55
-rw-r--r--host/lib/rfnoc/nocscript/CMakeLists.txt37
-rw-r--r--host/lib/rfnoc/nocscript/block_iface.cpp255
-rw-r--r--host/lib/rfnoc/nocscript/block_iface.hpp94
-rw-r--r--host/lib/rfnoc/nocscript/expression.cpp413
-rw-r--r--host/lib/rfnoc/nocscript/expression.hpp380
-rw-r--r--host/lib/rfnoc/nocscript/function_table.cpp113
-rw-r--r--host/lib/rfnoc/nocscript/function_table.hpp93
-rwxr-xr-xhost/lib/rfnoc/nocscript/gen_basic_funcs.py474
-rw-r--r--host/lib/rfnoc/nocscript/parser.cpp363
-rw-r--r--host/lib/rfnoc/nocscript/parser.hpp50
-rw-r--r--host/lib/rfnoc/node_ctrl_base.cpp103
-rw-r--r--host/lib/rfnoc/radio_ctrl_impl.cpp364
-rw-r--r--host/lib/rfnoc/radio_ctrl_impl.hpp209
-rw-r--r--host/lib/rfnoc/rate_node_ctrl.cpp67
-rw-r--r--host/lib/rfnoc/rx_stream_terminator.cpp131
-rw-r--r--host/lib/rfnoc/rx_stream_terminator.hpp86
-rw-r--r--host/lib/rfnoc/scalar_node_ctrl.cpp66
-rw-r--r--host/lib/rfnoc/sink_block_ctrl_base.cpp111
-rw-r--r--host/lib/rfnoc/sink_node_ctrl.cpp92
-rw-r--r--host/lib/rfnoc/source_block_ctrl_base.cpp137
-rw-r--r--host/lib/rfnoc/source_node_ctrl.cpp96
-rw-r--r--host/lib/rfnoc/stream_sig.cpp85
-rw-r--r--host/lib/rfnoc/tick_node_ctrl.cpp75
-rw-r--r--host/lib/rfnoc/tx_stream_terminator.cpp65
-rw-r--r--host/lib/rfnoc/tx_stream_terminator.hpp90
-rw-r--r--host/lib/rfnoc/utils.hpp77
-rw-r--r--host/lib/rfnoc/wb_iface_adapter.cpp71
-rw-r--r--host/lib/rfnoc/wb_iface_adapter.hpp70
-rw-r--r--host/lib/rfnoc/xports.hpp36
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 &reg_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 &reg_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 &reg, 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 &reg, 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 &reg, 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 &reg_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;
+ };
+
+};
+