diff options
Diffstat (limited to 'host/lib/rfnoc')
44 files changed, 7798 insertions, 0 deletions
| diff --git a/host/lib/rfnoc/CMakeLists.txt b/host/lib/rfnoc/CMakeLists.txt new file mode 100644 index 000000000..130b4173e --- /dev/null +++ b/host/lib/rfnoc/CMakeLists.txt @@ -0,0 +1,53 @@ +# +# Copyright 2014-2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +LIBUHD_APPEND_SOURCES( +    # Infrastructure: +    ${CMAKE_CURRENT_SOURCE_DIR}/block_ctrl_base.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/block_ctrl_base_factory.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/block_ctrl_impl.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/blockdef_xml_impl.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/block_id.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/ctrl_iface.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/graph_impl.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/legacy_compat.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/node_ctrl_base.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/rate_node_ctrl.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/rx_stream_terminator.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/scalar_node_ctrl.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/sink_block_ctrl_base.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/sink_node_ctrl.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/source_block_ctrl_base.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/source_node_ctrl.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/stream_sig.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/tick_node_ctrl.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/tx_stream_terminator.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/wb_iface_adapter.cpp +    # Default block control classes: +    ${CMAKE_CURRENT_SOURCE_DIR}/ddc_block_ctrl_impl.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/duc_block_ctrl_impl.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/radio_ctrl_impl.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/dma_fifo_block_ctrl_impl +) + +INCLUDE_SUBDIRECTORY(nocscript) diff --git a/host/lib/rfnoc/block_ctrl_base.cpp b/host/lib/rfnoc/block_ctrl_base.cpp new file mode 100644 index 000000000..21bd32a1c --- /dev/null +++ b/host/lib/rfnoc/block_ctrl_base.cpp @@ -0,0 +1,587 @@ +// +// Copyright 2014-2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +// This file contains the block control functions for block controller classes. +// See block_ctrl_base_factory.cpp for discovery and factory functions. + +#include "ctrl_iface.hpp" +#include "nocscript/block_iface.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/convert.hpp> +#include <uhd/rfnoc/block_ctrl_base.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <boost/bind.hpp> + +#define UHD_BLOCK_LOG() UHD_LOGV(never) + +using namespace uhd; +using namespace uhd::rfnoc; +using std::string; + +/*********************************************************************** + * Helpers + **********************************************************************/ +//! Convert register to a peek/poke compatible address +inline boost::uint32_t _sr_to_addr(boost::uint32_t reg) { return reg * 4; }; +inline boost::uint32_t _sr_to_addr64(boost::uint32_t reg) { return reg * 8; }; // for peek64 + +/*********************************************************************** + * Structors + **********************************************************************/ +block_ctrl_base::block_ctrl_base( +        const make_args_t &make_args +) : _tree(make_args.tree), +    _transport_is_big_endian(make_args.is_big_endian), +    _ctrl_ifaces(make_args.ctrl_ifaces), +    _base_address(make_args.base_address & 0xFFF0) +{ +    UHD_BLOCK_LOG() << "block_ctrl_base()" << std::endl; + +    /*** Identify this block (NoC-ID, block-ID, and block definition) *******/ +    // Read NoC-ID (name is passed in through make_args): +    boost::uint64_t noc_id = sr_read64(SR_READBACK_REG_ID); +    _block_def = blockdef::make_from_noc_id(noc_id); +    if (_block_def) UHD_BLOCK_LOG() <<  "Found valid blockdef" << std::endl; +    if (not _block_def) +        _block_def = blockdef::make_from_noc_id(DEFAULT_NOC_ID); +    UHD_ASSERT_THROW(_block_def); +    // For the block ID, we start with block count 0 and increase until +    // we get a block ID that's not already registered: +    _block_id.set(make_args.device_index, make_args.block_name, 0); +    while (_tree->exists("xbar/" + _block_id.get_local())) { +        _block_id++; +    } +    UHD_BLOCK_LOG() +        << "NOC ID: " << str(boost::format("0x%016X  ") % noc_id) +        << "Block ID: " << _block_id << std::endl; + +    /*** Initialize property tree *******************************************/ +    _root_path = "xbar/" + _block_id.get_local(); +    _tree->create<boost::uint64_t>(_root_path / "noc_id").set(noc_id); + +    /*** Reset block state *******************************************/ +    clear(); + +    /*** Configure ports ****************************************************/ +    size_t n_valid_input_buffers = 0; +    BOOST_FOREACH(const size_t ctrl_port, get_ctrl_ports()) { +        // Set source addresses: +        sr_write(SR_BLOCK_SID, get_address(ctrl_port), ctrl_port); +        // Set sink buffer sizes: +        settingsbus_reg_t reg = SR_READBACK_REG_FIFOSIZE; +        uint64_t value = sr_read64(reg, ctrl_port); +        size_t buf_size_log2 = value & 0xFF; +        size_t buf_size_bytes = BYTES_PER_LINE * (1 << buf_size_log2); // Bytes == 8 * 2^x +        if (buf_size_bytes > 0) n_valid_input_buffers++; +        _tree->create<size_t>(_root_path / "input_buffer_size" / ctrl_port).set(buf_size_bytes); +    } + +    /*** Register names *****************************************************/ +    blockdef::registers_t sregs = _block_def->get_settings_registers(); +    BOOST_FOREACH(const std::string ®_name, sregs.keys()) { +        if (DEFAULT_NAMED_SR.has_key(reg_name)) { +            throw uhd::runtime_error(str( +                    boost::format("Register name %s is already defined!") +                    % reg_name +            )); +        } +        _tree->create<size_t>(_root_path / "registers" / "sr" / reg_name) +            .set(sregs.get(reg_name)); +    } +    blockdef::registers_t rbacks = _block_def->get_readback_registers(); +    BOOST_FOREACH(const std::string ®_name, rbacks.keys()) { +        _tree->create<size_t>(_root_path / "registers"/ "rb" / reg_name) +            .set(rbacks.get(reg_name)); +    } + +    /*** Init I/O port definitions ******************************************/ +    _init_port_defs("in",  _block_def->get_input_ports()); +    _init_port_defs("out", _block_def->get_output_ports()); +    // FIXME this warning always fails until the input buffer code above is fixed +    if (_tree->list(_root_path / "ports/in").size() != n_valid_input_buffers) { +        UHD_MSG(warning) << +            boost::format("[%s] defines %d input buffer sizes, but %d input ports") +            % get_block_id().get() % n_valid_input_buffers % _tree->list(_root_path / "ports/in").size() +            << std::endl; +    } + +    /*** Init default block args ********************************************/ +    _nocscript_iface = nocscript::block_iface::make(this); +    _init_block_args(); +} + +block_ctrl_base::~block_ctrl_base() +{ +    _tree->remove(_root_path); +} + +void block_ctrl_base::_init_port_defs( +            const std::string &direction, +            blockdef::ports_t ports, +            const size_t first_port_index +) { +    size_t port_index = first_port_index; +    BOOST_FOREACH(const blockdef::port_t &port_def, ports) { +        fs_path port_path = _root_path / "ports" / direction / port_index; +        if (not _tree->exists(port_path)) { +            _tree->create<blockdef::port_t>(port_path); +        } +        UHD_RFNOC_BLOCK_TRACE()  << "Adding port definition at " << port_path +            << boost::format(": type = '%s' pkt_size = '%s' vlen = '%s'") % port_def["type"] % port_def["pkt_size"] % port_def["vlen"] +            << std::endl; +        _tree->access<blockdef::port_t>(port_path).set(port_def); +        port_index++; +    } +} + +void block_ctrl_base::_init_block_args() +{ +    blockdef::args_t args = _block_def->get_args(); +    fs_path arg_path = _root_path / "args"; +    BOOST_FOREACH(const size_t port, get_ctrl_ports()) { +        _tree->create<std::string>(arg_path / port); +    } + +    // First, create all nodes. +    BOOST_FOREACH(const blockdef::arg_t &arg, args) { +        fs_path arg_type_path = arg_path / arg["port"] / arg["name"] / "type"; +        _tree->create<std::string>(arg_type_path).set(arg["type"]); +        fs_path arg_val_path  = arg_path / arg["port"] / arg["name"] / "value"; +        if (arg["type"] == "int_vector") { throw uhd::runtime_error("not yet implemented: int_vector"); } +        else if (arg["type"] == "int") { _tree->create<int>(arg_val_path); } +        else if (arg["type"] == "double") { _tree->create<double>(arg_val_path); } +        else if (arg["type"] == "string") { _tree->create<string>(arg_val_path); } +        else { UHD_THROW_INVALID_CODE_PATH(); } +    } +    // Next: Create all the subscribers and coercers. +    // TODO: Add coercer +#define _SUBSCRIBE_CHECK_AND_RUN(type, arg_tag, error_message) \ +    _tree->access<type>(arg_val_path).add_coerced_subscriber(boost::bind((&nocscript::block_iface::run_and_check), _nocscript_iface, arg[#arg_tag], error_message)) +    BOOST_FOREACH(const blockdef::arg_t &arg, args) { +        fs_path arg_val_path = arg_path / arg["port"] / arg["name"] / "value"; +        if (not arg["check"].empty()) { +            if (arg["type"] == "string") { _SUBSCRIBE_CHECK_AND_RUN(string, check, arg["check_message"]); } +            else if (arg["type"] == "int") { _SUBSCRIBE_CHECK_AND_RUN(int, check, arg["check_message"]); } +            else if (arg["type"] == "double") { _SUBSCRIBE_CHECK_AND_RUN(double, check, arg["check_message"]); } +            else if (arg["type"] == "int_vector") { throw uhd::runtime_error("not yet implemented: int_vector"); } +            else { UHD_THROW_INVALID_CODE_PATH(); } +        } +        if (not arg["action"].empty()) { +            if (arg["type"] == "string") { _SUBSCRIBE_CHECK_AND_RUN(string, action, ""); } +            else if (arg["type"] == "int") { _SUBSCRIBE_CHECK_AND_RUN(int, action, ""); } +            else if (arg["type"] == "double") { _SUBSCRIBE_CHECK_AND_RUN(double, action, ""); } +            else if (arg["type"] == "int_vector") { throw uhd::runtime_error("not yet implemented: int_vector"); } +            else { UHD_THROW_INVALID_CODE_PATH(); } +        } +    } + +    // Finally: Set the values. This will call subscribers, if we have any. +    BOOST_FOREACH(const blockdef::arg_t &arg, args) { +        fs_path arg_val_path = arg_path / arg["port"] / arg["name"] / "value"; +        if (not arg["value"].empty()) { +            if (arg["type"] == "int_vector") { throw uhd::runtime_error("not yet implemented: int_vector"); } +            else if (arg["type"] == "int") { _tree->access<int>(arg_val_path).set(boost::lexical_cast<int>(arg["value"])); } +            else if (arg["type"] == "double") { _tree->access<double>(arg_val_path).set(boost::lexical_cast<double>(arg["value"])); } +            else if (arg["type"] == "string") { _tree->access<string>(arg_val_path).set(arg["value"]); } +            else { UHD_THROW_INVALID_CODE_PATH(); } +        } +    } +} + +/*********************************************************************** + * FPGA control & communication + **********************************************************************/ +wb_iface::sptr block_ctrl_base::get_ctrl_iface(const size_t block_port) +{ +    return _ctrl_ifaces[block_port]; +} + +std::vector<size_t> block_ctrl_base::get_ctrl_ports() const +{ +    std::vector<size_t> ctrl_ports; +    ctrl_ports.reserve(_ctrl_ifaces.size()); +    std::pair<size_t, wb_iface::sptr> it; +    BOOST_FOREACH(it, _ctrl_ifaces) { +        ctrl_ports.push_back(it.first); +    } +    return ctrl_ports; +} + +void block_ctrl_base::sr_write(const boost::uint32_t reg, const boost::uint32_t data, const size_t port) +{ +    //UHD_BLOCK_LOG() << "  "; +    //UHD_RFNOC_BLOCK_TRACE() << boost::format("sr_write(%d, %08X, %d)") % reg % data % port << std::endl; +    if (not _ctrl_ifaces.count(port)) { +        throw uhd::key_error(str(boost::format("[%s] sr_write(): No such port: %d") % get_block_id().get() % port)); +    } +    try { +        _ctrl_ifaces[port]->poke32(_sr_to_addr(reg), data); +    } +    catch(const std::exception &ex) { +        throw uhd::io_error(str(boost::format("[%s] sr_write() failed: %s") % get_block_id().get() % ex.what())); +    } +} + +void block_ctrl_base::sr_write(const std::string ®, const boost::uint32_t data, const size_t port) +{ +    boost::uint32_t reg_addr = 255; +    if (DEFAULT_NAMED_SR.has_key(reg)) { +        reg_addr = DEFAULT_NAMED_SR[reg]; +    } else { +        if (not _tree->exists(_root_path / "registers" / "sr" / reg)) { +            throw uhd::key_error(str( +                    boost::format("Unknown settings register name: %s") +                    % reg +            )); +        } +        reg_addr = boost::uint32_t(_tree->access<size_t>(_root_path / "registers" / "sr" / reg).get()); +    } +    UHD_BLOCK_LOG() << "  "; +    UHD_RFNOC_BLOCK_TRACE() << boost::format("sr_write(%s, %08X) ==> ") % reg % data << std::endl; +    return sr_write(reg_addr, data, port); +} + +boost::uint64_t block_ctrl_base::sr_read64(const settingsbus_reg_t reg, const size_t port) +{ +    if (not _ctrl_ifaces.count(port)) { +        throw uhd::key_error(str(boost::format("[%s] sr_read64(): No such port: %d") % get_block_id().get() % port)); +    } +    try { +        return _ctrl_ifaces[port]->peek64(_sr_to_addr64(reg)); +    } +    catch(const std::exception &ex) { +        throw uhd::io_error(str(boost::format("[%s] sr_read64() failed: %s") % get_block_id().get() % ex.what())); +    } +} + +boost::uint32_t block_ctrl_base::sr_read32(const settingsbus_reg_t reg, const size_t port) +{ +    if (not _ctrl_ifaces.count(port)) { +        throw uhd::key_error(str(boost::format("[%s] sr_read32(): No such port: %d") % get_block_id().get() % port)); +    } +    try { +        return _ctrl_ifaces[port]->peek32(_sr_to_addr64(reg)); +    } +    catch(const std::exception &ex) { +        throw uhd::io_error(str(boost::format("[%s] sr_read32() failed: %s") % get_block_id().get() % ex.what())); +    } +} + +boost::uint64_t block_ctrl_base::user_reg_read64(const boost::uint32_t addr, const size_t port) +{ +    try { +        // Set readback register address +        sr_write(SR_READBACK_ADDR, addr, port); +        // Read readback register via RFNoC +        return sr_read64(SR_READBACK_REG_USER, port); +    } +    catch(const std::exception &ex) { +        throw uhd::io_error(str(boost::format("%s user_reg_read64() failed: %s") % get_block_id().get() % ex.what())); +    } +} + +boost::uint64_t block_ctrl_base::user_reg_read64(const std::string ®, const size_t port) +{ +    if (not _tree->exists(_root_path / "registers" / "rb" / reg)) { +        throw uhd::key_error(str( +                boost::format("Invalid readback register name: %s") +                % reg +        )); +    } +    return user_reg_read64(boost::uint32_t( +        _tree->access<size_t>(_root_path / "registers" / "rb" / reg).get() +    ), port); +} + +boost::uint32_t block_ctrl_base::user_reg_read32(const boost::uint32_t addr, const size_t port) +{ +    try { +        // Set readback register address +        sr_write(SR_READBACK_ADDR, addr, port); +        // Read readback register via RFNoC +        return sr_read32(SR_READBACK_REG_USER, port); +    } +    catch(const std::exception &ex) { +        throw uhd::io_error(str(boost::format("[%s] user_reg_read32() failed: %s") % get_block_id().get() % ex.what())); +    } +} + +boost::uint32_t block_ctrl_base::user_reg_read32(const std::string ®, const size_t port) +{ +    if (not _tree->exists(_root_path / "registers" / "rb" / reg)) { +        throw uhd::key_error(str( +                boost::format("Invalid readback register name: %s") +                % reg +        )); +    } +    return user_reg_read32(boost::uint32_t( +        _tree->access<size_t>(_root_path / "registers" / "sr" / reg).get() +    ), port); +} + +void block_ctrl_base::set_command_time( +        const time_spec_t &time_spec, +        const size_t port +) { +    if (port == ANY_PORT) { +        BOOST_FOREACH(const size_t specific_port, get_ctrl_ports()) { +            set_command_time(time_spec, specific_port); +        } +        return; +    } +    boost::shared_ptr<ctrl_iface> iface_sptr = +        boost::dynamic_pointer_cast<ctrl_iface>(get_ctrl_iface(port)); +    if (not iface_sptr) { +        throw uhd::assertion_error(str( +            boost::format("[%s] Cannot set command time on port '%d'") +            % unique_id() % port +        )); +    } + +    iface_sptr->set_time(time_spec); +} + +time_spec_t block_ctrl_base::get_command_time( +        const size_t port +) { +    boost::shared_ptr<ctrl_iface> iface_sptr = +        boost::dynamic_pointer_cast<ctrl_iface>(get_ctrl_iface(port)); +    if (not iface_sptr) { +        throw uhd::assertion_error(str( +            boost::format("[%s] Cannot get command time on port '%d'") +            % unique_id() % port +        )); +    } + +    return iface_sptr->get_time(); +} + +void block_ctrl_base::set_command_tick_rate( +        const double tick_rate, +        const size_t port +) { +    if (port == ANY_PORT) { +        BOOST_FOREACH(const size_t specific_port, get_ctrl_ports()) { +            set_command_tick_rate(tick_rate, specific_port); +        } +        return; +    } +    boost::shared_ptr<ctrl_iface> iface_sptr = +        boost::dynamic_pointer_cast<ctrl_iface>(get_ctrl_iface(port)); +    if (not iface_sptr) { +        throw uhd::assertion_error(str( +            boost::format("[%s] Cannot set command time on port '%d'") +            % unique_id() % port +        )); +    } + +    iface_sptr->set_tick_rate(tick_rate); +} + +void block_ctrl_base::clear_command_time(const size_t port) +{ +    boost::shared_ptr<ctrl_iface> iface_sptr = +        boost::dynamic_pointer_cast<ctrl_iface>(get_ctrl_iface(port)); +    if (not iface_sptr) { +        throw uhd::assertion_error(str( +            boost::format("[%s] Cannot set command time on port '%d'") +            % unique_id() % port +        )); +    } + +    iface_sptr->set_time(time_spec_t(0.0)); +} + +void block_ctrl_base::clear(const size_t /* port */) +{ +    UHD_RFNOC_BLOCK_TRACE() << "block_ctrl_base::clear() " << std::endl; +    // Call parent... +    node_ctrl_base::clear(); +    // ...then child +    BOOST_FOREACH(const size_t port_index, get_ctrl_ports()) { +        _clear(port_index); +    } +} + +boost::uint32_t block_ctrl_base::get_address(size_t block_port) { +    UHD_ASSERT_THROW(block_port < 16); +    return (_base_address & 0xFFF0) | (block_port & 0xF); +} + +/*********************************************************************** + * Argument handling + **********************************************************************/ +void block_ctrl_base::set_args(const uhd::device_addr_t &args, const size_t port) +{ +    BOOST_FOREACH(const std::string &key, args.keys()) { +        if (_tree->exists(get_arg_path(key, port))) { +            set_arg(key, args.get(key), port); +        } +    } +} + +void block_ctrl_base::set_arg(const std::string &key, const std::string &val, const size_t port) +{ +    fs_path arg_path = get_arg_path(key, port); +    if (not _tree->exists(arg_path / "value")) { +        throw uhd::runtime_error(str( +                boost::format("Attempting to set uninitialized argument '%s' on block '%s'") +                % key % unique_id() +        )); +    } + +    std::string type = _tree->access<std::string>(arg_path / "type").get(); +    fs_path arg_val_path = arg_path / "value"; +    try { +        if (type == "string") { +            _tree->access<std::string>(arg_val_path).set(val); +        } +        else if (type == "int") { +            _tree->access<int>(arg_val_path).set(boost::lexical_cast<int>(val)); +        } +        else if (type == "double") { +            _tree->access<double>(arg_val_path).set(boost::lexical_cast<double>(val)); +        } +        else if (type == "int_vector") { +            throw uhd::runtime_error("not yet implemented: int_vector"); +        } +    } catch (const boost::bad_lexical_cast &) { +        throw uhd::value_error(str( +                    boost::format("Error trying to cast value %s == '%s' to type '%s'") +                    % key % val % type +        )); +    } +} + +device_addr_t block_ctrl_base::get_args(const size_t port) const +{ +    device_addr_t args; +    BOOST_FOREACH(const std::string &key, _tree->list(_root_path / "args" / port)) { +        args[key] = get_arg(key); +    } +    return args; +} + +std::string block_ctrl_base::get_arg(const std::string &key, const size_t port) const +{ +    fs_path arg_path = get_arg_path(key, port); +    if (not _tree->exists(arg_path / "value")) { +        throw uhd::runtime_error(str( +                boost::format("Attempting to get uninitialized argument '%s' on block '%s'") +                % key % unique_id() +        )); +    } + +    std::string type = _tree->access<std::string>(arg_path / "type").get(); +    fs_path arg_val_path = arg_path / "value"; +    if (type == "string") { +        return _tree->access<std::string>(arg_val_path).get(); +    } +    else if (type == "int") { +        return boost::lexical_cast<std::string>(_tree->access<int>(arg_val_path).get()); +    } +    else if (type == "double") { +        return boost::lexical_cast<std::string>(_tree->access<double>(arg_val_path).get()); +    } +    else if (type == "int_vector") { +        throw uhd::runtime_error("not yet implemented: int_vector"); +    } + +    UHD_THROW_INVALID_CODE_PATH(); +    return ""; +} + +std::string block_ctrl_base::get_arg_type(const std::string &key, const size_t port) const +{ +    fs_path arg_type_path = _root_path / "args" / port / key / "type"; +    return _tree->access<std::string>(arg_type_path).get(); +} + +stream_sig_t block_ctrl_base::_resolve_port_def(const blockdef::port_t &port_def) const +{ +    if (not port_def.is_valid()) { +        throw uhd::runtime_error(str( +                boost::format("Invalid port definition: %s") % port_def.to_string() +        )); +    } + +    // TODO this entire section is pretty dumb at this point. Needs better +    // checks. +    stream_sig_t stream_sig; +    // Item Type +    if (port_def.is_variable("type")) { +        std::string var_name = port_def["type"].substr(1); +        // TODO check this is even a string +        stream_sig.item_type = get_arg(var_name); +    } else if (port_def.is_keyword("type")) { +        throw uhd::runtime_error("keywords resolution for type not yet implemented"); +    } else { +        stream_sig.item_type = port_def["type"]; +    } +    //UHD_RFNOC_BLOCK_TRACE() << "  item type: " << stream_sig.item_type << std::endl; + +    // Vector length +    if (port_def.is_variable("vlen")) { +        std::string var_name = port_def["vlen"].substr(1); +        stream_sig.vlen = boost::lexical_cast<size_t>(get_arg(var_name)); +    } else if (port_def.is_keyword("vlen")) { +        throw uhd::runtime_error("keywords resolution for vlen not yet implemented"); +    } else { +        stream_sig.vlen = boost::lexical_cast<size_t>(port_def["vlen"]); +    } +    //UHD_RFNOC_BLOCK_TRACE() << "  vector length: " << stream_sig.vlen << std::endl; + +    // Packet size +    if (port_def.is_variable("pkt_size")) { +        std::string var_name = port_def["pkt_size"].substr(1); +        stream_sig.packet_size = boost::lexical_cast<size_t>(get_arg(var_name)); +    } else if (port_def.is_keyword("pkt_size")) { +        if (port_def["pkt_size"] != "%vlen") { +            throw uhd::runtime_error("generic keywords resolution for pkt_size not yet implemented"); +        } +        if (stream_sig.vlen == 0) { +            stream_sig.packet_size = 0; +        } else { +            if (stream_sig.item_type.empty()) { +                throw uhd::runtime_error("cannot resolve pkt_size if item type is not given"); +            } +            size_t bpi = uhd::convert::get_bytes_per_item(stream_sig.item_type); +            stream_sig.packet_size = stream_sig.vlen * bpi; +        } +    } else { +        stream_sig.packet_size = boost::lexical_cast<size_t>(port_def["pkt_size"]); +    } +    //UHD_RFNOC_BLOCK_TRACE() << "  packet size: " << stream_sig.vlen << std::endl; + +    return stream_sig; +} + + +/*********************************************************************** + * Hooks & Derivables + **********************************************************************/ +void block_ctrl_base::_clear(const size_t port) +{ +    UHD_RFNOC_BLOCK_TRACE() << "block_ctrl_base::_clear() " << std::endl; +    sr_write(SR_CLEAR_TX_FC, 0x00C1EA12, port); // 'CLEAR', but we can write anything, really +    sr_write(SR_CLEAR_RX_FC, 0x00C1EA12, port); // 'CLEAR', but we can write anything, really +} + +// vim: sw=4 et: diff --git a/host/lib/rfnoc/block_ctrl_base_factory.cpp b/host/lib/rfnoc/block_ctrl_base_factory.cpp new file mode 100644 index 000000000..aab2ed475 --- /dev/null +++ b/host/lib/rfnoc/block_ctrl_base_factory.cpp @@ -0,0 +1,96 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/format.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/rfnoc/blockdef.hpp> +#include <uhd/rfnoc/block_ctrl_base.hpp> + +#define UHD_FACTORY_LOG() UHD_LOGV(never) + +using namespace uhd; +using namespace uhd::rfnoc; + +typedef uhd::dict<std::string, block_ctrl_base::make_t> block_fcn_reg_t; +// Instantiate the block function registry container +UHD_SINGLETON_FCN(block_fcn_reg_t, get_block_fcn_regs); + +void block_ctrl_base::register_block( +        const make_t &make, +        const std::string &key +) { +    if (get_block_fcn_regs().has_key(key)) { +        throw uhd::runtime_error( +            str(boost::format("Attempting to register an RFNoC block with key %s for the second time.") % key) +       ); +    } + +    get_block_fcn_regs().set(key, make); +} + +/*! Look up names for blocks in XML files using NoC ID. + */ +static void lookup_block_key(boost::uint64_t noc_id, make_args_t &make_args) +{ +    try { +        blockdef::sptr bd = blockdef::make_from_noc_id(noc_id); +        if (not bd) { +            make_args.block_key  = DEFAULT_BLOCK_NAME; +            make_args.block_name = DEFAULT_BLOCK_NAME; +            return; +        } +        UHD_ASSERT_THROW(bd->is_block()); +        make_args.block_key  = bd->get_key(); +        make_args.block_name = bd->get_name(); +        return; +    } catch (std::exception &e) { +        UHD_MSG(warning) << str(boost::format("Error while looking up name for NoC-ID %016X.\n%s") % noc_id % e.what()) << std::endl; +    } + +    make_args.block_key  = DEFAULT_BLOCK_NAME; +    make_args.block_name = DEFAULT_BLOCK_NAME; +} + + +block_ctrl_base::sptr block_ctrl_base::make( +        const make_args_t &make_args_, +        boost::uint64_t noc_id +) { +    UHD_FACTORY_LOG() << "[RFNoC Factory] block_ctrl_base::make() " << std::endl; +    make_args_t make_args = make_args_; + +    // Check if a block key was specified, in this case, we *must* either +    // create a specialized block controller class or throw +    if (make_args.block_key.empty()) { +        lookup_block_key(noc_id, make_args); +    } else if (not get_block_fcn_regs().has_key(make_args.block_key)) { +        throw uhd::runtime_error( +            str(boost::format("No block controller class registered for key '%s'.") % make_args.block_key) +        ); +    } +    if (not get_block_fcn_regs().has_key(make_args.block_key)) { +        make_args.block_key = DEFAULT_BLOCK_NAME; +    } +    if (make_args.block_name.empty()) { +        make_args.block_name = make_args.block_key; +    } + +    UHD_FACTORY_LOG() << "[RFNoC Factory] Using controller key '" << make_args.block_key << "' and block name '" << make_args.block_name << "'" << std::endl; +    return get_block_fcn_regs()[make_args.block_key](make_args); +} + diff --git a/host/lib/rfnoc/block_ctrl_impl.cpp b/host/lib/rfnoc/block_ctrl_impl.cpp new file mode 100644 index 000000000..be21538f3 --- /dev/null +++ b/host/lib/rfnoc/block_ctrl_impl.cpp @@ -0,0 +1,33 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/rfnoc/block_ctrl.hpp> + +using namespace uhd::rfnoc; + +class block_ctrl_impl : public block_ctrl +{ +public: +    UHD_RFNOC_BLOCK_CONSTRUCTOR(block_ctrl) +    { +        // nop +    } + +    // Very empty class, this one +}; + +UHD_RFNOC_BLOCK_REGISTER(block_ctrl, DEFAULT_BLOCK_NAME); diff --git a/host/lib/rfnoc/block_id.cpp b/host/lib/rfnoc/block_id.cpp new file mode 100644 index 000000000..55bd5e6f7 --- /dev/null +++ b/host/lib/rfnoc/block_id.cpp @@ -0,0 +1,150 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/format.hpp> +#include <boost/regex.hpp> +#include <boost/lexical_cast.hpp> +#include <uhd/exception.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <uhd/rfnoc/block_id.hpp> + +#include <iostream> + +using namespace uhd::rfnoc; + +block_id_t::block_id_t() : +    _device_no(0), +    _block_name(""), +    _block_ctr(0) +{ +} + +block_id_t::block_id_t(const std::string &block_str) +    : _device_no(0), +    _block_name(""), +    _block_ctr(0) +{ +    if (not set(block_str)) { +        throw uhd::value_error("block_id_t: Invalid block ID string."); +    } +} + +block_id_t::block_id_t( +        const size_t device_no, +        const std::string &block_name, +        const size_t block_ctr +) : _device_no(device_no), +    _block_name(block_name), +    _block_ctr(block_ctr) +{ +    if (not is_valid_blockname(block_name)) { +        throw uhd::value_error("block_id_t: Invalid block name."); +    } +} + +bool block_id_t::is_valid_blockname(const std::string &block_name) +{ +    return boost::regex_match(block_name, boost::regex(VALID_BLOCKNAME_REGEX)); +} + +bool block_id_t::is_valid_block_id(const std::string &block_name) +{ +    return boost::regex_match(block_name, boost::regex(VALID_BLOCKID_REGEX)); +} + +std::string block_id_t::to_string() const +{ +    return str(boost::format("%d/%s") +        % get_device_no() +        % get_local() +    ); +} + +std::string block_id_t::get_local() const +{ +    return str(boost::format("%s_%d") +        % get_block_name() +        % get_block_count() +    ); +} + +uhd::fs_path block_id_t::get_tree_root() const +{ +    return str(boost::format("/mboards/%d/xbar/%s") +        % get_device_no() +        % get_local() +    ); +} + +bool block_id_t::match(const std::string &block_str) +{ +    boost::cmatch matches; +    if (not boost::regex_match(block_str.c_str(), matches, boost::regex(VALID_BLOCKID_REGEX))) { +        return false; +    } +    try { +        return  (matches[1] == "" or boost::lexical_cast<size_t>(matches[1]) == _device_no) +            and (matches[2] == "" or matches[2] == _block_name) +            and (matches[3] == "" or boost::lexical_cast<size_t>(matches[3]) == _block_ctr) +            and not (matches[1] == "" and matches[2] == "" and matches[3] == ""); +    } catch (const std::bad_cast &e) { +        return false; +    } +    return false; +} + +bool block_id_t::set(const std::string &new_name) +{ +    boost::cmatch matches; +    if (not boost::regex_match(new_name.c_str(), matches, boost::regex(VALID_BLOCKID_REGEX))) { +        return false; +    } +    if (not (matches[1] == "")) { +        _device_no = boost::lexical_cast<size_t>(matches[1]); +    } +    if (not (matches[2] == "")) { +        _block_name = matches[2]; +    } +    if (not (matches[3] == "")) { +        _block_ctr = boost::lexical_cast<size_t>(matches[3]); +    } +    return true; +} + +bool block_id_t::set( +        const size_t device_no, +        const std::string &block_name, +        const size_t block_ctr +) { +    if (not set_block_name(block_name)) { +        return false; +    } +    set_device_no(device_no); +    set_block_count(block_ctr); +    return true; +} + +bool block_id_t::set_block_name(const std::string &block_name) +{ +    if (not is_valid_blockname(block_name)) { +        return false; +    } +    _block_name = block_name; +    return true; +} + diff --git a/host/lib/rfnoc/blockdef_xml_impl.cpp b/host/lib/rfnoc/blockdef_xml_impl.cpp new file mode 100644 index 000000000..5ff69d512 --- /dev/null +++ b/host/lib/rfnoc/blockdef_xml_impl.cpp @@ -0,0 +1,442 @@ +// +// Copyright 2014-2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/exception.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <uhd/rfnoc/blockdef.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/paths.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/filesystem/operations.hpp> +#include <boost/property_tree/ptree.hpp> +#include <boost/property_tree/xml_parser.hpp> +#include <cstdlib> + +using namespace uhd; +using namespace uhd::rfnoc; +namespace fs = boost::filesystem; +namespace pt = boost::property_tree; + +static const fs::path XML_BLOCKS_SUBDIR("blocks"); +static const fs::path XML_COMPONENTS_SUBDIR("components"); +static const fs::path XML_EXTENSION(".xml"); + + +/**************************************************************************** + * port_t stuff + ****************************************************************************/ +const device_addr_t blockdef::port_t::PORT_ARGS( +        "name," +        "type," +        "vlen=0," +        "pkt_size=0," +        "optional=0," +        "bursty=0," +        "port," +); + +blockdef::port_t::port_t() +{ +    // This guarantees that we can access these keys +    // even if they were never initialized: +    BOOST_FOREACH(const std::string &key, PORT_ARGS.keys()) { +        set(key, PORT_ARGS[key]); +    } +} + +bool blockdef::port_t::is_variable(const std::string &key) const +{ +    const std::string &val = get(key); +    return (val[0] == '$'); +} + +bool blockdef::port_t::is_keyword(const std::string &key) const +{ +    const std::string &val = get(key); +    return (val[0] == '%'); +} + +bool blockdef::port_t::is_valid() const +{ +    // Check we have all the keys: +    BOOST_FOREACH(const std::string &key, PORT_ARGS.keys()) { +        if (not has_key(key)) { +            return false; +        } +    } + +    // Twelve of the clock, all seems well +    return true; +} + +std::string blockdef::port_t::to_string() const +{ +    std::string result; +    BOOST_FOREACH(const std::string &key, PORT_ARGS.keys()) { +        if (has_key(key)) { +            result += str(boost::format("%s=%s,") % key % get(key)); +        } +    } + +    return result; +} + +/**************************************************************************** + * arg_t stuff + ****************************************************************************/ +const device_addr_t blockdef::arg_t::ARG_ARGS( +    // List all tags/args an <arg> can have here: +        "name," +        "type," +        "value," +        "check," +        "check_message," +        "action," +        "port=0," +); + +const std::set<std::string> blockdef::arg_t::VALID_TYPES = boost::assign::list_of +    // List all tags/args a <type> can have here: +            ("string") +            ("int") +            ("int_vector") +            ("double") +; + +blockdef::arg_t::arg_t() +{ +    // This guarantees that we can access these keys +    // even if they were never initialized: +    BOOST_FOREACH(const std::string &key, ARG_ARGS.keys()) { +        set(key, ARG_ARGS[key]); +    } +} + +bool blockdef::arg_t::is_valid() const +{ +    // 1. Check we have all the keys: +    BOOST_FOREACH(const std::string &key, ARG_ARGS.keys()) { +        if (not has_key(key)) { +            return false; +        } +    } + +    // 2. Check arg type is valid +    if (not get("type").empty() and not VALID_TYPES.count(get("type"))) { +        return false; +    } + +    // Twelve of the clock, all seems well +    return true; +} + +std::string blockdef::arg_t::to_string() const +{ +    std::string result; +    BOOST_FOREACH(const std::string &key, ARG_ARGS.keys()) { +        if (has_key(key)) { +            result += str(boost::format("%s=%s,") % key % get(key)); +        } +    } + +    return result; +} + +/**************************************************************************** + * blockdef_impl stuff + ****************************************************************************/ +class blockdef_xml_impl : public blockdef +{ +public: +    enum xml_repr_t { +        DESCRIBES_BLOCK, +        DESCRIBES_COMPONENT +    }; + +    //! Returns a list of base paths for the XML files. +    // It is assumed that block definitions are in a subdir with name +    // XML_BLOCKS_SUBDIR and component definitions in a subdir with name +    // XML_COMPONENTS_SUBDIR +    static std::vector<boost::filesystem::path> get_xml_paths() +    { +        std::vector<boost::filesystem::path> paths; + +        // Path from environment variable +        if (std::getenv(XML_PATH_ENV.c_str()) != NULL) { +            paths.push_back(boost::filesystem::path(std::getenv(XML_PATH_ENV.c_str()))); +        } + +        // Finally, the default path +        const boost::filesystem::path pkg_path = uhd::get_pkg_path(); +        paths.push_back(pkg_path / XML_DEFAULT_PATH); + +        return paths; +    } + +    //! Matches a NoC ID through substring matching +    static bool match_noc_id(const std::string &lhs_, boost::uint64_t rhs_) +    { +        // Sanitize input: Make both values strings with all uppercase +        // characters and no leading 0x. Check inputs are valid. +        std::string lhs = boost::to_upper_copy(lhs_); +        std::string rhs = str(boost::format("%016X") % rhs_); +        if (lhs.size() > 2 and lhs[0] == '0' and lhs[1] == 'X') { +            lhs = lhs.substr(2); +        } +        UHD_ASSERT_THROW(rhs.size() == 16); +        if (lhs.size() < 4 or lhs.size() > 16) { +            throw uhd::value_error(str(boost::format( +                    "%s is not a valid NoC ID (must be hexadecimal, min 4 and max 16 characters)" +            ) % lhs_)); +        } + +        // OK, all good now. Next, we try and match the substring lhs in rhs: +        return (rhs.find(lhs) == 0); +    } + +    //! Open the file at filename and see if it's a block definition for the given NoC ID +    static bool has_noc_id(boost::uint64_t noc_id, const fs::path &filename) +    { +        pt::ptree propt; +        try { +            read_xml(filename.string(), propt); +            BOOST_FOREACH(pt::ptree::value_type &v, propt.get_child("nocblock.ids")) { +                if (v.first == "id" and match_noc_id(v.second.data(), noc_id)) { +                    return true; +                } +            } +        } catch (std::exception &e) { +            UHD_MSG(warning) << "has_noc_id(): caught exception " << e.what() << std::endl; +            return false; +        } +        return false; +    } + +    blockdef_xml_impl(const fs::path &filename, boost::uint64_t noc_id, xml_repr_t type=DESCRIBES_BLOCK) : +        _type(type), +        _noc_id(noc_id) +    { +        //UHD_MSG(status) << "Reading XML file: " << filename.string().c_str() << std::endl; +        read_xml(filename.string(), _pt); +        try { +            // Check key is valid +            get_key(); +            // Check name is valid +            get_name(); +            // Check there's at least one port +            ports_t in = get_input_ports(); +            ports_t out = get_output_ports(); +            if (in.empty() and out.empty()) { +                throw uhd::runtime_error("Block does not define inputs or outputs."); +            } +            // Check args are valid +            get_args(); +            // TODO any more checks? +        } catch (const std::exception &e) { +            throw uhd::runtime_error(str( +                        boost::format("Invalid block definition in %s: %s") +                        % filename.string() % e.what() +            )); +        } +    } + +    bool is_block() const +    { +        return _type == DESCRIBES_BLOCK; +    } + +    bool is_component() const +    { +        return _type == DESCRIBES_COMPONENT; +    } + +    std::string get_key() const +    { +        try { +            return _pt.get<std::string>("nocblock.key"); +        } catch (const pt::ptree_bad_path &) { +            return _pt.get<std::string>("nocblock.blockname"); +        } +    } + +    std::string get_name() const +    { +        return _pt.get<std::string>("nocblock.blockname"); +    } + +    boost::uint64_t noc_id() const +    { +        return _noc_id; +    } + +    ports_t get_input_ports() +    { +        return _get_ports("sink"); +    } + +    ports_t get_output_ports() +    { +        return _get_ports("source"); +    } + +    ports_t _get_ports(const std::string &port_type) +    { +        std::set<size_t> port_numbers; +        size_t n_ports = 0; +        ports_t ports; +        BOOST_FOREACH(pt::ptree::value_type &v, _pt.get_child("nocblock.ports")) { +            if (v.first != port_type) continue; +            // Now we have the correct sink or source node: +            port_t port; +            BOOST_FOREACH(const std::string &key, port_t::PORT_ARGS.keys()) { +                port[key] = v.second.get(key, port_t::PORT_ARGS[key]); +            } +            // We have to be extra-careful with the port numbers: +            if (port["port"].empty()) { +                port["port"] = boost::lexical_cast<std::string>(n_ports); +            } +            size_t new_port_number; +            try { +                new_port_number = boost::lexical_cast<size_t>(port["port"]); +            } catch (const boost::bad_lexical_cast &e) { +                throw uhd::value_error(str( +                        boost::format("Invalid port number '%s' on port '%s'") +                        % port["port"] % port["name"] +                )); +            } +            if (port_numbers.count(new_port_number) or new_port_number > MAX_NUM_PORTS) { +                throw uhd::value_error(str( +                        boost::format("Port '%s' has invalid port number %d!") +                        % port["name"] % new_port_number +                )); +            } +            port_numbers.insert(new_port_number); +            n_ports++; +            ports.push_back(port); +        } +        return ports; +    } + +    std::vector<size_t> get_all_port_numbers() +    { +        std::set<size_t> set_ports; +        BOOST_FOREACH(const port_t &port, get_input_ports()) { +            set_ports.insert(boost::lexical_cast<size_t>(port["port"])); +        } +        BOOST_FOREACH(const port_t &port, get_output_ports()) { +            set_ports.insert(boost::lexical_cast<size_t>(port["port"])); +        } +        return std::vector<size_t>(set_ports.begin(), set_ports.end()); +    } + + +    blockdef::args_t get_args() +    { +        args_t args; +        bool is_valid = true; +        pt::ptree def; +        BOOST_FOREACH(pt::ptree::value_type &v, _pt.get_child("nocblock.args", def)) { +            arg_t arg; +            if (v.first != "arg") continue; +            BOOST_FOREACH(const std::string &key, arg_t::ARG_ARGS.keys()) { +                arg[key] = v.second.get(key, arg_t::ARG_ARGS[key]); +            } +            if (arg["type"].empty()) { +                arg["type"] = "string"; +            } +            if (not arg.is_valid()) { +                UHD_MSG(warning) << boost::format("Found invalid argument: %s") % arg.to_string() << std::endl; +                is_valid = false; +            } +            args.push_back(arg); +        } +        if (not is_valid) { +            throw uhd::runtime_error(str( +                    boost::format("Found invalid arguments for block %s.") +                    % get_name() +            )); +        } +        return args; +    } + +    registers_t get_settings_registers() +    { +        return _get_regs("setreg"); +    } + +    registers_t get_readback_registers() +    { +        return _get_regs("readback"); +    } + +    registers_t _get_regs(const std::string ®_type) +    { +        registers_t registers; +        pt::ptree def; +        BOOST_FOREACH(pt::ptree::value_type &v, _pt.get_child("nocblock.registers", def)) { +            if (v.first != reg_type) continue; +            registers[v.second.get<std::string>("name")] = +                boost::lexical_cast<size_t>(v.second.get<size_t>("address")); +        } +        return registers; +    } + + +private: + +    //! Tells us if is this for a NoC block, or a component. +    const xml_repr_t _type; +    //! The NoC-ID as reported (there may be several valid NoC IDs, this is the one used) +    const boost::uint64_t _noc_id; + +    //! This is a boost property tree, not the same as +    // our property tree. +    pt::ptree _pt; + +}; + +blockdef::sptr blockdef::make_from_noc_id(boost::uint64_t noc_id) +{ +    std::vector<fs::path> paths = blockdef_xml_impl::get_xml_paths(); +    // Iterate over all paths +    BOOST_FOREACH(const fs::path &base_path, paths) { +        fs::path this_path = base_path / XML_BLOCKS_SUBDIR; +        if (not fs::exists(this_path) or not fs::is_directory(this_path)) { +            continue; +        } +        // Iterate over all .xml files +        fs::directory_iterator end_itr; +        for (fs::directory_iterator i(this_path); i != end_itr; ++i) { +            if (not fs::exists(*i) or fs::is_directory(*i) or fs::is_empty(*i)) { +                continue; +            } +            if (i->path().filename().extension() != XML_EXTENSION) { +                continue; +            } +            if (blockdef_xml_impl::has_noc_id(noc_id, i->path())) { +                return blockdef::sptr(new blockdef_xml_impl(i->path(), noc_id)); +            } +        } +    } + +    return blockdef::sptr(); +} +// vim: sw=4 et: diff --git a/host/lib/rfnoc/ctrl_iface.cpp b/host/lib/rfnoc/ctrl_iface.cpp new file mode 100644 index 000000000..83c3a2626 --- /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 response messages +    struct resp_buff_type +    { +        boost::uint32_t data[8]; +    }; + +    /******************************************************************* +     * Primary control and interaction private methods +     ******************************************************************/ +    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("Block 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 << "] Block 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("Block 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..64c6f6abe --- /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..843cdea34 --- /dev/null +++ b/host/lib/rfnoc/legacy_compat.cpp @@ -0,0 +1,720 @@ +// +// 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")), +        _rx_spp(get_block_ctrl<radio_ctrl>(0, RADIO_BLOCK_NAME, 0)->get_arg<int>("spp")), +        _tx_spp(_rx_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(); +        if (_tree->exists("/mboards/0/mtu/send")) { +            _tx_spp = (_tree->access<size_t>("/mboards/0/mtu/send").get() - MAX_BYTES_PER_HEADER) / BYTES_PER_SAMPLE; +        } +        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 +    ) { +        // If the user provides spp, that value is always applied. If it's +        // different from what we thought it was, we need to update the blocks. +        // If it's not provided, we provide our own spp value. +        const size_t args_spp = args.args.cast<size_t>("spp", 0); +        if (dir == uhd::RX_DIRECTION) { +            if (args.args.has_key("spp") and args_spp != _rx_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); +                    } +                } +                _rx_spp = args_spp; +                // TODO: Update flow control on the blocks +            } else { +                args.args["spp"] = str(boost::format("%d") % _rx_spp); +            } +        } else { +            if (args.args.has_key("spp") and args_spp != _tx_spp) { +                _tx_spp = args_spp; +                // TODO: Update flow control on the blocks +            } else { +                args.args["spp"] = str(boost::format("%d") % _tx_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 != _rx_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 % _rx_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"); +        const size_t rx_bpp = _rx_spp * BYTES_PER_SAMPLE + MAX_BYTES_PER_HEADER; +        const size_t tx_bpp = _tx_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, +                            tx_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, +                                tx_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, +                                tx_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, +                            rx_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 _rx_spp; +    size_t _tx_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..1cc7a2472 --- /dev/null +++ b/host/lib/rfnoc/radio_ctrl_impl.cpp @@ -0,0 +1,368 @@ +// +// 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; +    if (not _is_streamer_active(uhd::RX_DIRECTION, chan)) { +        UHD_RFNOC_BLOCK_TRACE() << "radio_ctrl_impl::issue_stream_cmd() called on inactive channel. Skipping." << std::endl; +        return; +    } +    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; +    }; + +}; + | 
