diff options
author | Martin Braun <martin.braun@ettus.com> | 2016-08-01 18:17:41 -0700 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2016-08-09 12:42:52 -0700 |
commit | 3bf4b000f7d9a7f4af82c21753556ede7e8df6e3 (patch) | |
tree | 2228d7eb58c4d83d91192cb9b6a908e4e49f6317 /host | |
parent | c5b076173e2d866f3ee99c113a37183c5ec20f0b (diff) | |
download | uhd-3bf4b000f7d9a7f4af82c21753556ede7e8df6e3.tar.gz uhd-3bf4b000f7d9a7f4af82c21753556ede7e8df6e3.tar.bz2 uhd-3bf4b000f7d9a7f4af82c21753556ede7e8df6e3.zip |
Merging RFNoC support for X310
Diffstat (limited to 'host')
136 files changed, 17241 insertions, 1496 deletions
diff --git a/host/CMakeLists.txt b/host/CMakeLists.txt index 558597729..6660b3bdc 100644 --- a/host/CMakeLists.txt +++ b/host/CMakeLists.txt @@ -282,6 +282,8 @@ MESSAGE(STATUS "Boost libraries: ${Boost_LIBRARIES}") ######################################################################## # Additional settings for build environment ######################################################################## +# Note: RFNoC never gets fully disabled, but the public APIs do +SET(ENABLE_RFNOC OFF CACHE BOOL "Export RFNoC includes and symbols") INCLUDE(UHDGlobalDefs) ######################################################################## @@ -329,8 +331,8 @@ UHD_INSTALL(FILES #{{{IMG_SECTION # This section is written automatically by /images/create_imgs_package.py # Any manual changes in here will be overwritten. -SET(UHD_IMAGES_MD5SUM "d82c4b8bb779c1d5a810f0204815b84d") -SET(UHD_IMAGES_DOWNLOAD_SRC "uhd-images_003.010.git-197-g053111dc.zip") +SET(UHD_IMAGES_MD5SUM "071d51a666ddc98d27c5f1c8d47c474e") +SET(UHD_IMAGES_DOWNLOAD_SRC "uhd-images_003.010.000.master-310-304-g47551428.zip") #}}} ######################################################################## @@ -360,8 +362,6 @@ ENDIF(EXISTS "${FPGA_SUBMODULE_DIR}/docs/fpga.md") ######################################################################## # Add the subdirectories ######################################################################## -ADD_SUBDIRECTORY(docs) - IF(ENABLE_LIBUHD) ADD_SUBDIRECTORY(lib) ENDIF(ENABLE_LIBUHD) @@ -380,6 +380,8 @@ IF(ENABLE_UTILS) ADD_SUBDIRECTORY(utils) ENDIF(ENABLE_UTILS) +ADD_SUBDIRECTORY(docs) + ######################################################################## # Create Pkg Config File ######################################################################## diff --git a/host/cmake/Modules/UHDConfig.cmake.in b/host/cmake/Modules/UHDConfig.cmake.in index 78f01706f..bbe947dc1 100644 --- a/host/cmake/Modules/UHDConfig.cmake.in +++ b/host/cmake/Modules/UHDConfig.cmake.in @@ -36,6 +36,7 @@ set(ENV{UHD_CONFIG_USED} TRUE) # set default values SET(UHD_FOUND TRUE) +SET(UHD_RFNOC_FOUND TRUE) SET(UHD_INCLUDE_HINTS) SET(UHD_LIBDIR_HINTS) SET(UHD_DIR $ENV{UHD_DIR}) diff --git a/host/cmake/Modules/UHDGlobalDefs.cmake b/host/cmake/Modules/UHDGlobalDefs.cmake index 48d0c34cf..167861402 100644 --- a/host/cmake/Modules/UHDGlobalDefs.cmake +++ b/host/cmake/Modules/UHDGlobalDefs.cmake @@ -30,5 +30,10 @@ ELSE() ENDIF(UHD_VERSION_DEVEL) ADD_DEFINITIONS(-DUHD_VERSION=${UHD_VERSION_ADDED}) +## RFNoC +IF(ENABLE_RFNOC) + ADD_DEFINITIONS(-DUHD_RFNOC_ENABLED) +ENDIF(ENABLE_RFNOC) + ## make sure the code knows about config.h ADD_DEFINITIONS(-DHAVE_CONFIG_H) diff --git a/host/cmake/Modules/UHDUnitTest.cmake b/host/cmake/Modules/UHDUnitTest.cmake index f3e848906..b543a4d1c 100644 --- a/host/cmake/Modules/UHDUnitTest.cmake +++ b/host/cmake/Modules/UHDUnitTest.cmake @@ -60,7 +60,7 @@ function(UHD_ADD_TEST test_name) #replace list separator with the path separator string(REPLACE ";" ":" libpath "${libpath}") - list(APPEND environs "PATH=${binpath}" "${LD_PATH_VAR}=${libpath}") + list(APPEND environs "PATH=${binpath}" "${LD_PATH_VAR}=${libpath}" "UHD_RFNOC_DIR=${CMAKE_SOURCE_DIR}/include/uhd/rfnoc") #generate a bat file that sets the environment and runs the test if (CMAKE_CROSSCOMPILING) @@ -92,7 +92,7 @@ function(UHD_ADD_TEST test_name) #replace list separator with the path separator (escaped) string(REPLACE ";" "\\;" libpath "${libpath}") - list(APPEND environs "PATH=${libpath}") + list(APPEND environs "PATH=${libpath}" "UHD_RFNOC_DIR=${CMAKE_SOURCE_DIR}/include/uhd/rfnoc") #generate a bat file that sets the environment and runs the test set(bat_file ${CMAKE_CURRENT_BINARY_DIR}/${test_name}_test.bat) diff --git a/host/include/uhd/CMakeLists.txt b/host/include/uhd/CMakeLists.txt index e55141549..12709fc0a 100644 --- a/host/include/uhd/CMakeLists.txt +++ b/host/include/uhd/CMakeLists.txt @@ -15,6 +15,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # +ADD_SUBDIRECTORY(rfnoc) ADD_SUBDIRECTORY(transport) ADD_SUBDIRECTORY(types) ADD_SUBDIRECTORY(usrp) @@ -32,6 +33,7 @@ UHD_INSTALL(FILES convert.hpp deprecated.hpp device.hpp + device3.hpp device_deprecated.ipp exception.hpp property_tree.ipp diff --git a/host/include/uhd/config.hpp b/host/include/uhd/config.hpp index daa8a9066..cef7d3bb4 100644 --- a/host/include/uhd/config.hpp +++ b/host/include/uhd/config.hpp @@ -36,7 +36,7 @@ # pragma warning(disable: 4275) // non dll-interface class ... used as base for dll-interface class ... //# pragma warning(disable: 4267) // 'var' : conversion from 'size_t' to 'type', possible loss of data //# pragma warning(disable: 4511) // 'class' : copy constructor could not be generated -//# pragma warning(disable: 4250) // 'class' : inherits 'method' via dominance +# pragma warning(disable: 4250) // 'class' : inherits 'method' via dominance # pragma warning(disable: 4200) // nonstandard extension used : zero-sized array in struct/union // define logical operators @@ -97,6 +97,12 @@ typedef ptrdiff_t ssize_t; #else #define UHD_API UHD_IMPORT #endif // UHD_DLL_EXPORTS +#ifdef UHD_RFNOC_ENABLED + #define UHD_RFNOC_API UHD_API +#else + #define UHD_RFNOC_API +#endif // UHD_RFNOC_ENABLED + // Platform defines for conditional parts of headers: // Taken from boost/config/select_platform_config.hpp, diff --git a/host/include/uhd/device3.hpp b/host/include/uhd/device3.hpp new file mode 100644 index 000000000..da23bb263 --- /dev/null +++ b/host/include/uhd/device3.hpp @@ -0,0 +1,146 @@ +// +// 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_UHD_DEVICE3_HPP +#define INCLUDED_UHD_DEVICE3_HPP + +#include <uhd/device.hpp> +#include <uhd/rfnoc/graph.hpp> +#include <uhd/rfnoc/block_ctrl_base.hpp> +#include <boost/units/detail/utility.hpp> +#include <vector> + +namespace uhd { + +/*! + * \brief Extends uhd::device for third-generation USRP devices. + * + * Generation-3 devices are characterized by the following traits: + * - They support RFNoC (RF Network-on-Chip). + * - Data transport uses the compressed VITA (CVITA/CHDR) data format. + */ +class UHD_API device3 : public uhd::device { + + public: + typedef boost::shared_ptr<device3> sptr; + + //! Same as uhd::device::make(), but will fail if not actually a device3 + static sptr make(const device_addr_t &hint, const size_t which = 0); + + virtual rfnoc::graph::sptr create_graph(const std::string &name="") = 0; + + /*! Reset blocks after a stream. + * + * TODO write docs + */ + void clear(); + + /*! \brief Checks if an RFNoC block exists on the device. + * + * \param block_id Canonical block name (e.g. "0/FFT_1"). + * \return true if a block with the specified id exists + */ + bool has_block(const rfnoc::block_id_t &block_id) const; + + /*! Same as has_block(), but with a type check. + * + * \return true if a block of type T with the specified id exists + */ + template <typename T> + bool has_block(const rfnoc::block_id_t &block_id) const + { + if (has_block(block_id)) { + return bool(boost::dynamic_pointer_cast<T>(get_block_ctrl(block_id))); + } else { + return false; + } + } + + /*! \brief Returns a block controller class for an RFNoC block. + * + * If the given block ID is not valid (i.e. such a block does not exist + * on this device), it will throw a uhd::lookup_error. + * + * \param block_id Canonical block name (e.g. "0/FFT_1"). + */ + rfnoc::block_ctrl_base::sptr get_block_ctrl(const rfnoc::block_id_t &block_id) const; + + /*! Same as get_block_ctrl(), but with a type cast. + * + * If you have a block controller class that is derived from block_ctrl_base, + * use this function to access its specific methods. + * If the given block ID is not valid (i.e. such a block does not exist + * on this device) or if the type does not match, it will throw a uhd::lookup_error. + * + * \code{.cpp} + * // Assume DEV is a device3::sptr + * uhd::rfnoc::my_block_ctrl::sptr block_controller = get_block_ctrl<my_block_ctrl>("0/MyBlock_0"); + * block_controller->my_own_block_method(); + * \endcode + */ + template <typename T> + boost::shared_ptr<T> get_block_ctrl(const rfnoc::block_id_t &block_id) const + { + boost::shared_ptr<T> blk = boost::dynamic_pointer_cast<T>(get_block_ctrl(block_id)); + if (blk) { + return blk; + } else { + throw uhd::lookup_error(str(boost::format("This device does not have a block of type %s with ID: %s") + % boost::units::detail::demangle(typeid(T).name()) + % block_id.to_string())); + } + } + + /*! Returns the block ids of all blocks that match the specified hint + * Uses block_ctrl_base::match() internally. + * If no matching block is found, it returns an empty vector. + * + * To access specialized block controller classes (i.e. derived from block_ctrl_base), + * use the templated version of this function, e.g. + * \code{.cpp} + * // Assume DEV is a device3::sptr + * null_block_ctrl::sptr null_block = DEV->find_blocks<null_block_ctrl>("NullSrcSink"); + * \endcode + */ + std::vector<rfnoc::block_id_t> find_blocks(const std::string &block_id_hint) const; + + /*! Type-cast version of find_blocks(). + */ + template <typename T> + std::vector<rfnoc::block_id_t> find_blocks(const std::string &block_id_hint) const + { + std::vector<rfnoc::block_id_t> all_block_ids = find_blocks(block_id_hint); + std::vector<rfnoc::block_id_t> filt_block_ids; + for (size_t i = 0; i < all_block_ids.size(); i++) { + if (has_block<T>(all_block_ids[i])) { + filt_block_ids.push_back(all_block_ids[i]); + } + } + return filt_block_ids; + } + + protected: + //! List of *all* RFNoC blocks available on this device. + // It is the responsibility of the deriving class to make + // sure this gets correctly populated. + std::vector< rfnoc::block_ctrl_base::sptr > _rfnoc_block_ctrl; +}; + +} //namespace uhd + +#endif /* INCLUDED_UHD_DEVICE3_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/CMakeLists.txt b/host/include/uhd/rfnoc/CMakeLists.txt new file mode 100644 index 000000000..15a92ae8f --- /dev/null +++ b/host/include/uhd/rfnoc/CMakeLists.txt @@ -0,0 +1,49 @@ +# +# 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/>. +# + +IF(ENABLE_RFNOC) + UHD_INSTALL(FILES + # Infrastructure + block_ctrl_base.hpp + block_ctrl.hpp + blockdef.hpp + block_id.hpp + constants.hpp + graph.hpp + node_ctrl_base.hpp + node_ctrl_base.ipp + rate_node_ctrl.hpp + scalar_node_ctrl.hpp + sink_block_ctrl_base.hpp + sink_node_ctrl.hpp + source_block_ctrl_base.hpp + source_node_ctrl.hpp + stream_sig.hpp + terminator_node_ctrl.hpp + tick_node_ctrl.hpp + # Block controllers + ddc_block_ctrl.hpp + duc_block_ctrl.hpp + radio_ctrl.hpp + DESTINATION ${INCLUDE_DIR}/uhd/rfnoc + COMPONENT headers + ) +ENDIF(ENABLE_RFNOC) + +ADD_SUBDIRECTORY(blocks) +#ADD_SUBDIRECTORY(components) + diff --git a/host/include/uhd/rfnoc/block_ctrl.hpp b/host/include/uhd/rfnoc/block_ctrl.hpp new file mode 100644 index 000000000..b32192d86 --- /dev/null +++ b/host/include/uhd/rfnoc/block_ctrl.hpp @@ -0,0 +1,47 @@ +// +// 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_BLOCK_CTRL_HPP +#define INCLUDED_LIBUHD_RFNOC_BLOCK_CTRL_HPP + +#include <uhd/rfnoc/source_block_ctrl_base.hpp> +#include <uhd/rfnoc/sink_block_ctrl_base.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief This is the default implementation of a block_ctrl_base. + * + * For most blocks, this will be a sufficient implementation. All registers + * can be set by sr_write(). The default behaviour of functions is documented + * in uhd::rfnoc::block_ctrl_base. + */ +class UHD_RFNOC_API block_ctrl : public source_block_ctrl_base, public sink_block_ctrl_base +{ +public: + // Required macro in RFNoC block classes + UHD_RFNOC_BLOCK_OBJECT(block_ctrl) + + // Nothing else here -- all function definitions are in block_ctrl_base, + // source_block_ctrl_base and sink_block_ctrl_base + +}; /* class block_ctrl*/ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_BLOCK_CTRL_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/block_ctrl_base.hpp b/host/include/uhd/rfnoc/block_ctrl_base.hpp new file mode 100644 index 000000000..0212fc62c --- /dev/null +++ b/host/include/uhd/rfnoc/block_ctrl_base.hpp @@ -0,0 +1,424 @@ +// +// 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_BLOCK_CTRL_BASE_HPP +#define INCLUDED_LIBUHD_BLOCK_CTRL_BASE_HPP + +#include <uhd/property_tree.hpp> +#include <uhd/stream.hpp> +#include <uhd/types/sid.hpp> +#include <uhd/types/stream_cmd.hpp> +#include <uhd/types/wb_iface.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/rfnoc/block_id.hpp> +#include <uhd/rfnoc/stream_sig.hpp> +#include <uhd/rfnoc/blockdef.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <boost/cstdint.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/lexical_cast.hpp> +#include <stdint.h> + +namespace uhd { + namespace rfnoc { + namespace nocscript { + // Forward declaration + class block_iface; + } + + +// TODO: Move this out of public section +struct make_args_t +{ + make_args_t(const std::string &key="") : + device_index(0), + is_big_endian(true), + block_name(""), + block_key(key) + {} + + //! A valid interface that allows us to do peeks and pokes + std::map<size_t, uhd::wb_iface::sptr> ctrl_ifaces; + //! This block's base address (address of block port 0) + uint32_t base_address; + //! The device index (or motherboard index). + size_t device_index; + //! A property tree for this motherboard. Example: If the root a device's + // property tree is /mboards/0, pass a subtree starting at /mboards/0 + // to the constructor. + uhd::property_tree::sptr tree; + bool is_big_endian; + //! The name of the block as it will be addressed + std::string block_name; + //! The key of the block, i.e. how it was registered + std::string block_key; +}; + +//! This macro must be put in the public section of an RFNoC +// block class +#define UHD_RFNOC_BLOCK_OBJECT(class_name) \ + typedef boost::shared_ptr< class_name > sptr; + +//! Shorthand for block constructor +#define UHD_RFNOC_BLOCK_CONSTRUCTOR(CLASS_NAME) \ + CLASS_NAME##_impl( \ + const make_args_t &make_args \ + ) : block_ctrl_base(make_args) + +//! This macro must be placed inside a block implementation file +// after the class definition +#define UHD_RFNOC_BLOCK_REGISTER(CLASS_NAME, BLOCK_NAME) \ + block_ctrl_base::sptr CLASS_NAME##_make( \ + const make_args_t &make_args \ + ) { \ + return block_ctrl_base::sptr(new CLASS_NAME##_impl(make_args)); \ + } \ + UHD_STATIC_BLOCK(register_rfnoc_##CLASS_NAME) \ + { \ + uhd::rfnoc::block_ctrl_base::register_block(&CLASS_NAME##_make, BLOCK_NAME); \ + } + +/*! \brief Base class for all RFNoC block controller objects. + * + * For RFNoC, block controller objects must be derived from + * uhd::rfnoc::block_ctrl_base. This class provides all functions + * that a block *must* provide. Typically, you would not derive + * a block controller class directly from block_ctrl_base, but + * from a class such as uhd::usrp::rfnoc::source_block_ctrl_base or + * uhd::usrp::rfnoc::sink_block_ctrl_base which extends its functionality. + */ +class UHD_RFNOC_API block_ctrl_base; +class block_ctrl_base : virtual public node_ctrl_base +{ +public: + /*********************************************************************** + * Types + **********************************************************************/ + typedef boost::shared_ptr<block_ctrl_base> sptr; + typedef boost::function<sptr(const make_args_t &)> make_t; + + /*********************************************************************** + * Factory functions + **********************************************************************/ + + /*! Register a block controller class into the discovery and factory system. + * + * Note: It is not recommended to call this function directly. + * Rather, use the UHD_RFNOC_BLOCK_REGISTER() macro, which will set up + * the discovery and factory system correctly. + * + * \param make A factory function that makes a block controller object + * \param name A unique block name, e.g. 'FFT'. If a block has this block name, + * it will use \p make to generate the block controller class. + */ + static void register_block(const make_t &make, const std::string &name); + + /*! + * \brief Create a block controller class given a NoC-ID or a block name. + * + * If a block name is given in \p make_args, it will directly try to + * generate a block of this type. If no block name is given, it will + * look up a name using the NoC-ID and use that. + * If it can't find a suitable block controller class, it will generate + * a uhd::rfnoc::block_ctrl. However, if a block name *is* specified, + * it will throw a uhd::runtime_error if this block type is not registered. + * + * \param make_args Valid make args. + * \param noc_id The 64-Bit NoC-ID. + * \return a shared pointer to a new device instance + */ + static sptr make(const make_args_t &make_args, boost::uint64_t noc_id = ~0); + + /*********************************************************************** + * Block Communication and Control + * + * These functions do not require communication with the FPGA. + **********************************************************************/ + + /*! Returns the 16-Bit address for this block. + */ + boost::uint32_t get_address(size_t block_port=0); + + /*! Returns the unique block ID for this block (e.g. "0/FFT_1"). + */ + block_id_t get_block_id() const { return _block_id; }; + + /*! Shorthand for get_block_id().to_string() + */ + std::string unique_id() const { return _block_id.to_string(); }; + + /*********************************************************************** + * FPGA control & communication + **********************************************************************/ + + /*! Returns a list of valid ports that can be used for sr_write(), sr_read() etc. + */ + std::vector<size_t> get_ctrl_ports() const; + + /*! Allows setting one register on the settings bus. + * + * Note: There is no address translation ("memory mapping") necessary. + * Register 0 is 0, 1 is 1 etc. + * + * \param reg The settings register to write to. + * \param data New value of this register. + */ + void sr_write(const boost::uint32_t reg, const boost::uint32_t data, const size_t port = 0); + + /*! Allows setting one register on the settings bus. + * + * Like sr_write(), but takes a register name as argument. + * + * \param reg The settings register to write to. + * \param data New value of this register. + * \throw uhd::key_error if \p reg is not a valid register name + * + */ + void sr_write(const std::string ®, const boost::uint32_t data, const size_t port = 0); + + /*! Allows reading one register on the settings bus (64-Bit version). + * + * \param reg The settings register to be read. + * + * Returns the readback value. + */ + boost::uint64_t sr_read64(const settingsbus_reg_t reg, const size_t port = 0); + + /*! Allows reading one register on the settings bus (32-Bit version). + * + * \param reg The settings register to be read. + * + * Returns the readback value. + */ + boost::uint32_t sr_read32(const settingsbus_reg_t reg, const size_t port = 0); + + /*! Allows reading one user-defined register (64-Bit version). + * + * This is a shorthand for setting the requested address + * through sr_write() and then reading SR_READBACK_REG_USER + * with sr_read64(). + * + * \param addr The user register address. + * \returns the readback value. + */ + boost::uint64_t user_reg_read64(const boost::uint32_t addr, const size_t port = 0); + + /*! Allows reading one user-defined register (64-Bit version). + * + * Identical to user_reg_read64(), but takes a register name + * instead of a numeric address. The register name must be + * defined in the block definition file. + * + * \param addr The user register address. + * \returns the readback value. + * \throws uhd::key_error if \p reg is not a valid register name + */ + boost::uint64_t user_reg_read64(const std::string ®, const size_t port = 0); + + /*! Allows reading one user-defined register (32-Bit version). + * + * This is a shorthand for setting the requested address + * through sr_write() and then reading SR_READBACK_REG_USER + * with sr_read32(). + * + * \param addr The user register address. + * \returns the readback value. + */ + boost::uint32_t user_reg_read32(const boost::uint32_t addr, const size_t port = 0); + + /*! Allows reading one user-defined register (32-Bit version). + * + * Identical to user_reg_read32(), but takes a register name + * instead of a numeric address. The register name must be + * defined in the block definition file. + * + * \param reg The user register name. + * \returns the readback value. + * \throws uhd::key_error if \p reg is not a valid register name + */ + boost::uint32_t user_reg_read32(const std::string ®, const size_t port = 0); + + + /*! Sets a command time for all future command packets. + * + * \throws uhd::assertion_error if the underlying interface does not + * actually support timing. + */ + void set_command_time(const time_spec_t &time_spec, const size_t port = ANY_PORT); + + /*! Returns the current command time for all future command packets. + * + * \returns the command time as a time_spec_t. + */ + time_spec_t get_command_time(const size_t port = 0); + + /*! Sets a tick rate for the command timebase. + * + * \param the tick rate in Hz + */ + void set_command_tick_rate(const double tick_rate, const size_t port = ANY_PORT); + + /*! Resets the command time. + * Any command packet after this call will no longer have a time associated + * with it. + * + * \throws uhd::assertion_error if the underlying interface does not + * actually support timing. + */ + void clear_command_time(const size_t port); + + /*! Reset block after streaming operation. + * + * This does the following: + * - Reset flow control (sequence numbers etc.) + * - Clear the list of connected blocks + * + * Internally, rfnoc::node_ctrl_base::clear() and _clear() are called + * (in that order). + * + * Between runs, it can be necessary to call this method, + * or blocks might be left hanging in a streaming state, and can get + * confused when a new application starts. + * + * For custom behaviour, overwrite _clear(). If you do so, you must take + * take care of resetting flow control yourself. + * + * TODO: Find better name (it disconnects, clears FC...) + */ + void clear(const size_t port = 0/* reserved, currently not used */); + + /*********************************************************************** + * Argument handling + **********************************************************************/ + /*! Set multiple block args. Calls set_arg() for all individual items. + * + * Note that this function will silently ignore any keys in \p args that + * aren't already registered as block arguments. + */ + void set_args(const uhd::device_addr_t &args, const size_t port = 0); + + //! Set a specific block argument. \p val is converted to the corresponding + // data type using by looking up its type in the block definition. + void set_arg(const std::string &key, const std::string &val, const size_t port = 0); + + //! Direct access to set a block argument. + template <typename T> + void set_arg(const std::string &key, const T &val, const size_t port = 0) { + _tree->access<T>(get_arg_path(key, port) / "value").set(val); + } + + //! Return all block arguments as a device_addr_t. + uhd::device_addr_t get_args(const size_t port = 0) const; + + //! Return a single block argument in string format. + std::string get_arg(const std::string &key, const size_t port = 0) const; + + //! Direct access to get a block argument. + template <typename T> + T get_arg(const std::string &key, const size_t port = 0) const { + return _tree->access<T>(get_arg_path(key, port) / "value").get(); + } + + std::string get_arg_type(const std::string &key, const size_t port = 0) const; + +protected: + /*********************************************************************** + * Structors + **********************************************************************/ + block_ctrl_base(void) {}; // To allow pure virtual (interface) sub-classes + virtual ~block_ctrl_base(); + + /*! Constructor. This is only called from the internal block factory! + * + * \param make_args All arguments to this constructor are passed in this object. + * Its details are subject to change. Use the UHD_RFNOC_BLOCK_CONSTRUCTOR() + * macro to set up your block's constructor in a portable fashion. + */ + block_ctrl_base( + const make_args_t &make_args + ); + + /*********************************************************************** + * Helpers + **********************************************************************/ + stream_sig_t _resolve_port_def(const blockdef::port_t &port_def) const; + + //! Return the property tree path to a block argument \key on \p port + uhd::fs_path get_arg_path(const std::string &key, size_t port = 0) const { + return _root_path / "args" / port / key; + }; + + //! Get a control interface object for block port \p block_port + wb_iface::sptr get_ctrl_iface(const size_t block_port); + + + /*********************************************************************** + * Hooks & Derivables + **********************************************************************/ + + //! Override this function if your block does something else + // than reset register SR_CLEAR_TX_FC. + virtual void _clear(const size_t port = 0); + + /*********************************************************************** + * Protected members + **********************************************************************/ + + //! Property sub-tree + uhd::property_tree::sptr _tree; + + //! Root node of this block's properties + uhd::fs_path _root_path; + + //! Endianness of underlying transport (for data transport) + bool _transport_is_big_endian; + + //! Block definition (stores info about the block such as ports) + blockdef::sptr _block_def; + +private: + //! Helper function to initialize the port definition nodes in the prop tree + void _init_port_defs( + const std::string &direction, + blockdef::ports_t ports, + const size_t first_port_index=0 + ); + + //! Helper function to initialize the block args (used by ctor only) + void _init_block_args(); + + /*********************************************************************** + * Private members + **********************************************************************/ + //! Objects to actually send and receive the commands + std::map<size_t, wb_iface::sptr> _ctrl_ifaces; + + //! The base address of this block (the address of block port 0) + uint32_t _base_address; + + //! The (unique) block ID. + block_id_t _block_id; + + //! Interface to NocScript parser + boost::shared_ptr<nocscript::block_iface> _nocscript_iface; +}; /* class block_ctrl_base */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_BLOCK_CTRL_BASE_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/block_id.hpp b/host/include/uhd/rfnoc/block_id.hpp new file mode 100644 index 000000000..a8f2aec5a --- /dev/null +++ b/host/include/uhd/rfnoc/block_id.hpp @@ -0,0 +1,211 @@ +// 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_UHD_TYPES_BLOCK_ID_HPP +#define INCLUDED_UHD_TYPES_BLOCK_ID_HPP + +#include <uhd/config.hpp> +#include <boost/cstdint.hpp> +#include <boost/shared_ptr.hpp> +#include <iostream> +#include <string> + +namespace uhd { + struct fs_path; + + namespace rfnoc { + + /*! + * Identifies an RFNoC block. + * + * An RFNoC block ID is a string such as: 0/FFT_1 + * + * The rules for formatting such a string are: + * + * DEVICE/BLOCKNAME_COUNTER + * + * DEVICE: Identifies the device (usually the motherboard index) + * BLOCKNAME: A name given to this block + * COUNTER: If is are more than one block with a BLOCKNAME, this counts up. + * + * So, 0/FFT_1 means we're addressing the second block called FFT + * on the first device. + * + * This class can represent these block IDs. + */ + class UHD_RFNOC_API block_id_t + { + public: + block_id_t(); + block_id_t(const std::string &block_str); + //! \param device_no Device number + //! \param block_name Block name + //! \param block_ctr Which block of this type is this on this device? + block_id_t(const size_t device_no, const std::string &block_name, const size_t block_ctr=0); + + //! Return a string like this: "0/FFT_1" (includes all components, if set) + std::string to_string() const; + + //! Check if a given string is valid as a block name. + // + // Note: This only applies to the block *name*, not the entire block ID. + // Examples: + // * is_valid_blockname("FFT") will return true. + // * is_valid_blockname("FIR_Filter") will return false, because an underscore + // is not allowed in a block name. + // + // Internally, this matches the string with uhd::rfnoc::VALID_BLOCKNAME_REGEX. + static bool is_valid_blockname(const std::string &block_name); + + //! Check if a given string is valid as a block ID. + // + // Note: This does necessary require a complete complete ID. If this returns + // true, then it is a valid input for block_id_t::match(). + // + // Examples: + // * is_valid_block_id("FFT") will return true. + // * is_valid_block_id("0/Filter_1") will return true. + // * is_valid_block_id("0/Filter_Foo") will return false. + // + // Internally, this matches the string with uhd::rfnoc::VALID_BLOCKID_REGEX. + static bool is_valid_block_id(const std::string &block_id); + + //! Check if block_str matches this block. + // + // A match is a less strict version of equality. + // Less specific block IDs will match more specific ones, + // e.g. "FFT" will match "0/FFT_1", "1/FFT_2", etc. + // "FFT_1" will only match the former, etc. + bool match(const std::string &block_str); + + // Getters + + //! Short for to_string() + std::string get() const { return to_string(); }; + + //! Like get(), but only returns the local part ("FFT_1") + std::string get_local() const; + + //! Returns the property tree root for this block (e.g. "/mboards/0/xbar/FFT_1/") + uhd::fs_path get_tree_root() const; + + //! Return device number + size_t get_device_no() const { return _device_no; }; + + //! Return block count + size_t get_block_count() const { return _block_ctr; }; + + //! Return block name + std::string get_block_name() const { return _block_name; }; + + // Setters + + //! Set from string such as "0/FFT_1", "FFT_0", ... + // Returns true if successful (i.e. if string valid) + bool set(const std::string &new_name); + + //! Sets from individual compontents, like calling set_device_no(), set_block_name() + // and set_block_count() one after another, only if \p block_name is invalid, stops + // and returns false before chaning anything + bool set(const size_t device_no, const std::string &block_name, const size_t block_ctr=0); + + //! Set the device number + void set_device_no(size_t device_no) { _device_no = device_no; }; + + //! Set the block name. Will return false if invalid block string. + bool set_block_name(const std::string &block_name); + + //! Set the block count. + void set_block_count(size_t count) { _block_ctr = count; }; + + // Overloaded operators + + //! Assignment: Works like set(std::string) + block_id_t operator = (const std::string &new_name) { + set(new_name); + return *this; + } + + bool operator == (const block_id_t &block_id) const { + return (_device_no == block_id.get_device_no()) + and (_block_name == block_id.get_block_name()) + and (_block_ctr == block_id.get_block_count()); + } + + bool operator != (const block_id_t &block_id) const { + return not (*this == block_id); + } + + bool operator < (const block_id_t &block_id) const { + return ( + _device_no < block_id.get_device_no() + or (_device_no == block_id.get_device_no() and _block_name < block_id.get_block_name()) + or (_device_no == block_id.get_device_no() and _block_name == block_id.get_block_name() and _block_ctr < block_id.get_block_count()) + ); + } + + bool operator > (const block_id_t &block_id) const { + return ( + _device_no > block_id.get_device_no() + or (_device_no == block_id.get_device_no() and _block_name > block_id.get_block_name()) + or (_device_no == block_id.get_device_no() and _block_name == block_id.get_block_name() and _block_ctr > block_id.get_block_count()) + ); + } + + //! Check if a string matches the entire block ID (not like match()) + bool operator == (const std::string &block_id_str) const { + return get() == block_id_str; + } + + //! Check if a string matches the entire block ID (not like match()) + bool operator == (const char *block_id_str) const { + std::string comp = std::string(block_id_str); + return *this == comp; + } + + //! Type-cast operator does the same as to_string() + operator std::string() const { + return to_string(); + } + + //! Increment the block count ("FFT_1" -> "FFT_2") + block_id_t operator++() { + _block_ctr++; + return *this; + } + + //! Increment the block count ("FFT_1" -> "FFT_2") + block_id_t operator++(int) { + _block_ctr++; + return *this; + } + + private: + size_t _device_no; + std::string _block_name; + size_t _block_ctr; + }; + + //! Shortcut for << block_id.to_string() + inline std::ostream& operator<< (std::ostream& out, block_id_t block_id) { + out << block_id.to_string(); + return out; + } + +}} //namespace uhd::rfnoc + +#endif /* INCLUDED_UHD_TYPES_BLOCK_ID_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/blockdef.hpp b/host/include/uhd/rfnoc/blockdef.hpp new file mode 100644 index 000000000..fc3505d3c --- /dev/null +++ b/host/include/uhd/rfnoc/blockdef.hpp @@ -0,0 +1,126 @@ +// +// 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/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_BLOCKDEF_HPP +#define INCLUDED_LIBUHD_RFNOC_BLOCKDEF_HPP + +#include <boost/cstdint.hpp> +#include <boost/enable_shared_from_this.hpp> +#include <uhd/config.hpp> +#include <uhd/types/device_addr.hpp> +#include <vector> +#include <set> + +namespace uhd { namespace rfnoc { + +/*! Reads and stores block definitions for blocks and components. + */ +class UHD_RFNOC_API blockdef : public boost::enable_shared_from_this<blockdef> +{ +public: + typedef boost::shared_ptr<blockdef> sptr; + + //! Describes port options for a block definition. + // + // This is not the same as a uhd::rfnoc::stream_sig_t. This is used + // to describe which ports are defined in a block definition, and + // to describe what kind of connection is allowed for this port. + // + // All the keys listed in PORT_ARGS will be available in this class. + class port_t : public uhd::dict<std::string, std::string> { + public: + //! A list of args a port can have. + static const device_addr_t PORT_ARGS; + + port_t(); + + //! Checks if the value at \p key is a variable (e.g. '$fftlen') + bool is_variable(const std::string &key) const; + //! Checks if the value at \p key is a keyword (e.g. '%vlen') + bool is_keyword(const std::string &key) const; + //! Basic validity check of this port definition. Variables and + // keywords are not resolved. + bool is_valid() const; + //! Returns a string with the most important keys + std::string to_string() const; + }; + typedef std::vector<port_t> ports_t; + + //! Describes arguments in a block definition. + class arg_t : public uhd::dict<std::string, std::string> { + public: + //! A list of args an argument can have. + static const device_addr_t ARG_ARGS; + static const std::set<std::string> VALID_TYPES; + + arg_t(); + + //! Basic validity check of this argument definition. + bool is_valid() const; + //! Returns a string with the most important keys + std::string to_string() const; + + }; + typedef std::vector<arg_t> args_t; + + typedef uhd::dict<std::string, size_t> registers_t; + + /*! Create a block definition object for a NoC block given + * a NoC ID. This cannot be used for components. + * + * Note: If nothing is found, returns an + * empty sptr. Does not throw. + */ + static sptr make_from_noc_id(boost::uint64_t noc_id); + + //! Returns true if this represents a NoC block + virtual bool is_block() const = 0; + + //! Returns true if this represents a component + virtual bool is_component() const = 0; + + //! Returns block key (i.e. what is used for the registry) + virtual std::string get_key() const = 0; + + //! For blocks, returns the block name. For components, returns it's canonical name. + virtual std::string get_name() const = 0; + + //! Return the one NoC that is valid for this block + virtual boost::uint64_t noc_id() const = 0; + + virtual ports_t get_input_ports() = 0; + virtual ports_t get_output_ports() = 0; + + //! Returns the full list of port numbers used + virtual std::vector<size_t> get_all_port_numbers() = 0; + + //! Returns the args for this block. Checks if args are valid. + // + // \throws uhd::runtime_error if args are invalid. + virtual args_t get_args() = 0; + + //! Returns a list of settings registers by name. + virtual registers_t get_settings_registers() = 0; + + //! Returns a list of readback (user) registers by name. + virtual registers_t get_readback_registers() = 0; +}; + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_BLOCKDEF_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/blocks/CMakeLists.txt b/host/include/uhd/rfnoc/blocks/CMakeLists.txt new file mode 100644 index 000000000..341db3366 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/CMakeLists.txt @@ -0,0 +1,25 @@ +# +# 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/>. +# + +FILE(GLOB xml_files "*.xml") + +# We always need this, even when RFNoC is 'disabled' +UHD_INSTALL( + FILES ${xml_files} + DESTINATION ${PKG_DATA_DIR}/rfnoc/blocks + COMPONENT headers # TODO: Different component +) diff --git a/host/include/uhd/rfnoc/blocks/addsub.xml b/host/include/uhd/rfnoc/blocks/addsub.xml new file mode 100644 index 000000000..2412e5022 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/addsub.xml @@ -0,0 +1,50 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <!--The Adder & Subtractor takes inputs from Block Ports 0 & 1 and--> + <!--outputs the addition / subtraction of the values on Block Ports 0 & 1.--> + <!--- Block Port 0 + Block Port 1 => Block Port 0--> + <!--- Block Port 0 - Block Port 1 => Block Port 1--> + <name>Adder & Subtractor</name> + <blockname>AddSub</blockname> + <ids> + <id revision="0">ADD0</id> + </ids> + <!--Order matters. The first listed port is port 0, etc.--> + <ports> + <sink> + <name>in0</name> + <type>sc16</type> + <port>0</port> + </sink> + <sink> + <name>in1</name> + <type>sc16</type> + <port>1</port> + </sink> + <source> + <name>sum</name> + <type>sc16</type> + </source> + <source> + <name>diff</name> + <type>sc16</type> + </source> + </ports> + <!--<components>--> + <!--<component>--> + <!--<key revision="1">nocshell</key>--> + <!--</component>--> + <!--<component srbase="0">--> + <!--[>Will look for a component with this key:<]--> + <!--<key revision="1">componentname</key>--> + <!--</component>--> + <!--</components>--> + <!--<connection>--> + <!--<source port="0">nocshell</source>--> + <!--<sink port="0">componentname</sink>--> + <!--</connection>--> + <!--<connection>--> + <!--<source port="0">componentname</source>--> + <!--<sink port="0">nocshell</sink>--> + <!--</connection>--> +</nocblock> diff --git a/host/include/uhd/rfnoc/blocks/block.xml b/host/include/uhd/rfnoc/blocks/block.xml new file mode 100644 index 000000000..dfe616c45 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/block.xml @@ -0,0 +1,17 @@ +<!--Default XML file--> +<nocblock> + <name>Block</name> + <blockname>Block</blockname> + <ids> + <id revision="0">FFFFFFFFFFFFFFFF</id> + </ids> + <!--One input, one output. If this is used, better have all the info the C++ file.--> + <ports> + <sink> + <name>in</name> + </sink> + <source> + <name>out</name> + </source> + </ports> +</nocblock> diff --git a/host/include/uhd/rfnoc/blocks/ddc.xml b/host/include/uhd/rfnoc/blocks/ddc.xml new file mode 100644 index 000000000..a88616117 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/ddc.xml @@ -0,0 +1,154 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>Rx DSP (DDC/CORDIC)</name> + <blockname>DDC</blockname> + <key>DDC</key> + <!--There can be several of these:--> + <ids> + <id revision="0">DDC0</id> + </ids> + <!-- Registers --> + <registers> + <!-- AXI rate change block registers --> + <setreg> + <name>N</name> + <address>128</address> + </setreg> + <setreg> + <name>M</name> + <address>129</address> + </setreg> + <setreg> + <!-- 1 bit, enable clear user --> + <name>CONFIG</name> + <address>130</address> + </setreg> + <!-- DDC block registers --> + <setreg> + <!-- CORDIC phase increment word --> + <name>CORDIC_FREQ</name> + <address>132</address> + </setreg> + <setreg> + <!-- Scaling factor to compensate for gain through filters and CORDIC --> + <name>SCALE_IQ</name> + <address>133</address> + </setreg> + <setreg> + <!-- DDC control word, 10 bits total, 2 bits for Halfbands, 8 bits for CIC rate --> + <name>DECIM_WORD</name> + <address>134</address> + </setreg> + <setreg> + <!-- Real mode, swap IQ --> + <name>MODE</name> + <address>135</address> + </setreg> + <setreg> + <!-- Filter coefficients reload --> + <name>RELOAD</name> + <address>136</address> + </setreg> + </registers> + <!-- Args --> + <args> + <arg> + <name>freq</name> + <type>double</type> + <value>0.0</value> + <port>0</port> + <!--<action>--> + <!--SR_WRITE("CORDIC_FREQ", $cordic_freq)--> + <!--</action>--> + <!--FIXME Calculate this properly--> + </arg> + <arg> + <name>input_rate</name> + <type>double</type> + <value>1.0</value> + <port>0</port> + <check>GE($input_rate, 0.0)</check> + <check_message>The input rate must be a positive value (in Hz).</check_message> + </arg> + <arg> + <name>output_rate</name> + <type>double</type> + <value>1.0</value> + <port>0</port> + <check>GE($output_rate, 0.0)</check> + <check_message>The output rate must be a positive value (in Hz).</check_message> + </arg> + <arg> + <name>fullscale</name> + <type>double</type> + <value>1.0</value> + <port>0</port> + <check>GE($fullscale, 0.0)</check> + </arg> + <arg> + <name>scalar_correction</name> + <type>double</type> + <value>1.0</value> + <port>0</port> + </arg> + <arg> + <name>freq</name> + <type>double</type> + <value>0.0</value> + <port>1</port> + <!--<action>--> + <!--SR_WRITE("CORDIC_FREQ", $cordic_freq)--> + <!--</action>--> + <!--FIXME Calculate this properly--> + </arg> + <arg> + <name>input_rate</name> + <type>double</type> + <value>1.0</value> + <port>1</port> + <check>GE($input_rate, 0.0)</check> + <check_message>The input rate must be a positive value (in Hz).</check_message> + </arg> + <arg> + <name>output_rate</name> + <type>double</type> + <value>1.0</value> + <port>1</port> + <check>GE($output_rate, 0.0)</check> + <check_message>The output rate must be a positive value (in Hz).</check_message> + </arg> + <arg> + <name>fullscale</name> + <type>double</type> + <value>1.0</value> + <port>1</port> + <check>GE($fullscale, 0.0)</check> + </arg> + <arg> + <name>scalar_correction</name> + <type>double</type> + <value>1.0</value> + <port>1</port> + </arg> + </args> + <!--All the connections to the outside world are listed in 'ports':--> + <ports> + <sink> + <name>in0</name> + <type>sc16</type> + </sink> + <sink> + <name>in1</name> + <type>sc16</type> + </sink> + <source> + <name>out0</name> + <type>sc16</type> + </source> + <source> + <name>out1</name> + <type>sc16</type> + </source> + </ports> +</nocblock> + diff --git a/host/include/uhd/rfnoc/blocks/ddc_single.xml b/host/include/uhd/rfnoc/blocks/ddc_single.xml new file mode 100644 index 000000000..581487388 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/ddc_single.xml @@ -0,0 +1,117 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>Rx DSP (DDC/CORDIC)</name> + <blockname>DDC</blockname> + <key>DDC</key> + <!--There can be several of these:--> + <ids> + <id revision="0">DDC0000000000001</id> + </ids> + <!-- Registers --> + <registers> + <!-- AXI rate change block registers --> + <setreg> + <name>N</name> + <address>128</address> + </setreg> + <setreg> + <name>M</name> + <address>129</address> + </setreg> + <setreg> + <!-- 1 bit, enable clear user --> + <name>CONFIG</name> + <address>130</address> + </setreg> + <!-- DDC block registers --> + <setreg> + <!-- CORDIC phase increment word --> + <name>CORDIC_FREQ</name> + <address>132</address> + </setreg> + <setreg> + <!-- Scaling factor to compensate for gain through filters and CORDIC --> + <name>SCALE_IQ</name> + <address>133</address> + </setreg> + <setreg> + <!-- DDC control word, 10 bits total, 2 bits for Halfbands, 8 bits for CIC rate --> + <name>DECIM_WORD</name> + <address>134</address> + </setreg> + <setreg> + <!-- Real mode, swap IQ --> + <name>MODE</name> + <address>135</address> + </setreg> + <setreg> + <!-- Filter coefficients reload --> + <name>RELOAD</name> + <address>136</address> + </setreg> + </registers> + <!-- Args --> + <args> + <arg> + <name>freq</name> + <type>double</type> + <value>0.0</value> + <port>0</port> + <!--<action>--> + <!--SR_WRITE("CORDIC_FREQ", $cordic_freq)--> + <!--</action>--> + <!--FIXME Calculate this properly--> + </arg> + <arg> + <name>input_rate</name> + <type>double</type> + <value>1.0</value> + <port>0</port> + <check>GE($input_rate, 0.0)</check> + <check_message>The input rate must be a positive value (in Hz).</check_message> + </arg> + <arg> + <name>output_rate</name> + <type>double</type> + <value>1.0</value> + <port>0</port> + <check>GE($output_rate, 0.0)</check> + <check_message>The output rate must be a positive value (in Hz).</check_message> + </arg> + <arg> + <name>fullscale</name> + <type>double</type> + <value>1.0</value> + <port>0</port> + <check>GE($fullscale, 0.0)</check> + </arg> + <arg> + <name>scalar_correction</name> + <type>double</type> + <value>1.0</value> + <port>0</port> + </arg> + <arg> + <name>freq</name> + <type>double</type> + <value>0.0</value> + <port>1</port> + <!--<action>--> + <!--SR_WRITE("CORDIC_FREQ", $cordic_freq)--> + <!--</action>--> + <!--FIXME Calculate this properly--> + </arg> + </args> + <!--All the connections to the outside world are listed in 'ports':--> + <ports> + <sink> + <name>in0</name> + <type>sc16</type> + </sink> + <source> + <name>out0</name> + <type>sc16</type> + </source> + </ports> +</nocblock> + diff --git a/host/include/uhd/rfnoc/blocks/dma_fifo.xml b/host/include/uhd/rfnoc/blocks/dma_fifo.xml new file mode 100644 index 000000000..fb30d58fe --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/dma_fifo.xml @@ -0,0 +1,64 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>DMA FIFO</name> + <blockname>DmaFIFO</blockname> + <key>DmaFIFO</key> + <!--There can be several of these:--> + <ids> + <id revision="0">F1F0D000</id> + </ids> + <!-- Registers --> + <registers> + </registers> + <!-- Args --> + <args> + <arg> + <name>base_addr</name> + <type>int</type> + <!--<value>0</value>--> + <port>0</port> + <check>EQUAL($base_addr, 0) OR IS_PWR_OF_2($base_addr)</check> + <check_message>The base address must be 0 or a positive power of 2.</check_message> + </arg> + <arg> + <name>depth</name> + <type>int</type> + <!--<value>33554432</value>--> + <port>0</port> + <check>IS_PWR_OF_2($depth)</check> + <check_message>The FIFO depth must be a positive power of 2.</check_message> + </arg> + <arg> + <name>base_addr</name> + <type>int</type> + <!--<value>33554432</value>--> + <port>1</port> + <check>EQUAL($base_addr, 0) OR IS_PWR_OF_2($base_addr)</check> + <check_message>The base address must be 0 or a positive power of 2.</check_message> + </arg> + <arg> + <name>depth</name> + <type>int</type> + <!--<value>33554432</value>--> + <port>1</port> + <check>IS_PWR_OF_2($depth)</check> + <check_message>The FIFO depth must be a positive power of 2.</check_message> + </arg> + </args> + <!--All the connections to the outside world are listed in 'ports':--> + <ports> + <sink> + <name>in0</name> + </sink> + <sink> + <name>in1</name> + </sink> + <source> + <name>out0</name> + </source> + <source> + <name>out1</name> + </source> + </ports> +</nocblock> + diff --git a/host/include/uhd/rfnoc/blocks/duc.xml b/host/include/uhd/rfnoc/blocks/duc.xml new file mode 100644 index 000000000..62f005372 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/duc.xml @@ -0,0 +1,96 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>Tx DSP (DUC/CORDIC)</name> + <blockname>DUC</blockname> + <key>DUC</key> + <!--There can be several of these:--> + <ids> + <id revision="0">D0C0</id> + </ids> + <!-- Registers --> + <registers> + <!-- AXI rate change block registers --> + <setreg> + <name>N</name> + <address>128</address> + </setreg> + <setreg> + <name>M</name> + <address>129</address> + </setreg> + <setreg> + <!-- 1 bit, enable clear user --> + <name>CONFIG</name> + <address>130</address> + </setreg> + <!-- DUC block registers --> + <setreg> + <name>INTERP_WORD</name> <!--Includes the half-bands and the CIC--> + <address>131</address> + </setreg> + <setreg> + <name>CORDIC_FREQ</name> + <address>132</address> + </setreg> + <setreg> + <name>SCALE_IQ</name> + <address>133</address> + </setreg> + </registers> + <!-- Args --> + <args> + <arg> + <name>freq</name> + <type>double</type> + <value>0.0</value> + <port>0</port> + <!--<action>--> + <!--SR_WRITE("CORDIC_FREQ", $cordic_freq)--> + <!--</action>--> + <!--FIXME Calculate this properly--> + </arg> + <arg> + <name>input_rate</name> + <type>double</type> + <value>1.0</value> + <port>0</port> + <check>GE($input_rate, 0.0)</check> + <check_message>The input rate must be a positive value (in Hz).</check_message> + </arg> + <arg> + <name>output_rate</name> + <type>double</type> + <value>1.0</value> + <port>0</port> + <check>GE($output_rate, 0.0)</check> + <check_message>The output rate must be a positive value (in Hz).</check_message> + </arg> + <arg> + <name>fullscale</name> + <type>double</type> + <value>1.0</value> + <port>0</port> + <check>GE($fullscale, 0.0)</check> + <check_message>The output rate must be a positive value (in Hz).</check_message> + <!--FIXME Calculate this properly--> + </arg> + <arg> + <name>scalar_correction</name> + <type>double</type> + <value>1.0</value> + <port>0</port> + </arg> + </args> + <!--All the connections to the outside world are listed in 'ports':--> + <ports> + <sink> + <name>in</name> + <type>sc16</type> + </sink> + <source> + <name>out</name> + <type>sc16</type> + </source> + </ports> +</nocblock> + diff --git a/host/include/uhd/rfnoc/blocks/fft.xml b/host/include/uhd/rfnoc/blocks/fft.xml new file mode 100644 index 000000000..7dd2eff46 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/fft.xml @@ -0,0 +1,116 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>FFT</name> + <blockname>FFT</blockname> + <!--There can be several of these:--> + <ids> + <id revision="0">FF70</id> + </ids> + <!-- Registers --> + <registers> + <!--Note: AXI config bus uses 129 & 130--> + <setreg> + <name>FFT_RESET</name> + <address>131</address> + </setreg> + <setreg> + <name>FFT_SIZE_LOG2</name> + <address>132</address> + </setreg> + <setreg> + <name>MAGNITUDE_OUT</name> + <address>133</address> + </setreg> + <readback> + <name>RB_FFT_RESET</name> + <address>0</address> + </readback> + <readback> + <name>RB_MAGNITUDE_OUT</name> + <address>1</address> + </readback> + </registers> + <!-- Args --> + <args> + <arg> + <!--This controls only the fft shift part, so remember to also set the ctrl_word--> + <name>spp</name> + <type>int</type> + <value>256</value> + <check>GE($spp, 16) AND LE($spp, 4096) AND IS_PWR_OF_2($spp)</check> + <check_message>FFT size must be in [16, 4096] and a power of two.</check_message> + <action>SR_WRITE("FFT_SIZE_LOG2", LOG2($spp)) AND SR_WRITE("AXIS_CONFIG_BUS", ADD(873472, LOG2($spp)))</action> + </arg> + <arg> + <name>ctrl_word</name> + <type>int</type> + <value>873472</value> + <!--<check>EQUAL($otype, "sc16")</check>--> + <!--<check_message>Output data type must be sc16.</check_message>--> + <!--TODO: Check against mag-out value (requires GET() function) --> + <action>SR_WRITE("AXIS_CONFIG_BUS", ADD($ctrl_word, LOG2($spp)))</action> + </arg> + <arg> + <name>otype</name> + <type>string</type> + <value>sc16</value> + <check>EQUAL($otype, "sc16")</check> + <check_message>Output data type must be sc16.</check_message> + <!--TODO: Check against mag-out value (requires GET() function) --> + </arg> + <arg> + <name>reset</name> + <type>int</type> + <value>1</value> + <action> + IF(NOT(EQUAL($reset, 0)), SR_WRITE("FFT_RESET", 1) AND SR_WRITE("FFT_RESET", 0)) + </action> + <!--TODO: Set to zero after setting, add publisher--> + </arg> + <arg> + <name>magnitude_out</name> + <type>string</type> + <value>COMPLEX</value> + <check>EQUAL($magnitude_out, "COMPLEX") OR EQUAL($magnitude_out, "MAGNITUDE") OR EQUAL($magnitude_out, "MAGNITUDE_SQUARED")</check> + <check_message>Output format must be one of: COMPLEX, MAGNITUDE, MAGNITUDE_SQUARED.</check_message> + <action> + IF(EQUAL($magnitude_out, "COMPLEX"), SR_WRITE("MAGNITUDE_OUT", 0)) OR + IF(EQUAL($magnitude_out, "MAGNITUDE"), SR_WRITE("MAGNITUDE_OUT", 1)) OR + IF(EQUAL($magnitude_out, "MAGNITUDE_SQUARED"), SR_WRITE("MAGNITUDE_OUT", 2)) + </action> + <!--TODO: add publisher--> + </arg> + </args> + <!--All the connections to the outside world are listed in 'ports':--> + <ports> + <sink> + <name>in</name> + <type>sc16</type> + <vlen>$spp</vlen> + <pkt_size>%vlen</pkt_size> + </sink> + <source> + <name>out</name> + <type>$otype</type> <!--TODO make this dependent on the output type --> + <vlen>$spp</vlen> + <pkt_size>%vlen</pkt_size> + </source> + </ports> + <!--<components>--> + <!--<component>--> + <!--<key revision="1">nocshell</key>--> + <!--</component>--> + <!--<component srbase="0">--> + <!--[>Will look for a component with this key:<]--> + <!--<key revision="1">componentname</key>--> + <!--</component>--> + <!--</components>--> + <!--<connection>--> + <!--<source port="0">nocshell</source>--> + <!--<sink port="0">componentname</sink>--> + <!--</connection>--> + <!--<connection>--> + <!--<source port="0">componentname</source>--> + <!--<sink port="0">nocshell</sink>--> + <!--</connection>--> +</nocblock> diff --git a/host/include/uhd/rfnoc/blocks/fifo.xml b/host/include/uhd/rfnoc/blocks/fifo.xml new file mode 100644 index 000000000..9e8900b89 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/fifo.xml @@ -0,0 +1,34 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>FIFO</name> + <blockname>FIFO</blockname> + <!--There can be several of these:--> + <ids> + <id revision="0">F1F00000</id> + </ids> + <ports> + <sink> + <name>in0</name> + </sink> + <source> + <name>out0</name> + </source> + </ports> + <!--<components>--> + <!--<component>--> + <!--<key revision="1">nocshell</key>--> + <!--</component>--> + <!--<component srbase="0">--> + <!--[>Will look for a component with this key:<]--> + <!--<key revision="1">componentname</key>--> + <!--</component>--> + <!--</components>--> + <!--<connection>--> + <!--<source port="0">nocshell</source>--> + <!--<sink port="0">componentname</sink>--> + <!--</connection>--> + <!--<connection>--> + <!--<source port="0">componentname</source>--> + <!--<sink port="0">nocshell</sink>--> + <!--</connection>--> +</nocblock> diff --git a/host/include/uhd/rfnoc/blocks/fir.xml b/host/include/uhd/rfnoc/blocks/fir.xml new file mode 100644 index 000000000..9a97e3a84 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/fir.xml @@ -0,0 +1,19 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>FIR Filter</name> + <blockname>FIR</blockname> + <!--There can be several of these:--> + <ids> + <id revision="0">F112</id> + </ids> + <ports> + <sink> + <name>in</name> + <type>sc16</type> + </sink> + <source> + <name>out</name> + <type>sc16</type> + </source> + </ports> +</nocblock> diff --git a/host/include/uhd/rfnoc/blocks/fosphor.xml b/host/include/uhd/rfnoc/blocks/fosphor.xml new file mode 100644 index 000000000..762b7c1bd --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/fosphor.xml @@ -0,0 +1,157 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>fosphor</name> + <blockname>fosphor</blockname> + <!--There can be several of these:--> + <ids> + <id revision="0">666F</id> + </ids> + <!-- Registers --> + <registers> + <setreg> + <name>DECIM</name> + <address>129</address> + </setreg> + <setreg> + <name>OFFSET</name> + <address>130</address> + </setreg> + <setreg> + <name>SCALE</name> + <address>131</address> + </setreg> + <setreg> + <name>TRISE</name> + <address>132</address> + </setreg> + <setreg> + <name>TDECAY</name> + <address>133</address> + </setreg> + <setreg> + <name>ALPHA</name> + <address>134</address> + </setreg> + <setreg> + <name>EPSILON</name> + <address>135</address> + </setreg> + <setreg> + <name>RANDOM</name> + <address>136</address> + </setreg> + <setreg> + <name>CLEAR</name> + <address>137</address> + </setreg> + </registers> + <!-- Args --> + <args> + <arg> + <name>spp</name> + <type>int</type> + <value>1024</value> + </arg> + <arg> + <name>decim</name> + <type>int</type> + <value>2</value> + <check>GE($decim, 2) AND LE($decim, 1024)</check> + <check_message>fosphor decim constant must be within [2, 1024]</check_message> + <action>SR_WRITE("DECIM", ADD($decim, -2))</action> + </arg> + <arg> + <name>offset</name> + <type>int</type> + <value>0</value> + <check>GE($offset, 0) AND LE($offset, 65536)</check> + <check_message>"fosphor offset value must be within [0, 65535]"</check_message> + <action>SR_WRITE("OFFSET", $offset)</action> + </arg> + <arg> + <name>scale</name> + <type>int</type> + <value>256</value> + <check>GE($scale, 0) AND LE($scale, 65536)</check> + <check_message>"fosphor scale value must be within [0, 65535]"</check_message> + <action>SR_WRITE("SCALE", $scale)</action> + </arg> + <arg> + <name>trise</name> + <type>int</type> + <value>4096</value> + <check>GE($trise, 0) AND LE($trise, 65536)</check> + <check_message>"fosphor trise value must be within [0, 65535]"</check_message> + <action>SR_WRITE("TRISE", $trise)</action> + </arg> + <arg> + <name>tdecay</name> + <type>int</type> + <value>16384</value> + <check>GE($tdecay, 0) AND LE($tdecay, 65536)</check> + <check_message>"fosphor tdecay value must be within [0, 65535]"</check_message> + <action>SR_WRITE("TDECAY", $tdecay)</action> + </arg> + <arg> + <name>alpha</name> + <type>int</type> + <value>65280</value> + <check>GE($alpha, 0) AND LE($alpha, 65536)</check> + <check_message>"fosphor alpha value must be within [0, 65535]"</check_message> + <action>SR_WRITE("ALPHA", $alpha)</action> + </arg> + <arg> + <name>epsilon</name> + <type>int</type> + <value>1</value> + <check>GE($epsilon, 0) AND LE($epsilon, 65536)</check> + <check_message>"fosphor epsilon value must be within [0, 65535]"</check_message> + <action>SR_WRITE("EPSILON", $epsilon)</action> + </arg> + <arg> + <name>random</name> + <type>int</type> + <value>1</value> + <check>GE($random, 0) AND LE($random, 3)</check> + <check_message>"fosphor random value must be within [0, 65535]"</check_message> + <action>SR_WRITE("RANDOM", $random)</action> + </arg> + <arg> + <name>clear</name> + <type>int</type> + <action>IF(NOT(EQUAL($clear, 0)), SR_WRITE("CLEAR", $clear))</action> + </arg> + </args> + <!--All the connections to the outside world are listed in 'ports':--> + <ports> + <sink> + <name>in</name> + <type>sc16</type> + <vlen>$spp</vlen> + <pkt_size>%vlen</pkt_size> + </sink> + <source> + <name>out</name> + <type>u8</type> + <vlen>$spp</vlen> + <pkt_size>%vlen</pkt_size> + </source> + </ports> + <!--<components>--> + <!--<component>--> + <!--<key revision="1">nocshell</key>--> + <!--</component>--> + <!--<component srbase="0">--> + <!--[>Will look for a component with this key:<]--> + <!--<key revision="1">componentname</key>--> + <!--</component>--> + <!--</components>--> + <!--<connection>--> + <!--<source port="0">nocshell</source>--> + <!--<sink port="0">componentname</sink>--> + <!--</connection>--> + <!--<connection>--> + <!--<source port="0">componentname</source>--> + <!--<sink port="0">nocshell</sink>--> + <!--</connection>--> +</nocblock> diff --git a/host/include/uhd/rfnoc/blocks/keep_one_in_n.xml b/host/include/uhd/rfnoc/blocks/keep_one_in_n.xml new file mode 100644 index 000000000..5a99685de --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/keep_one_in_n.xml @@ -0,0 +1,55 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>Keep One in N</name> + <blockname>KeepOneInN</blockname> + <doc> + Block controller for the Keep One in N RFNoC block. + + For every N packets received, this block will output a single packet. + - One input / output block port + - N up to 65535 + </doc> + <!--There can be several of these:--> + <ids> + <id revision="0">0246</id> + </ids> + <registers> + <setreg> + <name>SR_N</name> + <address>129</address> + </setreg> + </registers> + <args> + <arg> + <name>n</name> + <type>int</type> + <value>256</value> + <action>SR_WRITE("SR_N", $n)</action> + </arg> + </args> + <ports> + <sink> + <name>in</name> + </sink> + <source> + <name>out</name> + </source> + </ports> + <!--<components>--> + <!--<component>--> + <!--<key revision="1">nocshell</key>--> + <!--</component>--> + <!--<component srbase="0">--> + <!--[>Will look for a component with this key:<]--> + <!--<key revision="1">componentname</key>--> + <!--</component>--> + <!--</components>--> + <!--<connection>--> + <!--<source port="0">nocshell</source>--> + <!--<sink port="0">componentname</sink>--> + <!--</connection>--> + <!--<connection>--> + <!--<source port="0">componentname</source>--> + <!--<sink port="0">nocshell</sink>--> + <!--</connection>--> +</nocblock> diff --git a/host/include/uhd/rfnoc/blocks/logpwr.xml b/host/include/uhd/rfnoc/blocks/logpwr.xml new file mode 100644 index 000000000..9307446e3 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/logpwr.xml @@ -0,0 +1,49 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>Log Power</name> + <blockname>LogPwr</blockname> + <!--There can be several of these:--> + <ids> + <id revision="0">4C50</id> + </ids> + <!-- Args --> + <args> + <arg> + <name>spp</name> + <type>int</type> + <value>256</value> + </arg> + </args> + <!--All the connections to the outside world are listed in 'ports':--> + <ports> + <sink> + <name>in</name> + <type>sc16</type> + <vlen>$spp</vlen> + <pkt_size>%vlen</pkt_size> + </sink> + <source> + <name>out</name> + <type>sc16</type> + <vlen>$spp</vlen> + <pkt_size>%vlen</pkt_size> + </source> + </ports> + <!--<components>--> + <!--<component>--> + <!--<key revision="1">nocshell</key>--> + <!--</component>--> + <!--<component srbase="0">--> + <!--[>Will look for a component with this key:<]--> + <!--<key revision="1">componentname</key>--> + <!--</component>--> + <!--</components>--> + <!--<connection>--> + <!--<source port="0">nocshell</source>--> + <!--<sink port="0">componentname</sink>--> + <!--</connection>--> + <!--<connection>--> + <!--<source port="0">componentname</source>--> + <!--<sink port="0">nocshell</sink>--> + <!--</connection>--> +</nocblock> diff --git a/host/include/uhd/rfnoc/blocks/nullblock.xml b/host/include/uhd/rfnoc/blocks/nullblock.xml new file mode 100644 index 000000000..f1ed3bbd2 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/nullblock.xml @@ -0,0 +1,30 @@ +<nocblock> + <name>Null Source/Sink</name> + <blockname>NullSrcSink</blockname> + <!--There can be several of these:--> + <ids> + <id revision="0">0000000000000000</id> + </ids> + <!-- Args --> + <args> + <arg> + <name>line_rate</name> + <type>int</type> + <value>65535</value> + </arg> + <arg> + <name>bpp</name> + <type>int</type> + <value>256</value> + </arg> + </args> + <!-- Ports --> + <ports> + <sink> + <name>dump</name> + </sink> + <source> + <name>src</name> + </source> + </ports> +</nocblock> diff --git a/host/include/uhd/rfnoc/blocks/ofdmeq.xml b/host/include/uhd/rfnoc/blocks/ofdmeq.xml new file mode 100644 index 000000000..50218e976 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/ofdmeq.xml @@ -0,0 +1,31 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>OFDM Equalizer</name> + <blockname>OFDMEq</blockname> + <!--There can be several of these:--> + <ids> + <id revision="0">FF42</id> + </ids> + <!-- Args --> + <args> + <arg> + <name>fftsize</name> + <type>int</type> + <value>64</value> + </arg> + </args> + <ports> + <sink> + <name>in</name> + <type>sc16</type> + <vlen>$fftsize</vlen> + <pkt_size>%vlen</pkt_size> + </sink> + <source> + <name>out</name> + <type>sc16</type> + <vlen>$fftsize</vlen> + <pkt_size>%vlen</pkt_size> + </source> + </ports> +</nocblock> diff --git a/host/include/uhd/rfnoc/blocks/packetresizer.xml b/host/include/uhd/rfnoc/blocks/packetresizer.xml new file mode 100644 index 000000000..306218318 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/packetresizer.xml @@ -0,0 +1,55 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>Packet Resizer</name> + <blockname>PacketResizer</blockname> + <doc> + Block controller for the Packet Resizer RFNoC block. + + The Packet Resizer RFNoC block changes the packet length of a stream. It can break large + packets into smaller packets or combine small packets into a single larger packet. + </doc> + <ids> + <id revision="0">12E5</id> + </ids> + <registers> + <setreg> + <name>SR_PKT_SIZE</name> + <address>129</address> + </setreg> + </registers> + <args> + <arg> + <name>pkt_size</name> + <type>int</type> + <value>32</value> + <check>GT($pkt_size, 0)</check> + <check_message>Packet size must be positive, non-zero.</check_message> + <action>SR_WRITE("SR_PKT_SIZE", $pkt_size)</action> + </arg> + </args> + <ports> + <sink> + <name>in</name> + </sink> + <source> + <name>out</name> + </source> + </ports> + <!--<components>--> + <!--<component>--> + <!--<key revision="1">nocshell</key>--> + <!--</component>--> + <!--<component srbase="0">--> + <!--[>Will look for a component with this key:<]--> + <!--<key revision="1">componentname</key>--> + <!--</component>--> + <!--</components>--> + <!--<connection>--> + <!--<source port="0">nocshell</source>--> + <!--<sink port="0">componentname</sink>--> + <!--</connection>--> + <!--<connection>--> + <!--<source port="0">componentname</source>--> + <!--<sink port="0">nocshell</sink>--> + <!--</connection>--> +</nocblock> diff --git a/host/include/uhd/rfnoc/blocks/radio_x300.xml b/host/include/uhd/rfnoc/blocks/radio_x300.xml new file mode 100644 index 000000000..4130522a5 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/radio_x300.xml @@ -0,0 +1,60 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>Radio (X300)</name> + <blockname>Radio</blockname> + <key>X300Radio</key> + <!--There can be several of these:--> + <ids> + <id revision="0">12AD100000000001</id> + </ids> + <!-- Registers --> + <registers> + <!--<setreg>--> + <!--<name>FFT_RESET</name>--> + <!--<address>131</address>--> + <!--</setreg>--> + <!--<readback>--> + <!--<name>RB_MAGNITUDE_OUT</name>--> + <!--<address>1</address>--> + <!--</readback>--> + </registers> + <!-- Args --> + <args> + <arg> + <name>spp</name> + <type>int</type> + <value>364</value> + <!--<value>256</value>--> + <!--<check>GE($spp, 16) AND LE($spp, 4096) AND IS_PWR_OF_2($spp)</check>--> + <!--<check_message>FFT size must be in [16, 4096] and a power of two.</check_message>--> + <!--<action>SR_WRITE("FFT_SIZE_LOG2", LOG2($spp)) AND SR_WRITE("AXIS_CONFIG_BUS", ADD(873472, LOG2($spp)))</action>--> + </arg> + </args> + <ports> + <sink> + <name>in0</name> + <type>sc16</type> + <!--<vlen>$spp</vlen>--> + <!--<pkt_size>%vlen</pkt_size>--> + </sink> + <sink> + <name>in1</name> + <type>sc16</type> + <!--<vlen>$spp</vlen>--> + <!--<pkt_size>%vlen</pkt_size>--> + </sink> + <source> + <name>out0</name> + <type>sc16</type> + <!--<vlen>$spp</vlen>--> + <!--<pkt_size>%vlen</pkt_size>--> + </source> + <source> + <name>out1</name> + <type>sc16</type> + <!--<vlen>$spp</vlen>--> + <!--<pkt_size>%vlen</pkt_size>--> + </source> + </ports> +</nocblock> + diff --git a/host/include/uhd/rfnoc/blocks/siggen.xml b/host/include/uhd/rfnoc/blocks/siggen.xml new file mode 100644 index 000000000..2850e6804 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/siggen.xml @@ -0,0 +1,116 @@ +<nocblock> + <name>Signal Generator</name> + <blockname>SigGen</blockname> + <!--There can be several of these:--> + <ids> + <id revision="0">5166311000000000</id> + </ids> + <!-- Registers --> + <registers> + <!-- Reg 128 used for FREQ --> + <setreg> + <name>FREQ</name> + <address>129</address> + </setreg> + <setreg> + <name>CARTESIAN</name> + <address>130</address> + </setreg> + <setreg> + <name>ENABLE</name> + <address>132</address> + </setreg> + <setreg> + <name>CONSTANT</name> + <address>138</address> + </setreg> + <setreg> + <name>GAIN</name> + <address>139</address> + </setreg> + <setreg> + <name>PKT_SIZE</name> + <address>140</address> + </setreg> + <setreg> + <name>WAVEFORM</name> + <address>142</address> + </setreg> + </registers> + <!-- Args --> + <args> + <arg> + <name>enable</name> + <type>int</type> + <value>0</value> + <check>EQUAL($enable, 0) OR EQUAL($enable, 1)</check> + <check_message>Enable is either 0 or 1.</check_message> + <action>SR_WRITE("ENABLE", $enable)</action> + </arg> + <arg> + <name>spp</name> + <type>int</type> + <value>256</value> + <action>SR_WRITE("PKT_SIZE", $spp)</action> + </arg> + <!-- Overall Gain --> + <arg> + <name>gain</name> + <type>double</type> + <value>1.0</value> + <check>GE($gain, 0.0) AND LE($gain, 1.0)</check> + <check_message>Invalid gain.</check_message> + <action> + SR_WRITE("GAIN", IROUND(MULT(32767.0,$gain))) + </action> + </arg> + <!-- Sine Wave, Constant I / Q --> + <arg> + <name>amplitude_i</name> + <type>double</type> + <value>1.0</value> + <check>GE($amplitude_i, -1.0) AND LE($amplitude_i, 1.0)</check> + <check_message>Invalid amplitude.</check_message> + </arg> + <arg> + <name>amplitude_q</name> + <type>double</type> + <value>1.0</value> + <check>GE($amplitude_q, -1.0) AND LE($amplitude_q, 1.0)</check> + <check_message>Invalid amplitude.</check_message> + <action> + SR_WRITE("CONSTANT", ADD(MULT(65536,IROUND(MULT(32767.0, $amplitude_i))),IROUND(MULT(32767.0, $amplitude_q)))) + </action> + </arg> + <arg> + <name>frequency</name> + <type>double</type> + <value>0.1</value> + <check>GE($frequency, -1.0) AND LE($frequency, 1.0)</check> + <check_message>Invalid frequency.</check_message> + <action>SR_WRITE("FREQ", IROUND(MULT(-8192.0, $frequency)))</action> + </arg> + <arg> + <name>waveform</name> + <type>string</type> + <value>CONSTANT</value> + <check>EQUAL($waveform, "CONSTANT") OR EQUAL($waveform, "SINE_WAVE") OR EQUAL($waveform, "NOISE")</check> + <check_message>Waveform type should be one of: CONSTANT, SINE WAVE, NOISE.</check_message> + <action> + IF(EQUAL($waveform, "CONSTANT"), SR_WRITE("WAVEFORM", 0) AND SR_WRITE("CONSTANT", ADD(MULT(65536,IROUND(MULT(32767.0, $amplitude_i))),IROUND(MULT(32767.0, $amplitude_q))))) OR + IF(EQUAL($waveform, "SINE_WAVE"), SR_WRITE("WAVEFORM", 1) AND SR_WRITE("CARTESIAN", MULT(65536,28000))) OR + IF(EQUAL($waveform, "NOISE"), SR_WRITE("WAVEFORM", 2)) + </action> + </arg> + </args> + <!-- Ports --> + <ports> + <sink> + <name>dump</name> + </sink> + <source> + <name>src</name> + <type>sc16</type> + </source> + </ports> +</nocblock> diff --git a/host/include/uhd/rfnoc/blocks/window.xml b/host/include/uhd/rfnoc/blocks/window.xml new file mode 100644 index 000000000..df36f4b4f --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/window.xml @@ -0,0 +1,47 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>Window</name> + <blockname>Window</blockname> + <!--There can be several of these:--> + <ids> + <id revision="0">D053</id> + </ids> + <args> + <arg> + <name>spp</name> + <type>int</type> + <value>256</value> + </arg> + </args> + <ports> + <sink> + <name>in</name> + <type>sc16</type> + <vlen>$spp</vlen> + <pkt_size>%vlen</pkt_size> + </sink> + <source> + <name>out</name> + <type>sc16</type> + <vlen>$spp</vlen> + <pkt_size>%vlen</pkt_size> + </source> + </ports> + <!--<components>--> + <!--<component>--> + <!--<key revision="1">nocshell</key>--> + <!--</component>--> + <!--<component srbase="0">--> + <!--[>Will look for a component with this key:<]--> + <!--<key revision="1">componentname</key>--> + <!--</component>--> + <!--</components>--> + <!--<connection>--> + <!--<source port="0">nocshell</source>--> + <!--<sink port="0">componentname</sink>--> + <!--</connection>--> + <!--<connection>--> + <!--<source port="0">componentname</source>--> + <!--<sink port="0">nocshell</sink>--> + <!--</connection>--> +</nocblock> diff --git a/host/include/uhd/rfnoc/constants.hpp b/host/include/uhd/rfnoc/constants.hpp new file mode 100644 index 000000000..14e0da55c --- /dev/null +++ b/host/include/uhd/rfnoc/constants.hpp @@ -0,0 +1,108 @@ +// +// 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_CONSTANTS_HPP +#define INCLUDED_LIBUHD_RFNOC_CONSTANTS_HPP + +#include <uhd/types/dict.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/cstdint.hpp> +#include <string> + +namespace uhd { + namespace rfnoc { + +// All these configure the XML reader +//! Where the RFNoC block/component definition files lie, relative to UHD_PKG_DIR +static const std::string XML_DEFAULT_PATH = "share/uhd/rfnoc"; +//! The name of the environment variable storing the bath to the block definition files +static const std::string XML_PATH_ENV = "UHD_RFNOC_DIR"; + +//! If the block name can't be automatically detected, this name is used +static const std::string DEFAULT_BLOCK_NAME = "Block"; +static const boost::uint64_t DEFAULT_NOC_ID = 0xFFFFFFFFFFFFFFFF; + +static const size_t MAX_PACKET_SIZE = 8000; // bytes +static const size_t DEFAULT_PACKET_SIZE = 1456; // bytes + +// One line in FPGA is 64 Bits +static const size_t BYTES_PER_LINE = 8; + +//! For flow control within a single crossbar +static const size_t DEFAULT_FC_XBAR_PKTS_PER_ACK = 2; +//! For flow control when data is flowing from device to host (rx) +static const size_t DEFAULT_FC_RX_RESPONSE_FREQ = 64; // ACKs per flow control window +//! For flow control when data is flowing from host to device (tx) +static const size_t DEFAULT_FC_TX_RESPONSE_FREQ = 8; // ACKs per flow control window +//! On the receive side, how full do we want the buffers? +// Why not 100% full? Because we need to have some headroom to account for the inaccuracy +// when computing the window size. We compute the flow control window based on the frame +// size but the buffer can have overhead due to things like UDP headers, page alignment, +// housekeeping info, etc. This number has to be transport agnostic so 20% of headroom is safe. +static const double DEFAULT_FC_RX_SW_BUFF_FULL_FACTOR = 0.80; + +// Common settings registers. +static const boost::uint32_t SR_FLOW_CTRL_CYCS_PER_ACK = 0; +static const boost::uint32_t SR_FLOW_CTRL_PKTS_PER_ACK = 1; +static const boost::uint32_t SR_FLOW_CTRL_WINDOW_SIZE = 2; +static const boost::uint32_t SR_FLOW_CTRL_WINDOW_EN = 3; +static const boost::uint32_t SR_ERROR_POLICY = 4; +static const boost::uint32_t SR_BLOCK_SID = 5; // TODO rename to SRC_SID +static const boost::uint32_t SR_NEXT_DST_SID = 6; +static const boost::uint32_t SR_RESP_IN_DST_SID = 7; +static const boost::uint32_t SR_RESP_OUT_DST_SID = 8; + +static const boost::uint32_t SR_READBACK_ADDR = 124; +static const boost::uint32_t SR_READBACK = 127; + +static const boost::uint32_t SR_CLEAR_RX_FC = 125; +static const boost::uint32_t SR_CLEAR_TX_FC = 126; + +//! Settings register readback +enum settingsbus_reg_t { + SR_READBACK_REG_ID = 0, + SR_READBACK_REG_GLOBAL_PARAMS = 1, + SR_READBACK_REG_FIFOSIZE = 2, // fifo size + SR_READBACK_REG_MTU = 3, + SR_READBACK_REG_BLOCKPORT_SIDS = 4, + SR_READBACK_REG_USER = 5 + /* 6 currently unused */ +}; + +// AXI stream configuration bus (output master bus of axi wrapper) registers +static const boost::uint32_t AXI_WRAPPER_BASE = 128; +static const boost::uint32_t AXIS_CONFIG_BUS = AXI_WRAPPER_BASE+1; // tdata with tvalid asserted +static const boost::uint32_t AXIS_CONFIG_BUS_TLAST = AXI_WRAPPER_BASE+2; // tdata with tvalid & tlast asserted + +// Named settings registers +static const uhd::dict<std::string, boost::uint32_t> DEFAULT_NAMED_SR = boost::assign::map_list_of + ("AXIS_CONFIG_BUS", AXIS_CONFIG_BUS) + ("AXIS_CONFIG_BUS_TLAST", AXIS_CONFIG_BUS_TLAST) +; + +// Block ports +static const size_t ANY_PORT = size_t(~0); +static const size_t MAX_NUM_PORTS = 16; + +// Regular expressions +static const std::string VALID_BLOCKNAME_REGEX = "[A-Za-z][A-Za-z0-9]*"; +static const std::string VALID_BLOCKID_REGEX = "(?:(\\d+)(?:/))?([A-Za-z][A-Za-z0-9]*)(?:(?:_)(\\d\\d?))?"; + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_CONSTANTS_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/ddc_block_ctrl.hpp b/host/include/uhd/rfnoc/ddc_block_ctrl.hpp new file mode 100644 index 000000000..d9dab3e71 --- /dev/null +++ b/host/include/uhd/rfnoc/ddc_block_ctrl.hpp @@ -0,0 +1,50 @@ +// +// 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/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_DDC_BLOCK_CTRL_HPP +#define INCLUDED_LIBUHD_RFNOC_DDC_BLOCK_CTRL_HPP + +#include <uhd/rfnoc/source_block_ctrl_base.hpp> +#include <uhd/rfnoc/sink_block_ctrl_base.hpp> +#include <uhd/rfnoc/rate_node_ctrl.hpp> +#include <uhd/rfnoc/scalar_node_ctrl.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief DDC block controller + * + * This block provides DSP for Rx operations. + * Its main component is a DDC chain, which can decimate over a wide range + * of decimation rates (using a CIC and halfband filters). + * + * It also includes a CORDIC component to shift signals in frequency. + */ +class UHD_RFNOC_API ddc_block_ctrl : + public source_block_ctrl_base, + public sink_block_ctrl_base, + public rate_node_ctrl, + public scalar_node_ctrl +{ +public: + UHD_RFNOC_BLOCK_OBJECT(ddc_block_ctrl) + +}; /* class ddc_block_ctrl*/ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_DDC_BLOCK_CTRL_HPP */ diff --git a/host/include/uhd/rfnoc/dma_fifo_block_ctrl.hpp b/host/include/uhd/rfnoc/dma_fifo_block_ctrl.hpp new file mode 100644 index 000000000..fe55fc678 --- /dev/null +++ b/host/include/uhd/rfnoc/dma_fifo_block_ctrl.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_LIBUHD_RFNOC_DMA_FIFO_BLOCK_HPP +#define INCLUDED_LIBUHD_RFNOC_DMA_FIFO_BLOCK_HPP + +#include <uhd/rfnoc/source_block_ctrl_base.hpp> +#include <uhd/rfnoc/sink_block_ctrl_base.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Block controller for a DMA FIFO block. + * + * The DMA FIFO block has the following features: + * - One input- and output-port (type agnostic) + * - Configurable base address and FIFO depth + * - The base storage for the FIFO can be device + * specific. Usually it will be an off-chip SDRAM + * bank. + * + */ +class UHD_RFNOC_API dma_fifo_block_ctrl : public source_block_ctrl_base, public sink_block_ctrl_base +{ +public: + UHD_RFNOC_BLOCK_OBJECT(dma_fifo_block_ctrl) + + //! Configure the base address and depth of the FIFO (in bytes). + virtual void resize(const uint32_t base_addr, const uint32_t depth, const size_t chan) = 0; + + //! Returns the base address of the FIFO (in bytes). + uint32_t get_base_addr(const size_t chan) const; + + //! Returns the depth of the FIFO (in bytes). + uint32_t get_depth(const size_t chan) const; + +}; /* class dma_fifo_block_ctrl*/ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_DMA_FIFO_BLOCK_HPP */ diff --git a/host/include/uhd/rfnoc/duc_block_ctrl.hpp b/host/include/uhd/rfnoc/duc_block_ctrl.hpp new file mode 100644 index 000000000..38c54aa31 --- /dev/null +++ b/host/include/uhd/rfnoc/duc_block_ctrl.hpp @@ -0,0 +1,51 @@ +// +// 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/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_DUC_BLOCK_CTRL_HPP +#define INCLUDED_LIBUHD_RFNOC_DUC_BLOCK_CTRL_HPP + +#include <uhd/rfnoc/source_block_ctrl_base.hpp> +#include <uhd/rfnoc/sink_block_ctrl_base.hpp> +#include <uhd/rfnoc/rate_node_ctrl.hpp> +#include <uhd/rfnoc/scalar_node_ctrl.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief DUC block controller + * + * This block provides DSP for Tx operations. + * Its main component is a DUC chain, which can interpolate over a wide range + * of interpolation rates (using a CIC and halfband filters). + * + * It also includes a CORDIC component to shift signals in frequency. + */ +class UHD_RFNOC_API duc_block_ctrl : + public source_block_ctrl_base, + public sink_block_ctrl_base, + public rate_node_ctrl, + public scalar_node_ctrl +{ +public: + UHD_RFNOC_BLOCK_OBJECT(duc_block_ctrl) + +}; /* class duc_block_ctrl*/ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_DUC_BLOCK_CTRL_HPP */ + diff --git a/host/include/uhd/rfnoc/graph.hpp b/host/include/uhd/rfnoc/graph.hpp new file mode 100644 index 000000000..8f9005d12 --- /dev/null +++ b/host/include/uhd/rfnoc/graph.hpp @@ -0,0 +1,62 @@ +// +// 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_HPP +#define INCLUDED_LIBUHD_RFNOC_GRAPH_HPP + +#include <boost/noncopyable.hpp> +#include <uhd/rfnoc/block_id.hpp> + +namespace uhd { namespace rfnoc { + +class graph : boost::noncopyable +{ +public: + typedef boost::shared_ptr<uhd::rfnoc::graph> sptr; + + /*! Connect a RFNOC block with block ID \p src_block to another with block ID \p dst_block. + * + * This will: + * - Check if this connection is valid (IO signatures, see if types match) + * - Configure the flow control for the blocks + * - Configure SID for the upstream block + * - Register the upstream block in the downstream block + */ + virtual 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 + ) = 0; + + /*! Shorthand for connect(). + * + * Using default ports for both source and destination. + */ + virtual void connect( + const block_id_t &src_block, + const block_id_t &dst_block + ) = 0; + + virtual std::string get_name() const = 0; +}; + +}}; /* name space uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_GRAPH_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/node_ctrl_base.hpp b/host/include/uhd/rfnoc/node_ctrl_base.hpp new file mode 100644 index 000000000..82e095b1d --- /dev/null +++ b/host/include/uhd/rfnoc/node_ctrl_base.hpp @@ -0,0 +1,244 @@ +// +// 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_NODE_CTRL_BASE_HPP +#define INCLUDED_LIBUHD_NODE_CTRL_BASE_HPP + +#include <uhd/types/device_addr.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <uhd/utils/log.hpp> +#include <boost/cstdint.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <boost/enable_shared_from_this.hpp> +#include <boost/function.hpp> +#include <map> +#include <set> + +namespace uhd { + namespace rfnoc { + +#define UHD_RFNOC_BLOCK_TRACE() UHD_LOGV(never) << "[" << unique_id() << "] " + +/*! \brief Abstract base class for streaming nodes. + * + */ +class UHD_RFNOC_API node_ctrl_base; +class node_ctrl_base : boost::noncopyable, public boost::enable_shared_from_this<node_ctrl_base> +{ +public: + /*********************************************************************** + * Types + **********************************************************************/ + typedef boost::shared_ptr<node_ctrl_base> sptr; + typedef boost::weak_ptr<node_ctrl_base> wptr; + typedef std::map< size_t, wptr > node_map_t; + typedef std::pair< size_t, wptr > node_map_pair_t; + + /*********************************************************************** + * Node control + **********************************************************************/ + //! Returns a unique string that identifies this block. + virtual std::string unique_id() const; + + /*********************************************************************** + * Connections + **********************************************************************/ + /*! Clears the list of connected nodes. + */ + virtual void clear(); + + node_map_t list_downstream_nodes() { return _downstream_nodes; }; + node_map_t list_upstream_nodes() { return _upstream_nodes; }; + + // TODO we need a more atomic connect procedure, this is too error-prone. + + /*! For an existing connection, store the remote port number. + * + * \throws uhd::value_error if \p this_port is not connected. + */ + void set_downstream_port(const size_t this_port, const size_t remote_port); + + /*! Return the remote port of a connection on a given port. + * + * \throws uhd::value_error if \p this_port is not connected. + */ + size_t get_downstream_port(const size_t this_port); + + /*! For an existing connection, store the remote port number. + * + * \throws uhd::value_error if \p this_port is not connected. + */ + void set_upstream_port(const size_t this_port, const size_t remote_port); + + /*! Return the remote port of a connection on a given port. + * + * \throws uhd::value_error if \p this_port is not connected. + */ + size_t get_upstream_port(const size_t this_port); + + /*! Find nodes downstream that match a predicate. + * + * Uses a non-recursive breadth-first search algorithm. + * On every branch, the search stops if a block matches. + * See this example: + * <pre> + * A -> B -> C -> C + * </pre> + * Say node A searches for nodes of type C. It will only find the + * first 'C' block, not the second. + * + * Returns blocks that are of type T. + * + * Search only goes downstream. + */ + template <typename T> + UHD_INLINE std::vector< boost::shared_ptr<T> > find_downstream_node() + { + return _find_child_node<T, true>(); + } + + /*! Same as find_downstream_node(), but only search upstream. + */ + template <typename T> + UHD_INLINE std::vector< boost::shared_ptr<T> > find_upstream_node() + { + return _find_child_node<T, false>(); + } + + /*! Checks if downstream nodes share a common, unique property. + * + * This will use find_downstream_node() to find all nodes downstream of + * this that are of type T. Then it will use \p get_property to return a + * property from all of them. If all these properties are identical, it will + * return that property. Otherwise, it will throw a uhd::runtime_error. + * + * \p get_property A functor to return the property from a node + * \p null_value If \p get_property returns this value, that node is skipped. + * \p explored_nodes A list of nodes to exclude from the search. This is typically + * to avoid recursion loops. + */ + template <typename T, typename value_type> + UHD_INLINE value_type find_downstream_unique_property( + boost::function<value_type(boost::shared_ptr<T> node, size_t port)> get_property, + value_type null_value, + const std::set< boost::shared_ptr<T> > &exclude_nodes=std::set< boost::shared_ptr<T> >() + ) { + return _find_unique_property<T, value_type, true>(get_property, null_value, exclude_nodes); + } + + /*! Like find_downstream_unique_property(), but searches upstream. + */ + template <typename T, typename value_type> + UHD_INLINE value_type find_upstream_unique_property( + boost::function<value_type(boost::shared_ptr<T> node, size_t port)> get_property, + value_type null_value, + const std::set< boost::shared_ptr<T> > &exclude_nodes=std::set< boost::shared_ptr<T> >() + ) { + return _find_unique_property<T, value_type, false>(get_property, null_value, exclude_nodes); + } + +protected: + /*********************************************************************** + * Structors + **********************************************************************/ + node_ctrl_base(void) {}; + virtual ~node_ctrl_base() {}; + + /*********************************************************************** + * Protected members + **********************************************************************/ + + //! Stores default arguments + uhd::device_addr_t _args; + + // TODO make these private + + //! List of upstream nodes + node_map_t _upstream_nodes; + + //! List of downstream nodes + node_map_t _downstream_nodes; + + /*********************************************************************** + * Connections + **********************************************************************/ + /*! Registers another node as downstream of this node, connected to a given port. + * + * This implies that this node is a source node, and the downstream node is + * a sink node. + * See also uhd::rfnoc::source_node_ctrl::_register_downstream_node(). + */ + virtual void _register_downstream_node( + node_ctrl_base::sptr downstream_node, + size_t port + ); + + /*! Registers another node as upstream of this node, connected to a given port. + * + * This implies that this node is a sink node, and the upstream node is + * a source node. + * See also uhd::rfnoc::sink_node_ctrl::_register_upstream_node(). + */ + virtual void _register_upstream_node( + node_ctrl_base::sptr upstream_node, + size_t port + ); + +private: + /*! Implements the search algorithm for find_downstream_node() and + * find_upstream_node(). + * + * Depending on \p downstream, "child nodes" are either defined as + * nodes connected downstream or upstream. + * + * \param downstream Set to true if search goes downstream, false for upstream. + */ + template <typename T, bool downstream> + std::vector< boost::shared_ptr<T> > _find_child_node(); + + /*! Implements the search algorithm for find_downstream_unique_property() and + * find_upstream_unique_property(). + * + * Depending on \p downstream, "child nodes" are either defined as + * nodes connected downstream or upstream. + * + * \param downstream Set to true if search goes downstream, false for upstream. + */ + template <typename T, typename value_type, bool downstream> + value_type _find_unique_property( + boost::function<value_type(boost::shared_ptr<T>, size_t)> get_property, + value_type NULL_VALUE, + const std::set< boost::shared_ptr<T> > &exclude_nodes + ); + + /*! Stores the remote port number of a downstream connection. + */ + std::map<size_t, size_t> _upstream_ports; + + /*! Stores the remote port number of a downstream connection. + */ + std::map<size_t, size_t> _downstream_ports; + +}; /* class node_ctrl_base */ + +}} /* namespace uhd::rfnoc */ + +#include <uhd/rfnoc/node_ctrl_base.ipp> + +#endif /* INCLUDED_LIBUHD_NODE_CTRL_BASE_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/node_ctrl_base.ipp b/host/include/uhd/rfnoc/node_ctrl_base.ipp new file mode 100644 index 000000000..136354cd2 --- /dev/null +++ b/host/include/uhd/rfnoc/node_ctrl_base.ipp @@ -0,0 +1,128 @@ +// +// 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/>. +// + +// Implements templated functions from node_ctrl_base.hpp + +#ifndef INCLUDED_LIBUHD_NODE_CTRL_BASE_IPP +#define INCLUDED_LIBUHD_NODE_CTRL_BASE_IPP + +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <boost/shared_ptr.hpp> +#include <vector> + +namespace uhd { + namespace rfnoc { + + template <typename T, bool downstream> + std::vector< boost::shared_ptr<T> > node_ctrl_base::_find_child_node() + { + typedef boost::shared_ptr<T> T_sptr; + static const size_t MAX_ITER = 20; + size_t iters = 0; + // List of return values: + std::set< T_sptr > results_s; + // To avoid cycles: + std::set< sptr > explored; + // Initialize our search queue with ourself: + std::set< sptr > search_q; + search_q.insert(shared_from_this()); + std::set< sptr > next_q; + + while (iters++ < MAX_ITER) { + next_q.clear(); + BOOST_FOREACH(const sptr &this_node, search_q) { + // Add this node to the list of explored nodes + explored.insert(this_node); + // Create set of all child nodes of this_node that are not in explored: + std::set< sptr > next_nodes; + { + node_map_t all_next_nodes = downstream ? this_node->list_downstream_nodes() : this_node->list_upstream_nodes(); + for ( + node_map_t::iterator it = all_next_nodes.begin(); + it != all_next_nodes.end(); + ++it + ) { + sptr one_next_node = it->second.lock(); + if (not one_next_node or explored.count(one_next_node)) { + continue; + } + T_sptr next_node_sptr = boost::dynamic_pointer_cast<T>(one_next_node); + if (next_node_sptr) { + results_s.insert(next_node_sptr); + } else { + next_nodes.insert(one_next_node); + } + } + } + // Add all of these nodes to the next search queue + next_q.insert(next_nodes.begin(), next_nodes.end()); + } + // If next_q is empty, we've exhausted our graph + if (next_q.empty()) { + break; + } + // Re-init the search queue + search_q = next_q; + } + + std::vector< T_sptr > results(results_s.begin(), results_s.end()); + return results; + } + + template <typename T, typename value_type, bool downstream> + value_type node_ctrl_base::_find_unique_property( + boost::function<value_type(boost::shared_ptr<T>, size_t)> get_property, + value_type NULL_VALUE, + const std::set< boost::shared_ptr<T> > &exclude_nodes + ) { + std::vector< boost::shared_ptr<T> > descendant_rate_nodes = _find_child_node<T, downstream>(); + value_type ret_val = NULL_VALUE; + std::string first_node_id; + BOOST_FOREACH(const boost::shared_ptr<T> &node, descendant_rate_nodes) { + if (exclude_nodes.count(node)) { + continue; + } + // FIXME we need to know the port!!! + size_t port = ANY_PORT; // NOOO! this is wrong!!!! FIXME + value_type this_property = get_property(node, port); + if (this_property == NULL_VALUE) { + continue; + } + // We use the first property we find as reference + if (ret_val == NULL_VALUE) { + ret_val = this_property; + first_node_id = node->unique_id(); + continue; + } + // In all subsequent finds, we make sure the property is equal to the reference + if (this_property != ret_val) { + throw uhd::runtime_error( + str( + boost::format("Node %1% specifies %2%, node %3% specifies %4%") + % first_node_id % ret_val % node->unique_id() % this_property + ) + ); + } + } + return ret_val; + } + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_NODE_CTRL_BASE_IPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/radio_ctrl.hpp b/host/include/uhd/rfnoc/radio_ctrl.hpp new file mode 100644 index 000000000..c29cc9b5a --- /dev/null +++ b/host/include/uhd/rfnoc/radio_ctrl.hpp @@ -0,0 +1,202 @@ +// +// Copyright 2015-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_HPP +#define INCLUDED_LIBUHD_RFNOC_RADIO_CTRL_HPP + +#include <uhd/types/direction.hpp> +#include <uhd/rfnoc/source_block_ctrl_base.hpp> +#include <uhd/rfnoc/sink_block_ctrl_base.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> + +namespace uhd { + namespace rfnoc { + +/*! \brief Block controller for all RFNoC-based radio blocks + */ +class UHD_RFNOC_API radio_ctrl : + public source_block_ctrl_base, + public sink_block_ctrl_base, + public rate_node_ctrl, + public tick_node_ctrl, + public terminator_node_ctrl +{ +public: + UHD_RFNOC_BLOCK_OBJECT(radio_ctrl) + + virtual ~radio_ctrl(){} + + /************************************************************************ + * API calls + ***********************************************************************/ + /*! Return the tick rate on all channels (rx and tx). + * + * \return The tick rate. + */ + virtual double get_rate() const = 0; + + /*! Set the tick/sample rate on all channels (rx and tx). + * + * Will coerce to the nearest possible rate and return the actual value. + */ + virtual double set_rate(double rate) = 0; + + /*! Return the selected TX antenna for channel \p chan. + * + * \return The selected antenna. + */ + virtual std::string get_tx_antenna(const size_t chan) /* const */ = 0; + + /*! Select RX antenna \p for channel \p chan. + * + * \throws uhd::value_error if \p ant is not a valid value. + */ + virtual void set_tx_antenna(const std::string &ant, const size_t chan) = 0; + + /*! Return the selected RX antenna for channel \p chan. + * + * \return The selected antenna. + */ + virtual std::string get_rx_antenna(const size_t chan) /* const */ = 0; + + /*! Select RX antenna \p for channel \p chan. + * + * \throws uhd::value_error if \p ant is not a valid value. + */ + virtual void set_rx_antenna(const std::string &ant, const size_t chan) = 0; + + /*! Return the current transmit LO frequency on channel \p chan. + * + * Note that the AD9361 only has one LO for all TX channels, and the + * \p chan parameter is thus only for API compatibility. + * + * \return The current LO frequency. + */ + virtual double get_tx_frequency(const size_t chan) /* const */ = 0; + + /*! Tune the TX LO for channel \p. + * + * This function will attempt to tune as close as possible, and return a + * coerced value of the actual tuning result. + * + * \return The actual LO frequency. + */ + virtual double set_tx_frequency(const double freq, size_t chan) = 0; + + /*! Return the current receive LO frequency on channel \p chan. + * + * \return The current LO frequency. + */ + virtual double get_rx_frequency(const size_t chan) /* const */ = 0; + + /*! Tune the RX LO for channel \p. + * + * This function will attempt to tune as close as possible, and return a + * coerced value of the actual tuning result. + * + * \return The actual LO frequency. + */ + virtual double set_rx_frequency(const double freq, const size_t chan) = 0; + + /*! Return the transmit gain on channel \p chan + * + * \return The actual gain value + */ + virtual double get_tx_gain(const size_t chan) = 0; + + /*! Set the transmit gain on channel \p chan + * + * This function will attempt to set the gain as close as possible, + * and return a coerced value of the actual gain value. + * + * \return The actual gain value + */ + virtual double set_tx_gain(const double gain, const size_t chan) = 0; + + /*! Return the transmit gain on channel \p chan + * + * \return The actual gain value + */ + virtual double get_rx_gain(const size_t chan) = 0; + + /*! Set the transmit gain on channel \p chan + * + * This function will attempt to set the gain as close as possible, + * and return a coerced value of the actual gain value. + * + * \return The actual gain value + */ + virtual double set_rx_gain(const double gain, const size_t chan) = 0; + + /*! Sets the time in the radio's timekeeper to the given value. + * + * Note that there is a non-deterministic delay between calling this + * function and the valung written to the register. For setting the + * time in alignment with a certain reference time, use + * set_time_next_pps(). + */ + virtual void set_time_now(const time_spec_t &time_spec) = 0; + + /*! Set the time registers at the next pps tick. + * + * The values will not be latched in until the pulse occurs. + * It is recommended that the user sleep(1) after calling to ensure + * that the time registers will be in a known state prior to use. + * + * Note: Because this call sets the time on the "next" pps, + * the seconds in the time spec should be current seconds + 1. + * + * \param time_spec the time to latch into the timekeeper + */ + virtual void set_time_next_pps(const time_spec_t &time_spec) = 0; + + /*! Get the current time in the timekeeper registers. + * + * Note that there is a non-deterministic delay between the time the + * register is read and the time the function value is returned. + * To get the time with respect to a tick edge, use get_time_last_pps(). + * + * \return A timespec representing current radio time + */ + virtual time_spec_t get_time_now() = 0; + + /*! Get the time when the last PPS pulse occurred. + * + * \return A timespec representing the last PPS + */ + virtual time_spec_t get_time_last_pps() = 0; + + /*! Given a frontend name, return the channel mapping. + * + * E.g.: For a TwinRX board, there's two frontends, '0' and '1', which + * map to channels 0 and 1 respectively. A BasicRX boards has alphabetical + * frontends (A, B) which map to channels differently. + */ + virtual size_t get_chan_from_dboard_fe(const std::string &fe, const uhd::direction_t dir) = 0; + + /*! The inverse function to get_chan_from_dboard_fe() + */ + virtual std::string get_dboard_fe_from_chan(const size_t chan, const uhd::direction_t dir) = 0; + +}; /* class radio_ctrl */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_RADIO_CTRL_HPP */ diff --git a/host/include/uhd/rfnoc/rate_node_ctrl.hpp b/host/include/uhd/rfnoc/rate_node_ctrl.hpp new file mode 100644 index 000000000..580f86fe2 --- /dev/null +++ b/host/include/uhd/rfnoc/rate_node_ctrl.hpp @@ -0,0 +1,67 @@ +// +// 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_RATE_NODE_CTRL_BASE_HPP +#define INCLUDED_LIBUHD_RATE_NODE_CTRL_BASE_HPP + +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/rfnoc/constants.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Sampling-rate-aware node control + * + * A "rate" node is a streaming node is a point in the flow graph + * that is aware of sampling rates. Such nodes include: + * - Radio Controls (these actually set a sampling rate) + * - Decimating FIR filters (their output rates depend on both + * incoming rates and their own settings, i.e. the decimation) + * - Streaming terminators (these need to know the sampling rates + * to configure the connected streamers) + */ +class UHD_RFNOC_API rate_node_ctrl; +class rate_node_ctrl : virtual public node_ctrl_base +{ +public: + /*********************************************************************** + * Types + **********************************************************************/ + typedef boost::shared_ptr<rate_node_ctrl> sptr; + //! This value is used by rate nodes that don't actually set a rate themselves + static const double RATE_UNDEFINED; + + /*********************************************************************** + * Rate controls + **********************************************************************/ + /*! Returns the sampling rate this block expects at its input. + * + * A radio will simply return the sampling rate it is set to. + * A decimating FIR filter will ask downstream for the input sampling rate + * and then return that value multiplied by the decimation factor. + * + */ + virtual double get_input_samp_rate(size_t port=ANY_PORT); + virtual double get_output_samp_rate(size_t port=ANY_PORT); + +}; /* class rate_node_ctrl */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RATE_NODE_CTRL_BASE_HPP */ +// vim: sw=4 et: + diff --git a/host/include/uhd/rfnoc/scalar_node_ctrl.hpp b/host/include/uhd/rfnoc/scalar_node_ctrl.hpp new file mode 100644 index 000000000..1b29f959e --- /dev/null +++ b/host/include/uhd/rfnoc/scalar_node_ctrl.hpp @@ -0,0 +1,74 @@ +// +// 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_SCALAR_NODE_CTRL_BASE_HPP +#define INCLUDED_LIBUHD_SCALAR_NODE_CTRL_BASE_HPP + +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/rfnoc/constants.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Scaling node control + * + * A "scalar" node is a streaming node in which a scaling takes + * place, usually for the conversion between fixed point and floating + * point (the latter usually being normalized between -1 and 1). + * + * Such blocks include: + * - Radio Controls + * - Potentially FFTs or FIRs, if they affect scaling + */ +class UHD_RFNOC_API scalar_node_ctrl; +class scalar_node_ctrl : virtual public node_ctrl_base +{ +public: + /*********************************************************************** + * Types + **********************************************************************/ + typedef boost::shared_ptr<scalar_node_ctrl> sptr; + //! Undefined scaling + static const double SCALE_UNDEFINED; + + /*********************************************************************** + * Scaling controls + **********************************************************************/ + /*! Returns the scaling factor for this block on input. + * + * A DUC block will return the scaling factor as determined by the duc + * stage. + * + * \param port Port Number + */ + virtual double get_input_scale_factor(size_t port=ANY_PORT); + + /*! Returns the scaling factor for this block on output. + * + * A DDC block will return the scaling factor as determined by the ddc + * stage. + * + * \param port Port Number + */ + virtual double get_output_scale_factor(size_t port=ANY_PORT); + +}; /* class scalar_node_ctrl */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_SCALAR_NODE_CTRL_BASE_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/sink_block_ctrl_base.hpp b/host/include/uhd/rfnoc/sink_block_ctrl_base.hpp new file mode 100644 index 000000000..ebc2370f2 --- /dev/null +++ b/host/include/uhd/rfnoc/sink_block_ctrl_base.hpp @@ -0,0 +1,126 @@ +// +// 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_TX_BLOCK_CTRL_BASE_HPP +#define INCLUDED_LIBUHD_TX_BLOCK_CTRL_BASE_HPP + +#include <uhd/rfnoc/block_ctrl_base.hpp> +#include <uhd/rfnoc/sink_node_ctrl.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Extends block_ctrl_base with input capabilities. + * + * A sink block is an RFNoC block that can receive data at an input. + * We can use this block to transmit data (In RFNoC nomenclature, a + * transmit operation means streaming data to the device from the host). + * + * Every input is defined by a port definition (port_t). + */ +class UHD_RFNOC_API sink_block_ctrl_base; +class sink_block_ctrl_base : virtual public block_ctrl_base, virtual public sink_node_ctrl +{ +public: + typedef boost::shared_ptr<sink_block_ctrl_base> sptr; + + /*********************************************************************** + * Stream signatures + **********************************************************************/ + /*! Return the input stream signature for a given block port. + * + * The actual signature is determined by the current configuration + * and the block definition file. The value returned here is calculated + * on-the-fly and is only valid as long as the configuration does not + * change. + * + * \returns The stream signature for port \p block_port + * \throws uhd::runtime_error if \p block_port is not a valid port + */ + stream_sig_t get_input_signature(size_t block_port=0) const; + + /*! Return a list of valid input ports. + */ + std::vector<size_t> get_input_ports() const; + + /*********************************************************************** + * FPGA Configuration + **********************************************************************/ + /*! Return the size of input buffer on a given block port. + * + * This is necessary for setting up flow control, among other things. + * Note: This does not query the block's settings register. The FIFO size + * is queried once during construction and cached. + * + * If the block port is not defined, it will return 0, and not throw. + * + * \param block_port The block port (0 through 15). + * + * Returns the size of the buffer in bytes. + */ + size_t get_fifo_size(size_t block_port=0) const; + + /*! Configure flow control for incoming streams. + * + * If flow control is enabled for incoming streams, this block will periodically + * send out ACKs, telling the upstream block which packets have been consumed, + * so the upstream block can increase his flow control credit. + * + * In the default implementation, this just sets registers + * SR_FLOW_CTRL_CYCS_PER_ACK and SR_FLOW_CTRL_PKTS_PER_ACK accordingly. + * + * Override this function if your block has port-specific flow control settings. + * + * \param cycles Send an ACK after this many clock cycles. + * Setting this to zero disables this type of flow control acknowledgement. + * \param packets Send an ACK after this many packets have been consumed. + * Setting this to zero disables this type of flow control acknowledgement. + * \param block_port Set up flow control for a stream coming in on this particular block port. + */ + virtual void configure_flow_control_in( + size_t cycles, + size_t packets, + size_t block_port=0 + ); + + /*! Configure the behaviour for errors on incoming packets + * (e.g. sequence errors). + * + * + */ + virtual void set_error_policy( + const std::string &policy + ); + +protected: + /*********************************************************************** + * Hooks + **********************************************************************/ + /*! Like sink_node_ctrl::_request_input_port(), but also checks + * the port has an input signature. + */ + virtual size_t _request_input_port( + const size_t suggested_port, + const uhd::device_addr_t &args + ) const; + +}; /* class sink_block_ctrl_base */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_TX_BLOCK_CTRL_BASE_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/sink_node_ctrl.hpp b/host/include/uhd/rfnoc/sink_node_ctrl.hpp new file mode 100644 index 000000000..5142a269e --- /dev/null +++ b/host/include/uhd/rfnoc/sink_node_ctrl.hpp @@ -0,0 +1,145 @@ +// +// 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_SINK_NODE_CTRL_BASE_HPP +#define INCLUDED_LIBUHD_SINK_NODE_CTRL_BASE_HPP + +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <uhd/rfnoc/sink_node_ctrl.hpp> +#include <boost/thread.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Abstract class for sink nodes. + * + * Sink nodes can have upstream blocks. + */ +class UHD_RFNOC_API sink_node_ctrl; +class sink_node_ctrl : virtual public node_ctrl_base +{ +public: + /*********************************************************************** + * Types + **********************************************************************/ + typedef boost::shared_ptr<sink_node_ctrl> sptr; + typedef std::map< size_t, boost::weak_ptr<sink_node_ctrl> > node_map_t; + typedef std::pair< size_t, boost::weak_ptr<sink_node_ctrl> > node_map_pair_t; + + /*********************************************************************** + * Sink block controls + **********************************************************************/ + /*! Connect another node upstream of this node. + * + * *Note:* If additional settings are required to make this connection work, + * e.g. configure flow control, these need to be done separately. + * + * If the requested connection is not possible, this function will throw. + * + * \p upstream_node Pointer to the node class to connect + * \p port Suggested port number on this block to connect the upstream + * block to. + * \p args Any arguments that can be useful for determining the port number. + * + * \returns The actual port number used. + */ + size_t connect_upstream( + node_ctrl_base::sptr upstream_node, + size_t port=ANY_PORT, + const uhd::device_addr_t &args=uhd::device_addr_t() + ); + + /*! Call this function to notify a node about its streamer activity. + * + * When \p active is set to true, this means this block is now part of + * an active tx streamer chain. Conversely, when set to false, this means + * the node has been removed from an tx streamer chain. + */ + virtual void set_tx_streamer(bool active, const size_t port); + + +protected: + + /*! For every input port, store tx streamer activity. + * + * If _tx_streamer_active[0] == true, this means that an active tx + * streamer is operating on port 0. If it is false, or if the entry + * does not exist, there is no streamer. + * Values are toggled by set_tx_streamer(). + */ + std::map<size_t, bool> _tx_streamer_active; + + /*! Ask for a port number to connect an upstream block to. + * + * Typically, this will be overridden for custom behaviour. + * The default is to return the suggested port, disregarding + * \p args, unless \p port == ANY_PORT, in which case the first + * unused input port is returned. + * + * When deriving this function for custom behaviour, consider: + * - The result is used to call register_upstream_node(), which + * has its own checks in place. + * - This function may throw if the arguments can't be resolved. + * The exception will propagate to the user space. + * - Alternatively, the function may return ANY_PORT to signify + * failure. + * - \p args and \p suggested_port should be treated as strong + * suggestions, but there's no reason to just return any valid + * port. + * + * *Note:* For reasons of thread safety, it is recommended to + * never, ever call this function directly. It will be used by + * connect_upstream() which will handle the connection process + * in a thread-safe manner. + * + * \param suggested_port Try and connect here. + * \param args When deciding on a port number, these arguments may be used. + * + * \returns A valid input port, or ANY_PORT on failure. + */ + virtual size_t _request_input_port( + const size_t suggested_port, + const uhd::device_addr_t &args + ) const; + +private: + /*! Makes connecting something to the input thread-safe. + */ + boost::mutex _input_mutex; + + /*! Register a node upstream of this one (i.e., a node that can send data to this node). + * + * By definition, the upstream node must of type source_node_ctrl. + * + * This saves a *weak pointer* to the upstream node and checks the port is + * available. Will throw otherwise. + * + * \param upstream_node A pointer to the node instantiation + * \param port Port number the upstream node is connected to + */ + void _register_upstream_node( + node_ctrl_base::sptr upstream_node, + size_t port + ); + +}; /* class sink_node_ctrl */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_SINK_NODE_CTRL_BASE_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/source_block_ctrl_base.hpp b/host/include/uhd/rfnoc/source_block_ctrl_base.hpp new file mode 100644 index 000000000..b3e908f72 --- /dev/null +++ b/host/include/uhd/rfnoc/source_block_ctrl_base.hpp @@ -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/>. +// + +#ifndef INCLUDED_LIBUHD_RX_BLOCK_CTRL_BASE_HPP +#define INCLUDED_LIBUHD_RX_BLOCK_CTRL_BASE_HPP + +#include <uhd/rfnoc/block_ctrl_base.hpp> +#include <uhd/rfnoc/source_node_ctrl.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Extends block_ctrl_base with receive capabilities. + * + * In RFNoC nomenclature, a receive operation means streaming + * data from the device (the crossbar) to the host. + * If a block has receive capabilities, this means we can receive + * data *from* this block. + */ +class UHD_RFNOC_API source_block_ctrl_base; +class source_block_ctrl_base : virtual public block_ctrl_base, virtual public source_node_ctrl +{ +public: + typedef boost::shared_ptr<source_block_ctrl_base> sptr; + + /*********************************************************************** + * Streaming operations + **********************************************************************/ + /*! Issue a stream command for this block. + * + * There is no guaranteed action for this command. The default implementation + * is to send this command to the next upstream block, or issue a warning if + * there is no upstream block registered. + * + * However, implementations of block_ctrl_base might choose to do whatever seems + * appropriate, including throwing exceptions. This may also be true for some + * stream commands and not for others (i.e. STREAM_MODE_START_CONTINUOUS may be + * implemented, and STREAM_MODE_NUM_SAMPS_AND_DONE may be not). + * + * This function does not check for infinite loops. Example: Say you have two blocks, + * which are both registered as upstream from one another. If they both use + * block_ctrl_base::issue_stream_cmd(), then the stream command will be passed from + * one block to another indefinitely. This will not happen if one the block's + * controller classes overrides this function and actually handles it. + * + * See also register_upstream_block(). + * + * \param stream_cmd The stream command. + */ + virtual void issue_stream_cmd(const uhd::stream_cmd_t &stream_cmd, const size_t chan=0); + + /*********************************************************************** + * Stream signatures + **********************************************************************/ + /*! Return the output stream signature for a given block port. + * + * The actual signature is determined by the current configuration + * and the block definition file. The value returned here is calculated + * on-the-fly and is only valid as long as the configuration does not + * change. + * + * \returns The stream signature for port \p block_port + * \throws uhd::runtime_error if \p block_port is not a valid port + */ + stream_sig_t get_output_signature(size_t block_port=0) const; + + /*! Return a list of valid output ports. + */ + std::vector<size_t> get_output_ports() const; + + /*********************************************************************** + * FPGA Configuration + **********************************************************************/ + /*! Configures data flowing from port \p output_block_port to go to \p next_address + * + * In the default implementation, this will write the value in \p next_address + * to register SR_NEXT_DST of this blocks settings bus. The value will also + * have bit 16 set to 1, since some blocks require this to respect this value. + */ + virtual void set_destination( + boost::uint32_t next_address, + size_t output_block_port = 0 + ); + + /*! Configure flow control for outgoing streams. + * + * In the default implementation, this just sets registers SR_FLOW_CTRL_BUF_SIZE + * and SR_FLOW_CTRL_ENABLE accordingly; \b block_port and \p sid are ignored. + * + * Override this function if your block has port-specific flow control settings. + * + * \param buf_size_pkts The size of the downstream block's input FIFO size in number of packets. Setting + * this to zero disables flow control. The block will then produce data as fast as it can. + * \b Warning: This can cause head-of-line blocking, and potentially lock up your device! + * \param Specify on which outgoing port this setting is valid. + * \param sid The SID for which this is valid. This is meant for cases where the outgoing block port is + * not sufficient to set the flow control, and as such is rarely used. + */ + virtual void configure_flow_control_out( + size_t buf_size_pkts, + size_t block_port=0, + const uhd::sid_t &sid=uhd::sid_t() + ); + + +protected: + /*********************************************************************** + * Hooks + **********************************************************************/ + /*! Like source_node_ctrl::_request_output_port(), but also checks if + * the port has an output signature. + */ + virtual size_t _request_output_port( + const size_t suggested_port, + const uhd::device_addr_t &args + ) const; + +}; /* class source_block_ctrl_base */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RX_BLOCK_CTRL_BASE_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/source_node_ctrl.hpp b/host/include/uhd/rfnoc/source_node_ctrl.hpp new file mode 100644 index 000000000..a351f6c8e --- /dev/null +++ b/host/include/uhd/rfnoc/source_node_ctrl.hpp @@ -0,0 +1,136 @@ +// +// 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_SOURCE_NODE_CTRL_BASE_HPP +#define INCLUDED_LIBUHD_SOURCE_NODE_CTRL_BASE_HPP + +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <uhd/types/stream_cmd.hpp> +#include <boost/thread.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Abstract class for source nodes. + * + * Source nodes can have downstream blocks. + */ +class UHD_RFNOC_API source_node_ctrl; +class source_node_ctrl : virtual public node_ctrl_base +{ +public: + /*********************************************************************** + * Types + **********************************************************************/ + typedef boost::shared_ptr<source_node_ctrl> sptr; + typedef std::map< size_t, boost::weak_ptr<source_node_ctrl> > node_map_t; + typedef std::pair< size_t, boost::weak_ptr<source_node_ctrl> > node_map_pair_t; + + /*********************************************************************** + * Source block controls + **********************************************************************/ + /*! Issue a stream command for this block. + * \param stream_cmd The stream command. + * \param chan Channel Index + */ + virtual void issue_stream_cmd( + const uhd::stream_cmd_t &stream_cmd, + const size_t chan=0 + ) = 0; + + /*! Connect another node downstream of this node. + * + * *Note:* If additional settings are required to make this connection work, + * e.g. configure flow control, these need to be done separately. + * + * If the requested connection is not possible, this function will throw. + * + * \p downstream_node Pointer to the node class to connect + * \p port Suggested port number on this block to connect the downstream + * block to. + * \p args Any arguments that can be useful for determining the port number. + * + * \returns The actual port number used. + */ + size_t connect_downstream( + node_ctrl_base::sptr downstream_node, + size_t port=ANY_PORT, + const uhd::device_addr_t &args=uhd::device_addr_t() + ); + + /*! Call this function to notify a node about its streamer activity. + * + * When \p active is set to true, this means this block is now part of + * an active rx streamer chain. Conversely, when set to false, this means + * the node has been removed from an rx streamer chain. + */ + virtual void set_rx_streamer(bool active, const size_t port); + +protected: + + /*! For every output port, store rx streamer activity. + * + * If _rx_streamer_active[0] == true, this means that an active rx + * streamer is operating on port 0. If it is false, or if the entry + * does not exist, there is no streamer. + * Values are toggled by set_rx_streamer(). + */ + std::map<size_t, bool> _rx_streamer_active; + + /*! Ask for a port number to connect a downstream block to. + * + * See sink_node_ctrl::_request_input_port(). This is the same + * for output. + * + * \param suggested_port Try and connect here. + * \param args When deciding on a port number, these arguments may be used. + * + * \returns A valid input port, or ANY_PORT on failure. + */ + virtual size_t _request_output_port( + const size_t suggested_port, + const uhd::device_addr_t &args + ) const; + + +private: + /*! Makes connecting something to the output thread-safe. + */ + boost::mutex _output_mutex; + + /*! Register a node downstream of this one (i.e., a node that receives data from this node). + * + * By definition, the upstream node must of type sink_node_ctrl. + * + * This saves a *weak pointer* to the downstream node and checks + * the port is available. Will throw otherwise. + * + * \param downstream_node A pointer to the node instantiation + * \param port Port number the downstream node is connected to + */ + void _register_downstream_node( + node_ctrl_base::sptr downstream_node, + size_t port + ); + +}; /* class source_node_ctrl */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_SOURCE_NODE_CTRL_BASE_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/stream_sig.hpp b/host/include/uhd/rfnoc/stream_sig.hpp new file mode 100644 index 000000000..28f2efbe7 --- /dev/null +++ b/host/include/uhd/rfnoc/stream_sig.hpp @@ -0,0 +1,88 @@ +// +// 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/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_STREAMSIG_HPP +#define INCLUDED_LIBUHD_RFNOC_STREAMSIG_HPP + +#include <iostream> +#include <uhd/config.hpp> + +namespace uhd { namespace rfnoc { + +/*! Describes a stream signature for data going to or coming from + * RFNoC ports. + * + * The stream signature may depend on a block's configuration. Even + * so, some attributes may be left undefined (e.g., a FIFO block + * works for any item type, so it doesn't need to set it). + */ +class UHD_RFNOC_API stream_sig_t { + public: + /*********************************************************************** + * Structors + ***********************************************************************/ + stream_sig_t(); + + /*********************************************************************** + * The stream signature attributes + ***********************************************************************/ + //! The data type of the individual items (e.g. 'sc16'). If undefined, set + // to empty. + std::string item_type; + + //! The vector length in multiples of items. If undefined, set to zero. + size_t vlen; + + //! Packet size in bytes. If undefined, set to zero. + size_t packet_size; + + bool is_bursty; + + /*********************************************************************** + * Helpers + ***********************************************************************/ + //! Compact string representation + std::string to_string(); + //! Pretty-print string representation + std::string to_pp_string(); + + //! Returns the number of bytes necessary to store one item. + // Note: The vector length is *not* considered here. + // + // \returns Number of bytes per item or 0 if the item type is + // undefined. + // \throws uhd::key_error if the item type is invalid. + size_t get_bytes_per_item() const; + + /*! Check if an output with signature \p output_sig could + * stream to an input signature \p input_sig. + * + * \return true if streams are compatible + */ + static bool is_compatible(const stream_sig_t &output_sig, const stream_sig_t &input_sig); +}; + +//! Shortcut for << stream_sig.to_string() +UHD_INLINE std::ostream& operator<< (std::ostream& out, stream_sig_t stream_sig) { + out << stream_sig.to_string().c_str(); + return out; +} + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_STREAMSIG_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/terminator_node_ctrl.hpp b/host/include/uhd/rfnoc/terminator_node_ctrl.hpp new file mode 100644 index 000000000..3cc4d0cb3 --- /dev/null +++ b/host/include/uhd/rfnoc/terminator_node_ctrl.hpp @@ -0,0 +1,53 @@ +// +// 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/>. +// + +#ifndef INCLUDED_LIBUHD_TERMINATOR_NODE_CTRL_BASE_HPP +#define INCLUDED_LIBUHD_TERMINATOR_NODE_CTRL_BASE_HPP + +#include <uhd/rfnoc/node_ctrl_base.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Abstract class for terminator nodes (i.e. nodes that terminate + * the flow graph). + * + * Terminator nodes have the following properties: + * - Data flowing into such a node is not propagated to any other node, and + * data coming out of this node originates in this node. + * - Chain commands are not propagated past this node. + * + * A block may be a terminator node, but have both upstream and downstream + * nodes. An example is the radio block, which can be used for Rx and Tx. + * Even if it's used for both, the data going into the radio block is not + * the data coming out. + */ +class UHD_RFNOC_API terminator_node_ctrl; +class terminator_node_ctrl : virtual public node_ctrl_base +{ +public: + /*********************************************************************** + * Types + **********************************************************************/ + typedef boost::shared_ptr<terminator_node_ctrl> sptr; + +}; /* class terminator_node_ctrl */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_TERMINATOR_NODE_CTRL_BASE_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/tick_node_ctrl.hpp b/host/include/uhd/rfnoc/tick_node_ctrl.hpp new file mode 100644 index 000000000..3f52bbad7 --- /dev/null +++ b/host/include/uhd/rfnoc/tick_node_ctrl.hpp @@ -0,0 +1,70 @@ +// +// 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_TICK_NODE_CTRL_BASE_HPP +#define INCLUDED_LIBUHD_TICK_NODE_CTRL_BASE_HPP + +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/rfnoc/constants.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Tick-rate-aware node control + * + * A "rate" node is a streaming node is a point in the flow graph + * that is aware of tick rates (time base). Such nodes include: + * - Radio Controls + * - Data generating blocks that add time stamps + */ +class UHD_RFNOC_API tick_node_ctrl; +class tick_node_ctrl : virtual public node_ctrl_base +{ +public: + /*********************************************************************** + * Types + **********************************************************************/ + typedef boost::shared_ptr<tick_node_ctrl> sptr; + + /*********************************************************************** + * Constants + **********************************************************************/ + //! This value is used by rate nodes that don't actually set a rate themselves + static const double RATE_UNDEFINED; + + /*********************************************************************** + * Rate controls + **********************************************************************/ + /*! Return a tick rate. + * + * This might be either a tick rate defined by this block (see also _get_tick_rate()) + * or it's a tick rate defined by an adjacent block. + * In that case, performs a graph search to figure out the tick rate. + */ + double get_tick_rate( + const std::set< node_ctrl_base::sptr > &_explored_nodes=std::set< node_ctrl_base::sptr >() + ); + +protected: + virtual double _get_tick_rate() { return RATE_UNDEFINED; }; + +}; /* class tick_node_ctrl */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_TICK_NODE_CTRL_BASE_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/usrp/CMakeLists.txt b/host/include/uhd/usrp/CMakeLists.txt index fd37ef560..7a7dd02f4 100644 --- a/host/include/uhd/usrp/CMakeLists.txt +++ b/host/include/uhd/usrp/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright 2010-2011,2015 Ettus Research LLC +# Copyright 2010-2011,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 diff --git a/host/include/uhd/usrp/multi_usrp.hpp b/host/include/uhd/usrp/multi_usrp.hpp index d09b5bb15..f11c382b4 100644 --- a/host/include/uhd/usrp/multi_usrp.hpp +++ b/host/include/uhd/usrp/multi_usrp.hpp @@ -35,6 +35,7 @@ #include <uhd/config.hpp> #include <uhd/device.hpp> +#include <uhd/device3.hpp> #include <uhd/deprecated.hpp> #include <uhd/types/ranges.hpp> #include <uhd/types/stream_cmd.hpp> @@ -126,10 +127,24 @@ public: /*! * Get the underlying device object. * This is needed to get access to the streaming API and properties. - * \return the device object within this single usrp + * \return the device object within this USRP */ virtual device::sptr get_device(void) = 0; + /*! Returns true if this is a generation-3 device. + */ + virtual bool is_device3(void) = 0; + + /*! + * Get the underlying device3 object. Only works for generation-3 (or later) devices. + * + * This is needed to get access to the streaming API and properties. + * + * \return The uhd::device3 object for this USRP. + * \throws uhd::type_error if this device is not actually a generation-3 device. + */ + virtual device3::sptr get_device3(void) = 0; + //! Convenience method to get a RX streamer. See also uhd::device::get_rx_stream(). virtual rx_streamer::sptr get_rx_stream(const stream_args_t &args) = 0; diff --git a/host/lib/CMakeLists.txt b/host/lib/CMakeLists.txt index d6e2ec2cd..0cd89953c 100644 --- a/host/lib/CMakeLists.txt +++ b/host/lib/CMakeLists.txt @@ -90,11 +90,12 @@ LIBUHD_REGISTER_COMPONENT("OctoClock" ENABLE_OCTOCLOCK ON "ENABLE_LIBUHD" OFF OF INCLUDE_SUBDIRECTORY(ic_reg_maps) INCLUDE_SUBDIRECTORY(types) INCLUDE_SUBDIRECTORY(convert) -INCLUDE_SUBDIRECTORY(transport) +INCLUDE_SUBDIRECTORY(rfnoc) INCLUDE_SUBDIRECTORY(usrp) INCLUDE_SUBDIRECTORY(usrp_clock) INCLUDE_SUBDIRECTORY(utils) INCLUDE_SUBDIRECTORY(experts) +INCLUDE_SUBDIRECTORY(transport) ######################################################################## # Build info @@ -121,6 +122,7 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_BINARY_DIR}/build_info.cpp ${CMAKE_CURRENT_SOURCE_DIR}/deprecated.cpp ${CMAKE_CURRENT_SOURCE_DIR}/device.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/device3.cpp ${CMAKE_CURRENT_SOURCE_DIR}/image_loader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/stream.cpp ${CMAKE_CURRENT_SOURCE_DIR}/exception.cpp diff --git a/host/lib/device3.cpp b/host/lib/device3.cpp new file mode 100644 index 000000000..3b316e8ea --- /dev/null +++ b/host/lib/device3.cpp @@ -0,0 +1,75 @@ +// +// 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/device3.hpp> +#include <uhd/utils/msg.hpp> + +using namespace uhd; +using namespace uhd::rfnoc; + +device3::sptr device3::make(const device_addr_t &hint, const size_t which) +{ + device3::sptr device3_sptr = + boost::dynamic_pointer_cast< device3 >(device::make(hint, device::USRP, which)); + if (not device3_sptr) { + throw uhd::key_error(str( + boost::format("No gen-3 devices found for ----->\n%s") % hint.to_pp_string() + )); + } + + return device3_sptr; +} + +bool device3::has_block(const rfnoc::block_id_t &block_id) const +{ + for (size_t i = 0; i < _rfnoc_block_ctrl.size(); i++) { + if (_rfnoc_block_ctrl[i]->get_block_id() == block_id) { + return true; + } + } + return false; +} + +block_ctrl_base::sptr device3::get_block_ctrl(const block_id_t &block_id) const +{ + for (size_t i = 0; i < _rfnoc_block_ctrl.size(); i++) { + if (_rfnoc_block_ctrl[i]->get_block_id() == block_id) { + return _rfnoc_block_ctrl[i]; + } + } + throw uhd::lookup_error(str(boost::format("This device does not have a block with ID: %s") % block_id.to_string())); +} + +std::vector<rfnoc::block_id_t> device3::find_blocks(const std::string &block_id_hint) const +{ + std::vector<rfnoc::block_id_t> block_ids; + for (size_t i = 0; i < _rfnoc_block_ctrl.size(); i++) { + if (_rfnoc_block_ctrl[i]->get_block_id().match(block_id_hint)) { + block_ids.push_back(_rfnoc_block_ctrl[i]->get_block_id()); + } + } + return block_ids; +} + +void device3::clear() +{ + BOOST_FOREACH(const block_ctrl_base::sptr &block, _rfnoc_block_ctrl) { + block->clear(); + } +} +// vim: sw=4 et: diff --git a/host/lib/rfnoc/CMakeLists.txt b/host/lib/rfnoc/CMakeLists.txt new file mode 100644 index 000000000..130b4173e --- /dev/null +++ b/host/lib/rfnoc/CMakeLists.txt @@ -0,0 +1,53 @@ +# +# Copyright 2014-2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +LIBUHD_APPEND_SOURCES( + # Infrastructure: + ${CMAKE_CURRENT_SOURCE_DIR}/block_ctrl_base.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/block_ctrl_base_factory.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/block_ctrl_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/blockdef_xml_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/block_id.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ctrl_iface.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/graph_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/legacy_compat.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/node_ctrl_base.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rate_node_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rx_stream_terminator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/scalar_node_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/sink_block_ctrl_base.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/sink_node_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/source_block_ctrl_base.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/source_node_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/stream_sig.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tick_node_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tx_stream_terminator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/wb_iface_adapter.cpp + # Default block control classes: + ${CMAKE_CURRENT_SOURCE_DIR}/ddc_block_ctrl_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/duc_block_ctrl_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/radio_ctrl_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dma_fifo_block_ctrl_impl +) + +INCLUDE_SUBDIRECTORY(nocscript) diff --git a/host/lib/rfnoc/block_ctrl_base.cpp b/host/lib/rfnoc/block_ctrl_base.cpp new file mode 100644 index 000000000..21bd32a1c --- /dev/null +++ b/host/lib/rfnoc/block_ctrl_base.cpp @@ -0,0 +1,587 @@ +// +// Copyright 2014-2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +// This file contains the block control functions for block controller classes. +// See block_ctrl_base_factory.cpp for discovery and factory functions. + +#include "ctrl_iface.hpp" +#include "nocscript/block_iface.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/convert.hpp> +#include <uhd/rfnoc/block_ctrl_base.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <boost/bind.hpp> + +#define UHD_BLOCK_LOG() UHD_LOGV(never) + +using namespace uhd; +using namespace uhd::rfnoc; +using std::string; + +/*********************************************************************** + * Helpers + **********************************************************************/ +//! Convert register to a peek/poke compatible address +inline boost::uint32_t _sr_to_addr(boost::uint32_t reg) { return reg * 4; }; +inline boost::uint32_t _sr_to_addr64(boost::uint32_t reg) { return reg * 8; }; // for peek64 + +/*********************************************************************** + * Structors + **********************************************************************/ +block_ctrl_base::block_ctrl_base( + const make_args_t &make_args +) : _tree(make_args.tree), + _transport_is_big_endian(make_args.is_big_endian), + _ctrl_ifaces(make_args.ctrl_ifaces), + _base_address(make_args.base_address & 0xFFF0) +{ + UHD_BLOCK_LOG() << "block_ctrl_base()" << std::endl; + + /*** Identify this block (NoC-ID, block-ID, and block definition) *******/ + // Read NoC-ID (name is passed in through make_args): + boost::uint64_t noc_id = sr_read64(SR_READBACK_REG_ID); + _block_def = blockdef::make_from_noc_id(noc_id); + if (_block_def) UHD_BLOCK_LOG() << "Found valid blockdef" << std::endl; + if (not _block_def) + _block_def = blockdef::make_from_noc_id(DEFAULT_NOC_ID); + UHD_ASSERT_THROW(_block_def); + // For the block ID, we start with block count 0 and increase until + // we get a block ID that's not already registered: + _block_id.set(make_args.device_index, make_args.block_name, 0); + while (_tree->exists("xbar/" + _block_id.get_local())) { + _block_id++; + } + UHD_BLOCK_LOG() + << "NOC ID: " << str(boost::format("0x%016X ") % noc_id) + << "Block ID: " << _block_id << std::endl; + + /*** Initialize property tree *******************************************/ + _root_path = "xbar/" + _block_id.get_local(); + _tree->create<boost::uint64_t>(_root_path / "noc_id").set(noc_id); + + /*** Reset block state *******************************************/ + clear(); + + /*** Configure ports ****************************************************/ + size_t n_valid_input_buffers = 0; + BOOST_FOREACH(const size_t ctrl_port, get_ctrl_ports()) { + // Set source addresses: + sr_write(SR_BLOCK_SID, get_address(ctrl_port), ctrl_port); + // Set sink buffer sizes: + settingsbus_reg_t reg = SR_READBACK_REG_FIFOSIZE; + uint64_t value = sr_read64(reg, ctrl_port); + size_t buf_size_log2 = value & 0xFF; + size_t buf_size_bytes = BYTES_PER_LINE * (1 << buf_size_log2); // Bytes == 8 * 2^x + if (buf_size_bytes > 0) n_valid_input_buffers++; + _tree->create<size_t>(_root_path / "input_buffer_size" / ctrl_port).set(buf_size_bytes); + } + + /*** Register names *****************************************************/ + blockdef::registers_t sregs = _block_def->get_settings_registers(); + BOOST_FOREACH(const std::string ®_name, sregs.keys()) { + if (DEFAULT_NAMED_SR.has_key(reg_name)) { + throw uhd::runtime_error(str( + boost::format("Register name %s is already defined!") + % reg_name + )); + } + _tree->create<size_t>(_root_path / "registers" / "sr" / reg_name) + .set(sregs.get(reg_name)); + } + blockdef::registers_t rbacks = _block_def->get_readback_registers(); + BOOST_FOREACH(const std::string ®_name, rbacks.keys()) { + _tree->create<size_t>(_root_path / "registers"/ "rb" / reg_name) + .set(rbacks.get(reg_name)); + } + + /*** Init I/O port definitions ******************************************/ + _init_port_defs("in", _block_def->get_input_ports()); + _init_port_defs("out", _block_def->get_output_ports()); + // FIXME this warning always fails until the input buffer code above is fixed + if (_tree->list(_root_path / "ports/in").size() != n_valid_input_buffers) { + UHD_MSG(warning) << + boost::format("[%s] defines %d input buffer sizes, but %d input ports") + % get_block_id().get() % n_valid_input_buffers % _tree->list(_root_path / "ports/in").size() + << std::endl; + } + + /*** Init default block args ********************************************/ + _nocscript_iface = nocscript::block_iface::make(this); + _init_block_args(); +} + +block_ctrl_base::~block_ctrl_base() +{ + _tree->remove(_root_path); +} + +void block_ctrl_base::_init_port_defs( + const std::string &direction, + blockdef::ports_t ports, + const size_t first_port_index +) { + size_t port_index = first_port_index; + BOOST_FOREACH(const blockdef::port_t &port_def, ports) { + fs_path port_path = _root_path / "ports" / direction / port_index; + if (not _tree->exists(port_path)) { + _tree->create<blockdef::port_t>(port_path); + } + UHD_RFNOC_BLOCK_TRACE() << "Adding port definition at " << port_path + << boost::format(": type = '%s' pkt_size = '%s' vlen = '%s'") % port_def["type"] % port_def["pkt_size"] % port_def["vlen"] + << std::endl; + _tree->access<blockdef::port_t>(port_path).set(port_def); + port_index++; + } +} + +void block_ctrl_base::_init_block_args() +{ + blockdef::args_t args = _block_def->get_args(); + fs_path arg_path = _root_path / "args"; + BOOST_FOREACH(const size_t port, get_ctrl_ports()) { + _tree->create<std::string>(arg_path / port); + } + + // First, create all nodes. + BOOST_FOREACH(const blockdef::arg_t &arg, args) { + fs_path arg_type_path = arg_path / arg["port"] / arg["name"] / "type"; + _tree->create<std::string>(arg_type_path).set(arg["type"]); + fs_path arg_val_path = arg_path / arg["port"] / arg["name"] / "value"; + if (arg["type"] == "int_vector") { throw uhd::runtime_error("not yet implemented: int_vector"); } + else if (arg["type"] == "int") { _tree->create<int>(arg_val_path); } + else if (arg["type"] == "double") { _tree->create<double>(arg_val_path); } + else if (arg["type"] == "string") { _tree->create<string>(arg_val_path); } + else { UHD_THROW_INVALID_CODE_PATH(); } + } + // Next: Create all the subscribers and coercers. + // TODO: Add coercer +#define _SUBSCRIBE_CHECK_AND_RUN(type, arg_tag, error_message) \ + _tree->access<type>(arg_val_path).add_coerced_subscriber(boost::bind((&nocscript::block_iface::run_and_check), _nocscript_iface, arg[#arg_tag], error_message)) + BOOST_FOREACH(const blockdef::arg_t &arg, args) { + fs_path arg_val_path = arg_path / arg["port"] / arg["name"] / "value"; + if (not arg["check"].empty()) { + if (arg["type"] == "string") { _SUBSCRIBE_CHECK_AND_RUN(string, check, arg["check_message"]); } + else if (arg["type"] == "int") { _SUBSCRIBE_CHECK_AND_RUN(int, check, arg["check_message"]); } + else if (arg["type"] == "double") { _SUBSCRIBE_CHECK_AND_RUN(double, check, arg["check_message"]); } + else if (arg["type"] == "int_vector") { throw uhd::runtime_error("not yet implemented: int_vector"); } + else { UHD_THROW_INVALID_CODE_PATH(); } + } + if (not arg["action"].empty()) { + if (arg["type"] == "string") { _SUBSCRIBE_CHECK_AND_RUN(string, action, ""); } + else if (arg["type"] == "int") { _SUBSCRIBE_CHECK_AND_RUN(int, action, ""); } + else if (arg["type"] == "double") { _SUBSCRIBE_CHECK_AND_RUN(double, action, ""); } + else if (arg["type"] == "int_vector") { throw uhd::runtime_error("not yet implemented: int_vector"); } + else { UHD_THROW_INVALID_CODE_PATH(); } + } + } + + // Finally: Set the values. This will call subscribers, if we have any. + BOOST_FOREACH(const blockdef::arg_t &arg, args) { + fs_path arg_val_path = arg_path / arg["port"] / arg["name"] / "value"; + if (not arg["value"].empty()) { + if (arg["type"] == "int_vector") { throw uhd::runtime_error("not yet implemented: int_vector"); } + else if (arg["type"] == "int") { _tree->access<int>(arg_val_path).set(boost::lexical_cast<int>(arg["value"])); } + else if (arg["type"] == "double") { _tree->access<double>(arg_val_path).set(boost::lexical_cast<double>(arg["value"])); } + else if (arg["type"] == "string") { _tree->access<string>(arg_val_path).set(arg["value"]); } + else { UHD_THROW_INVALID_CODE_PATH(); } + } + } +} + +/*********************************************************************** + * FPGA control & communication + **********************************************************************/ +wb_iface::sptr block_ctrl_base::get_ctrl_iface(const size_t block_port) +{ + return _ctrl_ifaces[block_port]; +} + +std::vector<size_t> block_ctrl_base::get_ctrl_ports() const +{ + std::vector<size_t> ctrl_ports; + ctrl_ports.reserve(_ctrl_ifaces.size()); + std::pair<size_t, wb_iface::sptr> it; + BOOST_FOREACH(it, _ctrl_ifaces) { + ctrl_ports.push_back(it.first); + } + return ctrl_ports; +} + +void block_ctrl_base::sr_write(const boost::uint32_t reg, const boost::uint32_t data, const size_t port) +{ + //UHD_BLOCK_LOG() << " "; + //UHD_RFNOC_BLOCK_TRACE() << boost::format("sr_write(%d, %08X, %d)") % reg % data % port << std::endl; + if (not _ctrl_ifaces.count(port)) { + throw uhd::key_error(str(boost::format("[%s] sr_write(): No such port: %d") % get_block_id().get() % port)); + } + try { + _ctrl_ifaces[port]->poke32(_sr_to_addr(reg), data); + } + catch(const std::exception &ex) { + throw uhd::io_error(str(boost::format("[%s] sr_write() failed: %s") % get_block_id().get() % ex.what())); + } +} + +void block_ctrl_base::sr_write(const std::string ®, const boost::uint32_t data, const size_t port) +{ + boost::uint32_t reg_addr = 255; + if (DEFAULT_NAMED_SR.has_key(reg)) { + reg_addr = DEFAULT_NAMED_SR[reg]; + } else { + if (not _tree->exists(_root_path / "registers" / "sr" / reg)) { + throw uhd::key_error(str( + boost::format("Unknown settings register name: %s") + % reg + )); + } + reg_addr = boost::uint32_t(_tree->access<size_t>(_root_path / "registers" / "sr" / reg).get()); + } + UHD_BLOCK_LOG() << " "; + UHD_RFNOC_BLOCK_TRACE() << boost::format("sr_write(%s, %08X) ==> ") % reg % data << std::endl; + return sr_write(reg_addr, data, port); +} + +boost::uint64_t block_ctrl_base::sr_read64(const settingsbus_reg_t reg, const size_t port) +{ + if (not _ctrl_ifaces.count(port)) { + throw uhd::key_error(str(boost::format("[%s] sr_read64(): No such port: %d") % get_block_id().get() % port)); + } + try { + return _ctrl_ifaces[port]->peek64(_sr_to_addr64(reg)); + } + catch(const std::exception &ex) { + throw uhd::io_error(str(boost::format("[%s] sr_read64() failed: %s") % get_block_id().get() % ex.what())); + } +} + +boost::uint32_t block_ctrl_base::sr_read32(const settingsbus_reg_t reg, const size_t port) +{ + if (not _ctrl_ifaces.count(port)) { + throw uhd::key_error(str(boost::format("[%s] sr_read32(): No such port: %d") % get_block_id().get() % port)); + } + try { + return _ctrl_ifaces[port]->peek32(_sr_to_addr64(reg)); + } + catch(const std::exception &ex) { + throw uhd::io_error(str(boost::format("[%s] sr_read32() failed: %s") % get_block_id().get() % ex.what())); + } +} + +boost::uint64_t block_ctrl_base::user_reg_read64(const boost::uint32_t addr, const size_t port) +{ + try { + // Set readback register address + sr_write(SR_READBACK_ADDR, addr, port); + // Read readback register via RFNoC + return sr_read64(SR_READBACK_REG_USER, port); + } + catch(const std::exception &ex) { + throw uhd::io_error(str(boost::format("%s user_reg_read64() failed: %s") % get_block_id().get() % ex.what())); + } +} + +boost::uint64_t block_ctrl_base::user_reg_read64(const std::string ®, const size_t port) +{ + if (not _tree->exists(_root_path / "registers" / "rb" / reg)) { + throw uhd::key_error(str( + boost::format("Invalid readback register name: %s") + % reg + )); + } + return user_reg_read64(boost::uint32_t( + _tree->access<size_t>(_root_path / "registers" / "rb" / reg).get() + ), port); +} + +boost::uint32_t block_ctrl_base::user_reg_read32(const boost::uint32_t addr, const size_t port) +{ + try { + // Set readback register address + sr_write(SR_READBACK_ADDR, addr, port); + // Read readback register via RFNoC + return sr_read32(SR_READBACK_REG_USER, port); + } + catch(const std::exception &ex) { + throw uhd::io_error(str(boost::format("[%s] user_reg_read32() failed: %s") % get_block_id().get() % ex.what())); + } +} + +boost::uint32_t block_ctrl_base::user_reg_read32(const std::string ®, const size_t port) +{ + if (not _tree->exists(_root_path / "registers" / "rb" / reg)) { + throw uhd::key_error(str( + boost::format("Invalid readback register name: %s") + % reg + )); + } + return user_reg_read32(boost::uint32_t( + _tree->access<size_t>(_root_path / "registers" / "sr" / reg).get() + ), port); +} + +void block_ctrl_base::set_command_time( + const time_spec_t &time_spec, + const size_t port +) { + if (port == ANY_PORT) { + BOOST_FOREACH(const size_t specific_port, get_ctrl_ports()) { + set_command_time(time_spec, specific_port); + } + return; + } + boost::shared_ptr<ctrl_iface> iface_sptr = + boost::dynamic_pointer_cast<ctrl_iface>(get_ctrl_iface(port)); + if (not iface_sptr) { + throw uhd::assertion_error(str( + boost::format("[%s] Cannot set command time on port '%d'") + % unique_id() % port + )); + } + + iface_sptr->set_time(time_spec); +} + +time_spec_t block_ctrl_base::get_command_time( + const size_t port +) { + boost::shared_ptr<ctrl_iface> iface_sptr = + boost::dynamic_pointer_cast<ctrl_iface>(get_ctrl_iface(port)); + if (not iface_sptr) { + throw uhd::assertion_error(str( + boost::format("[%s] Cannot get command time on port '%d'") + % unique_id() % port + )); + } + + return iface_sptr->get_time(); +} + +void block_ctrl_base::set_command_tick_rate( + const double tick_rate, + const size_t port +) { + if (port == ANY_PORT) { + BOOST_FOREACH(const size_t specific_port, get_ctrl_ports()) { + set_command_tick_rate(tick_rate, specific_port); + } + return; + } + boost::shared_ptr<ctrl_iface> iface_sptr = + boost::dynamic_pointer_cast<ctrl_iface>(get_ctrl_iface(port)); + if (not iface_sptr) { + throw uhd::assertion_error(str( + boost::format("[%s] Cannot set command time on port '%d'") + % unique_id() % port + )); + } + + iface_sptr->set_tick_rate(tick_rate); +} + +void block_ctrl_base::clear_command_time(const size_t port) +{ + boost::shared_ptr<ctrl_iface> iface_sptr = + boost::dynamic_pointer_cast<ctrl_iface>(get_ctrl_iface(port)); + if (not iface_sptr) { + throw uhd::assertion_error(str( + boost::format("[%s] Cannot set command time on port '%d'") + % unique_id() % port + )); + } + + iface_sptr->set_time(time_spec_t(0.0)); +} + +void block_ctrl_base::clear(const size_t /* port */) +{ + UHD_RFNOC_BLOCK_TRACE() << "block_ctrl_base::clear() " << std::endl; + // Call parent... + node_ctrl_base::clear(); + // ...then child + BOOST_FOREACH(const size_t port_index, get_ctrl_ports()) { + _clear(port_index); + } +} + +boost::uint32_t block_ctrl_base::get_address(size_t block_port) { + UHD_ASSERT_THROW(block_port < 16); + return (_base_address & 0xFFF0) | (block_port & 0xF); +} + +/*********************************************************************** + * Argument handling + **********************************************************************/ +void block_ctrl_base::set_args(const uhd::device_addr_t &args, const size_t port) +{ + BOOST_FOREACH(const std::string &key, args.keys()) { + if (_tree->exists(get_arg_path(key, port))) { + set_arg(key, args.get(key), port); + } + } +} + +void block_ctrl_base::set_arg(const std::string &key, const std::string &val, const size_t port) +{ + fs_path arg_path = get_arg_path(key, port); + if (not _tree->exists(arg_path / "value")) { + throw uhd::runtime_error(str( + boost::format("Attempting to set uninitialized argument '%s' on block '%s'") + % key % unique_id() + )); + } + + std::string type = _tree->access<std::string>(arg_path / "type").get(); + fs_path arg_val_path = arg_path / "value"; + try { + if (type == "string") { + _tree->access<std::string>(arg_val_path).set(val); + } + else if (type == "int") { + _tree->access<int>(arg_val_path).set(boost::lexical_cast<int>(val)); + } + else if (type == "double") { + _tree->access<double>(arg_val_path).set(boost::lexical_cast<double>(val)); + } + else if (type == "int_vector") { + throw uhd::runtime_error("not yet implemented: int_vector"); + } + } catch (const boost::bad_lexical_cast &) { + throw uhd::value_error(str( + boost::format("Error trying to cast value %s == '%s' to type '%s'") + % key % val % type + )); + } +} + +device_addr_t block_ctrl_base::get_args(const size_t port) const +{ + device_addr_t args; + BOOST_FOREACH(const std::string &key, _tree->list(_root_path / "args" / port)) { + args[key] = get_arg(key); + } + return args; +} + +std::string block_ctrl_base::get_arg(const std::string &key, const size_t port) const +{ + fs_path arg_path = get_arg_path(key, port); + if (not _tree->exists(arg_path / "value")) { + throw uhd::runtime_error(str( + boost::format("Attempting to get uninitialized argument '%s' on block '%s'") + % key % unique_id() + )); + } + + std::string type = _tree->access<std::string>(arg_path / "type").get(); + fs_path arg_val_path = arg_path / "value"; + if (type == "string") { + return _tree->access<std::string>(arg_val_path).get(); + } + else if (type == "int") { + return boost::lexical_cast<std::string>(_tree->access<int>(arg_val_path).get()); + } + else if (type == "double") { + return boost::lexical_cast<std::string>(_tree->access<double>(arg_val_path).get()); + } + else if (type == "int_vector") { + throw uhd::runtime_error("not yet implemented: int_vector"); + } + + UHD_THROW_INVALID_CODE_PATH(); + return ""; +} + +std::string block_ctrl_base::get_arg_type(const std::string &key, const size_t port) const +{ + fs_path arg_type_path = _root_path / "args" / port / key / "type"; + return _tree->access<std::string>(arg_type_path).get(); +} + +stream_sig_t block_ctrl_base::_resolve_port_def(const blockdef::port_t &port_def) const +{ + if (not port_def.is_valid()) { + throw uhd::runtime_error(str( + boost::format("Invalid port definition: %s") % port_def.to_string() + )); + } + + // TODO this entire section is pretty dumb at this point. Needs better + // checks. + stream_sig_t stream_sig; + // Item Type + if (port_def.is_variable("type")) { + std::string var_name = port_def["type"].substr(1); + // TODO check this is even a string + stream_sig.item_type = get_arg(var_name); + } else if (port_def.is_keyword("type")) { + throw uhd::runtime_error("keywords resolution for type not yet implemented"); + } else { + stream_sig.item_type = port_def["type"]; + } + //UHD_RFNOC_BLOCK_TRACE() << " item type: " << stream_sig.item_type << std::endl; + + // Vector length + if (port_def.is_variable("vlen")) { + std::string var_name = port_def["vlen"].substr(1); + stream_sig.vlen = boost::lexical_cast<size_t>(get_arg(var_name)); + } else if (port_def.is_keyword("vlen")) { + throw uhd::runtime_error("keywords resolution for vlen not yet implemented"); + } else { + stream_sig.vlen = boost::lexical_cast<size_t>(port_def["vlen"]); + } + //UHD_RFNOC_BLOCK_TRACE() << " vector length: " << stream_sig.vlen << std::endl; + + // Packet size + if (port_def.is_variable("pkt_size")) { + std::string var_name = port_def["pkt_size"].substr(1); + stream_sig.packet_size = boost::lexical_cast<size_t>(get_arg(var_name)); + } else if (port_def.is_keyword("pkt_size")) { + if (port_def["pkt_size"] != "%vlen") { + throw uhd::runtime_error("generic keywords resolution for pkt_size not yet implemented"); + } + if (stream_sig.vlen == 0) { + stream_sig.packet_size = 0; + } else { + if (stream_sig.item_type.empty()) { + throw uhd::runtime_error("cannot resolve pkt_size if item type is not given"); + } + size_t bpi = uhd::convert::get_bytes_per_item(stream_sig.item_type); + stream_sig.packet_size = stream_sig.vlen * bpi; + } + } else { + stream_sig.packet_size = boost::lexical_cast<size_t>(port_def["pkt_size"]); + } + //UHD_RFNOC_BLOCK_TRACE() << " packet size: " << stream_sig.vlen << std::endl; + + return stream_sig; +} + + +/*********************************************************************** + * Hooks & Derivables + **********************************************************************/ +void block_ctrl_base::_clear(const size_t port) +{ + UHD_RFNOC_BLOCK_TRACE() << "block_ctrl_base::_clear() " << std::endl; + sr_write(SR_CLEAR_TX_FC, 0x00C1EA12, port); // 'CLEAR', but we can write anything, really + sr_write(SR_CLEAR_RX_FC, 0x00C1EA12, port); // 'CLEAR', but we can write anything, really +} + +// vim: sw=4 et: diff --git a/host/lib/rfnoc/block_ctrl_base_factory.cpp b/host/lib/rfnoc/block_ctrl_base_factory.cpp new file mode 100644 index 000000000..aab2ed475 --- /dev/null +++ b/host/lib/rfnoc/block_ctrl_base_factory.cpp @@ -0,0 +1,96 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/format.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/rfnoc/blockdef.hpp> +#include <uhd/rfnoc/block_ctrl_base.hpp> + +#define UHD_FACTORY_LOG() UHD_LOGV(never) + +using namespace uhd; +using namespace uhd::rfnoc; + +typedef uhd::dict<std::string, block_ctrl_base::make_t> block_fcn_reg_t; +// Instantiate the block function registry container +UHD_SINGLETON_FCN(block_fcn_reg_t, get_block_fcn_regs); + +void block_ctrl_base::register_block( + const make_t &make, + const std::string &key +) { + if (get_block_fcn_regs().has_key(key)) { + throw uhd::runtime_error( + str(boost::format("Attempting to register an RFNoC block with key %s for the second time.") % key) + ); + } + + get_block_fcn_regs().set(key, make); +} + +/*! Look up names for blocks in XML files using NoC ID. + */ +static void lookup_block_key(boost::uint64_t noc_id, make_args_t &make_args) +{ + try { + blockdef::sptr bd = blockdef::make_from_noc_id(noc_id); + if (not bd) { + make_args.block_key = DEFAULT_BLOCK_NAME; + make_args.block_name = DEFAULT_BLOCK_NAME; + return; + } + UHD_ASSERT_THROW(bd->is_block()); + make_args.block_key = bd->get_key(); + make_args.block_name = bd->get_name(); + return; + } catch (std::exception &e) { + UHD_MSG(warning) << str(boost::format("Error while looking up name for NoC-ID %016X.\n%s") % noc_id % e.what()) << std::endl; + } + + make_args.block_key = DEFAULT_BLOCK_NAME; + make_args.block_name = DEFAULT_BLOCK_NAME; +} + + +block_ctrl_base::sptr block_ctrl_base::make( + const make_args_t &make_args_, + boost::uint64_t noc_id +) { + UHD_FACTORY_LOG() << "[RFNoC Factory] block_ctrl_base::make() " << std::endl; + make_args_t make_args = make_args_; + + // Check if a block key was specified, in this case, we *must* either + // create a specialized block controller class or throw + if (make_args.block_key.empty()) { + lookup_block_key(noc_id, make_args); + } else if (not get_block_fcn_regs().has_key(make_args.block_key)) { + throw uhd::runtime_error( + str(boost::format("No block controller class registered for key '%s'.") % make_args.block_key) + ); + } + if (not get_block_fcn_regs().has_key(make_args.block_key)) { + make_args.block_key = DEFAULT_BLOCK_NAME; + } + if (make_args.block_name.empty()) { + make_args.block_name = make_args.block_key; + } + + UHD_FACTORY_LOG() << "[RFNoC Factory] Using controller key '" << make_args.block_key << "' and block name '" << make_args.block_name << "'" << std::endl; + return get_block_fcn_regs()[make_args.block_key](make_args); +} + diff --git a/host/lib/rfnoc/block_ctrl_impl.cpp b/host/lib/rfnoc/block_ctrl_impl.cpp new file mode 100644 index 000000000..be21538f3 --- /dev/null +++ b/host/lib/rfnoc/block_ctrl_impl.cpp @@ -0,0 +1,33 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/rfnoc/block_ctrl.hpp> + +using namespace uhd::rfnoc; + +class block_ctrl_impl : public block_ctrl +{ +public: + UHD_RFNOC_BLOCK_CONSTRUCTOR(block_ctrl) + { + // nop + } + + // Very empty class, this one +}; + +UHD_RFNOC_BLOCK_REGISTER(block_ctrl, DEFAULT_BLOCK_NAME); diff --git a/host/lib/rfnoc/block_id.cpp b/host/lib/rfnoc/block_id.cpp new file mode 100644 index 000000000..55bd5e6f7 --- /dev/null +++ b/host/lib/rfnoc/block_id.cpp @@ -0,0 +1,150 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/format.hpp> +#include <boost/regex.hpp> +#include <boost/lexical_cast.hpp> +#include <uhd/exception.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <uhd/rfnoc/block_id.hpp> + +#include <iostream> + +using namespace uhd::rfnoc; + +block_id_t::block_id_t() : + _device_no(0), + _block_name(""), + _block_ctr(0) +{ +} + +block_id_t::block_id_t(const std::string &block_str) + : _device_no(0), + _block_name(""), + _block_ctr(0) +{ + if (not set(block_str)) { + throw uhd::value_error("block_id_t: Invalid block ID string."); + } +} + +block_id_t::block_id_t( + const size_t device_no, + const std::string &block_name, + const size_t block_ctr +) : _device_no(device_no), + _block_name(block_name), + _block_ctr(block_ctr) +{ + if (not is_valid_blockname(block_name)) { + throw uhd::value_error("block_id_t: Invalid block name."); + } +} + +bool block_id_t::is_valid_blockname(const std::string &block_name) +{ + return boost::regex_match(block_name, boost::regex(VALID_BLOCKNAME_REGEX)); +} + +bool block_id_t::is_valid_block_id(const std::string &block_name) +{ + return boost::regex_match(block_name, boost::regex(VALID_BLOCKID_REGEX)); +} + +std::string block_id_t::to_string() const +{ + return str(boost::format("%d/%s") + % get_device_no() + % get_local() + ); +} + +std::string block_id_t::get_local() const +{ + return str(boost::format("%s_%d") + % get_block_name() + % get_block_count() + ); +} + +uhd::fs_path block_id_t::get_tree_root() const +{ + return str(boost::format("/mboards/%d/xbar/%s") + % get_device_no() + % get_local() + ); +} + +bool block_id_t::match(const std::string &block_str) +{ + boost::cmatch matches; + if (not boost::regex_match(block_str.c_str(), matches, boost::regex(VALID_BLOCKID_REGEX))) { + return false; + } + try { + return (matches[1] == "" or boost::lexical_cast<size_t>(matches[1]) == _device_no) + and (matches[2] == "" or matches[2] == _block_name) + and (matches[3] == "" or boost::lexical_cast<size_t>(matches[3]) == _block_ctr) + and not (matches[1] == "" and matches[2] == "" and matches[3] == ""); + } catch (const std::bad_cast &e) { + return false; + } + return false; +} + +bool block_id_t::set(const std::string &new_name) +{ + boost::cmatch matches; + if (not boost::regex_match(new_name.c_str(), matches, boost::regex(VALID_BLOCKID_REGEX))) { + return false; + } + if (not (matches[1] == "")) { + _device_no = boost::lexical_cast<size_t>(matches[1]); + } + if (not (matches[2] == "")) { + _block_name = matches[2]; + } + if (not (matches[3] == "")) { + _block_ctr = boost::lexical_cast<size_t>(matches[3]); + } + return true; +} + +bool block_id_t::set( + const size_t device_no, + const std::string &block_name, + const size_t block_ctr +) { + if (not set_block_name(block_name)) { + return false; + } + set_device_no(device_no); + set_block_count(block_ctr); + return true; +} + +bool block_id_t::set_block_name(const std::string &block_name) +{ + if (not is_valid_blockname(block_name)) { + return false; + } + _block_name = block_name; + return true; +} + diff --git a/host/lib/rfnoc/blockdef_xml_impl.cpp b/host/lib/rfnoc/blockdef_xml_impl.cpp new file mode 100644 index 000000000..5ff69d512 --- /dev/null +++ b/host/lib/rfnoc/blockdef_xml_impl.cpp @@ -0,0 +1,442 @@ +// +// Copyright 2014-2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/exception.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <uhd/rfnoc/blockdef.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/paths.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/filesystem/operations.hpp> +#include <boost/property_tree/ptree.hpp> +#include <boost/property_tree/xml_parser.hpp> +#include <cstdlib> + +using namespace uhd; +using namespace uhd::rfnoc; +namespace fs = boost::filesystem; +namespace pt = boost::property_tree; + +static const fs::path XML_BLOCKS_SUBDIR("blocks"); +static const fs::path XML_COMPONENTS_SUBDIR("components"); +static const fs::path XML_EXTENSION(".xml"); + + +/**************************************************************************** + * port_t stuff + ****************************************************************************/ +const device_addr_t blockdef::port_t::PORT_ARGS( + "name," + "type," + "vlen=0," + "pkt_size=0," + "optional=0," + "bursty=0," + "port," +); + +blockdef::port_t::port_t() +{ + // This guarantees that we can access these keys + // even if they were never initialized: + BOOST_FOREACH(const std::string &key, PORT_ARGS.keys()) { + set(key, PORT_ARGS[key]); + } +} + +bool blockdef::port_t::is_variable(const std::string &key) const +{ + const std::string &val = get(key); + return (val[0] == '$'); +} + +bool blockdef::port_t::is_keyword(const std::string &key) const +{ + const std::string &val = get(key); + return (val[0] == '%'); +} + +bool blockdef::port_t::is_valid() const +{ + // Check we have all the keys: + BOOST_FOREACH(const std::string &key, PORT_ARGS.keys()) { + if (not has_key(key)) { + return false; + } + } + + // Twelve of the clock, all seems well + return true; +} + +std::string blockdef::port_t::to_string() const +{ + std::string result; + BOOST_FOREACH(const std::string &key, PORT_ARGS.keys()) { + if (has_key(key)) { + result += str(boost::format("%s=%s,") % key % get(key)); + } + } + + return result; +} + +/**************************************************************************** + * arg_t stuff + ****************************************************************************/ +const device_addr_t blockdef::arg_t::ARG_ARGS( + // List all tags/args an <arg> can have here: + "name," + "type," + "value," + "check," + "check_message," + "action," + "port=0," +); + +const std::set<std::string> blockdef::arg_t::VALID_TYPES = boost::assign::list_of + // List all tags/args a <type> can have here: + ("string") + ("int") + ("int_vector") + ("double") +; + +blockdef::arg_t::arg_t() +{ + // This guarantees that we can access these keys + // even if they were never initialized: + BOOST_FOREACH(const std::string &key, ARG_ARGS.keys()) { + set(key, ARG_ARGS[key]); + } +} + +bool blockdef::arg_t::is_valid() const +{ + // 1. Check we have all the keys: + BOOST_FOREACH(const std::string &key, ARG_ARGS.keys()) { + if (not has_key(key)) { + return false; + } + } + + // 2. Check arg type is valid + if (not get("type").empty() and not VALID_TYPES.count(get("type"))) { + return false; + } + + // Twelve of the clock, all seems well + return true; +} + +std::string blockdef::arg_t::to_string() const +{ + std::string result; + BOOST_FOREACH(const std::string &key, ARG_ARGS.keys()) { + if (has_key(key)) { + result += str(boost::format("%s=%s,") % key % get(key)); + } + } + + return result; +} + +/**************************************************************************** + * blockdef_impl stuff + ****************************************************************************/ +class blockdef_xml_impl : public blockdef +{ +public: + enum xml_repr_t { + DESCRIBES_BLOCK, + DESCRIBES_COMPONENT + }; + + //! Returns a list of base paths for the XML files. + // It is assumed that block definitions are in a subdir with name + // XML_BLOCKS_SUBDIR and component definitions in a subdir with name + // XML_COMPONENTS_SUBDIR + static std::vector<boost::filesystem::path> get_xml_paths() + { + std::vector<boost::filesystem::path> paths; + + // Path from environment variable + if (std::getenv(XML_PATH_ENV.c_str()) != NULL) { + paths.push_back(boost::filesystem::path(std::getenv(XML_PATH_ENV.c_str()))); + } + + // Finally, the default path + const boost::filesystem::path pkg_path = uhd::get_pkg_path(); + paths.push_back(pkg_path / XML_DEFAULT_PATH); + + return paths; + } + + //! Matches a NoC ID through substring matching + static bool match_noc_id(const std::string &lhs_, boost::uint64_t rhs_) + { + // Sanitize input: Make both values strings with all uppercase + // characters and no leading 0x. Check inputs are valid. + std::string lhs = boost::to_upper_copy(lhs_); + std::string rhs = str(boost::format("%016X") % rhs_); + if (lhs.size() > 2 and lhs[0] == '0' and lhs[1] == 'X') { + lhs = lhs.substr(2); + } + UHD_ASSERT_THROW(rhs.size() == 16); + if (lhs.size() < 4 or lhs.size() > 16) { + throw uhd::value_error(str(boost::format( + "%s is not a valid NoC ID (must be hexadecimal, min 4 and max 16 characters)" + ) % lhs_)); + } + + // OK, all good now. Next, we try and match the substring lhs in rhs: + return (rhs.find(lhs) == 0); + } + + //! Open the file at filename and see if it's a block definition for the given NoC ID + static bool has_noc_id(boost::uint64_t noc_id, const fs::path &filename) + { + pt::ptree propt; + try { + read_xml(filename.string(), propt); + BOOST_FOREACH(pt::ptree::value_type &v, propt.get_child("nocblock.ids")) { + if (v.first == "id" and match_noc_id(v.second.data(), noc_id)) { + return true; + } + } + } catch (std::exception &e) { + UHD_MSG(warning) << "has_noc_id(): caught exception " << e.what() << std::endl; + return false; + } + return false; + } + + blockdef_xml_impl(const fs::path &filename, boost::uint64_t noc_id, xml_repr_t type=DESCRIBES_BLOCK) : + _type(type), + _noc_id(noc_id) + { + //UHD_MSG(status) << "Reading XML file: " << filename.string().c_str() << std::endl; + read_xml(filename.string(), _pt); + try { + // Check key is valid + get_key(); + // Check name is valid + get_name(); + // Check there's at least one port + ports_t in = get_input_ports(); + ports_t out = get_output_ports(); + if (in.empty() and out.empty()) { + throw uhd::runtime_error("Block does not define inputs or outputs."); + } + // Check args are valid + get_args(); + // TODO any more checks? + } catch (const std::exception &e) { + throw uhd::runtime_error(str( + boost::format("Invalid block definition in %s: %s") + % filename.string() % e.what() + )); + } + } + + bool is_block() const + { + return _type == DESCRIBES_BLOCK; + } + + bool is_component() const + { + return _type == DESCRIBES_COMPONENT; + } + + std::string get_key() const + { + try { + return _pt.get<std::string>("nocblock.key"); + } catch (const pt::ptree_bad_path &) { + return _pt.get<std::string>("nocblock.blockname"); + } + } + + std::string get_name() const + { + return _pt.get<std::string>("nocblock.blockname"); + } + + boost::uint64_t noc_id() const + { + return _noc_id; + } + + ports_t get_input_ports() + { + return _get_ports("sink"); + } + + ports_t get_output_ports() + { + return _get_ports("source"); + } + + ports_t _get_ports(const std::string &port_type) + { + std::set<size_t> port_numbers; + size_t n_ports = 0; + ports_t ports; + BOOST_FOREACH(pt::ptree::value_type &v, _pt.get_child("nocblock.ports")) { + if (v.first != port_type) continue; + // Now we have the correct sink or source node: + port_t port; + BOOST_FOREACH(const std::string &key, port_t::PORT_ARGS.keys()) { + port[key] = v.second.get(key, port_t::PORT_ARGS[key]); + } + // We have to be extra-careful with the port numbers: + if (port["port"].empty()) { + port["port"] = boost::lexical_cast<std::string>(n_ports); + } + size_t new_port_number; + try { + new_port_number = boost::lexical_cast<size_t>(port["port"]); + } catch (const boost::bad_lexical_cast &e) { + throw uhd::value_error(str( + boost::format("Invalid port number '%s' on port '%s'") + % port["port"] % port["name"] + )); + } + if (port_numbers.count(new_port_number) or new_port_number > MAX_NUM_PORTS) { + throw uhd::value_error(str( + boost::format("Port '%s' has invalid port number %d!") + % port["name"] % new_port_number + )); + } + port_numbers.insert(new_port_number); + n_ports++; + ports.push_back(port); + } + return ports; + } + + std::vector<size_t> get_all_port_numbers() + { + std::set<size_t> set_ports; + BOOST_FOREACH(const port_t &port, get_input_ports()) { + set_ports.insert(boost::lexical_cast<size_t>(port["port"])); + } + BOOST_FOREACH(const port_t &port, get_output_ports()) { + set_ports.insert(boost::lexical_cast<size_t>(port["port"])); + } + return std::vector<size_t>(set_ports.begin(), set_ports.end()); + } + + + blockdef::args_t get_args() + { + args_t args; + bool is_valid = true; + pt::ptree def; + BOOST_FOREACH(pt::ptree::value_type &v, _pt.get_child("nocblock.args", def)) { + arg_t arg; + if (v.first != "arg") continue; + BOOST_FOREACH(const std::string &key, arg_t::ARG_ARGS.keys()) { + arg[key] = v.second.get(key, arg_t::ARG_ARGS[key]); + } + if (arg["type"].empty()) { + arg["type"] = "string"; + } + if (not arg.is_valid()) { + UHD_MSG(warning) << boost::format("Found invalid argument: %s") % arg.to_string() << std::endl; + is_valid = false; + } + args.push_back(arg); + } + if (not is_valid) { + throw uhd::runtime_error(str( + boost::format("Found invalid arguments for block %s.") + % get_name() + )); + } + return args; + } + + registers_t get_settings_registers() + { + return _get_regs("setreg"); + } + + registers_t get_readback_registers() + { + return _get_regs("readback"); + } + + registers_t _get_regs(const std::string ®_type) + { + registers_t registers; + pt::ptree def; + BOOST_FOREACH(pt::ptree::value_type &v, _pt.get_child("nocblock.registers", def)) { + if (v.first != reg_type) continue; + registers[v.second.get<std::string>("name")] = + boost::lexical_cast<size_t>(v.second.get<size_t>("address")); + } + return registers; + } + + +private: + + //! Tells us if is this for a NoC block, or a component. + const xml_repr_t _type; + //! The NoC-ID as reported (there may be several valid NoC IDs, this is the one used) + const boost::uint64_t _noc_id; + + //! This is a boost property tree, not the same as + // our property tree. + pt::ptree _pt; + +}; + +blockdef::sptr blockdef::make_from_noc_id(boost::uint64_t noc_id) +{ + std::vector<fs::path> paths = blockdef_xml_impl::get_xml_paths(); + // Iterate over all paths + BOOST_FOREACH(const fs::path &base_path, paths) { + fs::path this_path = base_path / XML_BLOCKS_SUBDIR; + if (not fs::exists(this_path) or not fs::is_directory(this_path)) { + continue; + } + // Iterate over all .xml files + fs::directory_iterator end_itr; + for (fs::directory_iterator i(this_path); i != end_itr; ++i) { + if (not fs::exists(*i) or fs::is_directory(*i) or fs::is_empty(*i)) { + continue; + } + if (i->path().filename().extension() != XML_EXTENSION) { + continue; + } + if (blockdef_xml_impl::has_noc_id(noc_id, i->path())) { + return blockdef::sptr(new blockdef_xml_impl(i->path(), noc_id)); + } + } + } + + return blockdef::sptr(); +} +// vim: sw=4 et: diff --git a/host/lib/rfnoc/ctrl_iface.cpp b/host/lib/rfnoc/ctrl_iface.cpp new file mode 100644 index 000000000..4518e88cf --- /dev/null +++ b/host/lib/rfnoc/ctrl_iface.cpp @@ -0,0 +1,376 @@ +// +// Copyright 2012-2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "ctrl_iface.hpp" +#include "async_packet_handler.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/types/sid.hpp> +#include <uhd/transport/chdr.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/thread/thread.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <queue> + +using namespace uhd; +using namespace uhd::rfnoc; +using namespace uhd::transport; + +static const double ACK_TIMEOUT = 2.0; //supposed to be worst case practical timeout +static const double MASSIVE_TIMEOUT = 10.0; //for when we wait on a timed command +static const size_t SR_READBACK = 32; + +ctrl_iface::~ctrl_iface(void){ + /* NOP */ +} + +class ctrl_iface_impl: public ctrl_iface +{ +public: + + ctrl_iface_impl(const bool big_endian, + uhd::transport::zero_copy_if::sptr ctrl_xport, + uhd::transport::zero_copy_if::sptr resp_xport, + const boost::uint32_t sid, const std::string &name + ) : + _link_type(vrt::if_packet_info_t::LINK_TYPE_CHDR), + _packet_type(vrt::if_packet_info_t::PACKET_TYPE_CONTEXT), + _bige(big_endian), + _ctrl_xport(ctrl_xport), _resp_xport(resp_xport), + _sid(sid), + _name(name), + _seq_out(0), + _timeout(ACK_TIMEOUT), + _resp_queue(128/*max response msgs*/), + _resp_queue_size(_resp_xport ? _resp_xport->get_num_recv_frames() : 3), + _rb_address(uhd::rfnoc::SR_READBACK) + { + if (resp_xport) { + while (resp_xport->get_recv_buff(0.0)) {} //flush + } + this->set_time(uhd::time_spec_t(0.0)); + this->set_tick_rate(1.0); //something possible but bogus + } + + ~ctrl_iface_impl(void) + { + _timeout = ACK_TIMEOUT; //reset timeout to something small + UHD_SAFE_CALL( + this->peek32(0);//dummy peek with the purpose of ack'ing all packets + _async_task.reset();//now its ok to release the task + ) + } + + /******************************************************************* + * Peek and poke 32 bit implementation + ******************************************************************/ + void poke32(const wb_addr_type addr, const boost::uint32_t data) + { + boost::mutex::scoped_lock lock(_mutex); + this->send_pkt(addr/4, data); + this->wait_for_ack(false); + } + + boost::uint32_t peek32(const wb_addr_type addr) + { + boost::mutex::scoped_lock lock(_mutex); + this->send_pkt(_rb_address, addr/8); + const boost::uint64_t res = this->wait_for_ack(true); + const boost::uint32_t lo = boost::uint32_t(res & 0xffffffff); + const boost::uint32_t hi = boost::uint32_t(res >> 32); + return ((addr/4) & 0x1)? hi : lo; + } + + boost::uint64_t peek64(const wb_addr_type addr) + { + boost::mutex::scoped_lock lock(_mutex); + this->send_pkt(_rb_address, addr/8); + return this->wait_for_ack(true); + } + + /******************************************************************* + * Update methods for time + ******************************************************************/ + void set_time(const uhd::time_spec_t &time) + { + boost::mutex::scoped_lock lock(_mutex); + _time = time; + _use_time = _time != uhd::time_spec_t(0.0); + if (_use_time) _timeout = MASSIVE_TIMEOUT; //permanently sets larger timeout + } + + uhd::time_spec_t get_time(void) + { + boost::mutex::scoped_lock lock(_mutex); + return _time; + } + + void set_tick_rate(const double rate) + { + boost::mutex::scoped_lock lock(_mutex); + _tick_rate = rate; + } + +private: + // This is the buffer type for messages in radio control core. + struct resp_buff_type + { + boost::uint32_t data[8]; + }; + + /******************************************************************* + * Primary control and interaction private methods + ******************************************************************/ + UHD_INLINE void send_pkt(const boost::uint32_t addr, const boost::uint32_t data = 0) + { + managed_send_buffer::sptr buff = _ctrl_xport->get_send_buff(0.0); + if (not buff) { + throw uhd::runtime_error("fifo ctrl timed out getting a send buffer"); + } + boost::uint32_t *pkt = buff->cast<boost::uint32_t *>(); + + //load packet info + vrt::if_packet_info_t packet_info; + packet_info.link_type = _link_type; + packet_info.packet_type = _packet_type; + packet_info.num_payload_words32 = 2; + packet_info.num_payload_bytes = packet_info.num_payload_words32*sizeof(boost::uint32_t); + packet_info.packet_count = _seq_out; + packet_info.tsf = _time.to_ticks(_tick_rate); + packet_info.sob = false; + packet_info.eob = false; + packet_info.sid = _sid; + packet_info.has_sid = true; + packet_info.has_cid = false; + packet_info.has_tsi = false; + packet_info.has_tsf = _use_time; + packet_info.has_tlr = false; + + //load header + if (_bige) vrt::if_hdr_pack_be(pkt, packet_info); + else vrt::if_hdr_pack_le(pkt, packet_info); + + //load payload + pkt[packet_info.num_header_words32+0] = (_bige)? uhd::htonx(addr) : uhd::htowx(addr); + pkt[packet_info.num_header_words32+1] = (_bige)? uhd::htonx(data) : uhd::htowx(data); + //UHD_MSG(status) << boost::format("0x%08x, 0x%08x\n") % addr % data; + //send the buffer over the interface + _outstanding_seqs.push(_seq_out); + buff->commit(sizeof(boost::uint32_t)*(packet_info.num_packet_words32)); + + _seq_out++;//inc seq for next call + } + + UHD_INLINE boost::uint64_t wait_for_ack(const bool readback) + { + while (readback or (_outstanding_seqs.size() >= _resp_queue_size)) + { + //get seq to ack from outstanding packets list + UHD_ASSERT_THROW(not _outstanding_seqs.empty()); + const size_t seq_to_ack = _outstanding_seqs.front(); + _outstanding_seqs.pop(); + + //parse the packet + vrt::if_packet_info_t packet_info; + resp_buff_type resp_buff; + memset(&resp_buff, 0x00, sizeof(resp_buff)); + boost::uint32_t const *pkt = NULL; + managed_recv_buffer::sptr buff; + + //get buffer from response endpoint - or die in timeout + if (_resp_xport) + { + buff = _resp_xport->get_recv_buff(_timeout); + try + { + UHD_ASSERT_THROW(bool(buff)); + UHD_ASSERT_THROW(buff->size() > 0); + } + catch(const std::exception &ex) + { + throw uhd::io_error(str(boost::format("Radio ctrl (%s) no response packet - %s") % _name % ex.what())); + } + pkt = buff->cast<const boost::uint32_t *>(); + packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); + } + + //get buffer from response endpoint - or die in timeout + else + { + /* + * Couldn't get message with haste. + * Now check both possible queues for messages. + * Messages should come in on _resp_queue, + * but could end up in dump_queue. + * If we don't get a message --> Die in timeout. + */ + double accum_timeout = 0.0; + const double short_timeout = 0.005; // == 5ms + while(not ((_resp_queue.pop_with_haste(resp_buff)) + || (check_dump_queue(resp_buff)) + || (_resp_queue.pop_with_timed_wait(resp_buff, short_timeout)) + )){ + /* + * If a message couldn't be received within a given timeout + * --> throw AssertionError! + */ + accum_timeout += short_timeout; + UHD_ASSERT_THROW(accum_timeout < _timeout); + } + + pkt = resp_buff.data; + packet_info.num_packet_words32 = sizeof(resp_buff)/sizeof(boost::uint32_t); + } + + //parse the buffer + try + { + packet_info.link_type = _link_type; + if (_bige) vrt::chdr::if_hdr_unpack_be(pkt, packet_info); + else vrt::chdr::if_hdr_unpack_le(pkt, packet_info); + } + catch(const std::exception &ex) + { + UHD_MSG(error) << "[" << _name << "] Radio ctrl bad VITA packet: " << ex.what() << std::endl; + if (buff){ + UHD_MSG(status) << boost::format("%08X") % pkt[0] << std::endl; + UHD_MSG(status) << boost::format("%08X") % pkt[1] << std::endl; + UHD_MSG(status) << boost::format("%08X") % pkt[2] << std::endl; + UHD_MSG(status) << boost::format("%08X") % pkt[3] << std::endl; + } + else{ + UHD_MSG(status) << "buff is NULL" << std::endl; + } + } + + //check the buffer + try + { + UHD_ASSERT_THROW(packet_info.has_sid); + if (packet_info.sid != boost::uint32_t((_sid >> 16) | (_sid << 16))) { + throw uhd::io_error( + str( + boost::format("Expected SID: %s Received SID: %s") + % uhd::sid_t(_sid).reversed().to_pp_string_hex() + % uhd::sid_t(packet_info.sid).to_pp_string_hex() + ) + ); + } + + if (packet_info.packet_count != (seq_to_ack & 0xfff)) { + throw uhd::io_error( + str( + boost::format("Expected packet index: %d Received index: %d") + % packet_info.packet_count + % (seq_to_ack & 0xfff) + ) + ); + } + + UHD_ASSERT_THROW(packet_info.num_payload_words32 == 2); + //UHD_ASSERT_THROW(packet_info.packet_type == _packet_type); + } + catch(const std::exception &ex) + { + throw uhd::io_error(str(boost::format("Radio ctrl (%s) packet parse error - %s") % _name % ex.what())); + } + + //return the readback value + if (readback and _outstanding_seqs.empty()) + { + const boost::uint64_t hi = (_bige)? uhd::ntohx(pkt[packet_info.num_header_words32+0]) : uhd::wtohx(pkt[packet_info.num_header_words32+0]); + const boost::uint64_t lo = (_bige)? uhd::ntohx(pkt[packet_info.num_header_words32+1]) : uhd::wtohx(pkt[packet_info.num_header_words32+1]); + return ((hi << 32) | lo); + } + } + + return 0; + } + + /* + * If ctrl_core waits for a message that didn't arrive it can search for it in the dump queue. + * This actually happens during shutdown. + * handle_async_task can't access queue anymore thus it returns the corresponding message. + * msg_task class implements a dump_queue to store such messages. + * With check_dump_queue we can check if a message we are waiting for got stranded there. + * If a message got stuck we get it here and push it onto our own message_queue. + */ + bool check_dump_queue(resp_buff_type& b) { + const size_t min_buff_size = 8; // Same value as in b200_io_impl->handle_async_task + boost::uint32_t recv_sid = (((_sid)<<16)|((_sid)>>16)); + uhd::msg_task::msg_payload_t msg; + do{ + msg = _async_task->get_msg_from_dump_queue(recv_sid); + } + while(msg.size() < min_buff_size && msg.size() != 0); + + if(msg.size() >= min_buff_size) { + memcpy(b.data, &msg.front(), std::min(msg.size(), sizeof(b.data))); + return true; + } + return false; + } + + void push_response(const boost::uint32_t *buff) + { + resp_buff_type resp_buff; + std::memcpy(resp_buff.data, buff, sizeof(resp_buff)); + _resp_queue.push_with_haste(resp_buff); + } + + void hold_task(uhd::msg_task::sptr task) + { + _async_task = task; + } + + const vrt::if_packet_info_t::link_type_t _link_type; + const vrt::if_packet_info_t::packet_type_t _packet_type; + const bool _bige; + const uhd::transport::zero_copy_if::sptr _ctrl_xport; + const uhd::transport::zero_copy_if::sptr _resp_xport; + uhd::msg_task::sptr _async_task; + const boost::uint32_t _sid; + const std::string _name; + boost::mutex _mutex; + size_t _seq_out; + uhd::time_spec_t _time; + bool _use_time; + double _tick_rate; + double _timeout; + std::queue<size_t> _outstanding_seqs; + bounded_buffer<resp_buff_type> _resp_queue; + const size_t _resp_queue_size; + + const size_t _rb_address; +}; + +ctrl_iface::sptr ctrl_iface::make( + const bool big_endian, + zero_copy_if::sptr ctrl_xport, + zero_copy_if::sptr resp_xport, + const boost::uint32_t sid, + const std::string &name +) { + return sptr(new ctrl_iface_impl( + big_endian, ctrl_xport, resp_xport, sid, name + )); +} diff --git a/host/lib/rfnoc/ctrl_iface.hpp b/host/lib/rfnoc/ctrl_iface.hpp new file mode 100644 index 000000000..4141b6583 --- /dev/null +++ b/host/lib/rfnoc/ctrl_iface.hpp @@ -0,0 +1,68 @@ +// +// Copyright 2012-2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_CTRL_IFACE_HPP +#define INCLUDED_LIBUHD_RFNOC_CTRL_IFACE_HPP + +#include <uhd/utils/msg_task.hpp> +#include <uhd/types/time_spec.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <uhd/types/wb_iface.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <string> + +namespace uhd { namespace rfnoc { + +/*! + * Provide access to peek, poke for the radio ctrl module + */ +class ctrl_iface : public uhd::timed_wb_iface +{ +public: + typedef boost::shared_ptr<ctrl_iface> sptr; + + virtual ~ctrl_iface(void) = 0; + + //! Make a new control object + static sptr make( + const bool big_endian, + uhd::transport::zero_copy_if::sptr ctrl_xport, + uhd::transport::zero_copy_if::sptr resp_xport, + const boost::uint32_t sid, + const std::string &name = "0" + ); + + //! Hold a ref to a task thats feeding push response + virtual void hold_task(uhd::msg_task::sptr task) = 0; + + //! Push a response externall (resp_xport is NULL) + virtual void push_response(const boost::uint32_t *buff) = 0; + + //! Set the command time that will activate + virtual void set_time(const uhd::time_spec_t &time) = 0; + + //! Get the command time that will activate + virtual uhd::time_spec_t get_time(void) = 0; + + //! Set the tick rate (converting time into ticks) + virtual void set_tick_rate(const double rate) = 0; +}; + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_CTRL_IFACE_HPP */ diff --git a/host/lib/rfnoc/ddc_block_ctrl_impl.cpp b/host/lib/rfnoc/ddc_block_ctrl_impl.cpp new file mode 100644 index 000000000..2aac22ca4 --- /dev/null +++ b/host/lib/rfnoc/ddc_block_ctrl_impl.cpp @@ -0,0 +1,281 @@ +// +// Copyright 2016 Ettus Research +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "dsp_core_utils.hpp" +#include <uhd/rfnoc/ddc_block_ctrl.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/convert.hpp> +#include <uhd/types/ranges.hpp> +#include <boost/math/special_functions/round.hpp> +#include <cmath> + +using namespace uhd::rfnoc; + +// TODO move this to a central location +template <class T> T ceil_log2(T num){ + return std::ceil(std::log(num)/std::log(T(2))); +} + +// TODO remove this once we have actual lambdas +static double lambda_forward_prop(uhd::property_tree::sptr tree, uhd::fs_path prop, double value) +{ + return tree->access<double>(prop).set(value).get(); +} + +static double lambda_forward_prop(uhd::property_tree::sptr tree, uhd::fs_path prop) +{ + return tree->access<double>(prop).get(); +} + +class ddc_block_ctrl_impl : public ddc_block_ctrl +{ +public: + static const size_t NUM_HALFBANDS = 3; + static const size_t CIC_MAX_DECIM = 255; + + UHD_RFNOC_BLOCK_CONSTRUCTOR(ddc_block_ctrl) + { + // Argument/prop tree hooks + for (size_t chan = 0; chan < get_input_ports().size(); chan++) { + double default_freq = get_arg<double>("freq", chan); + _tree->access<double>(get_arg_path("freq/value", chan)) + .set_coercer(boost::bind(&ddc_block_ctrl_impl::set_freq, this, _1, chan)) + .set(default_freq); + ; + double default_output_rate = get_arg<double>("output_rate", chan); + _tree->access<double>(get_arg_path("output_rate/value", chan)) + .set_coercer(boost::bind(&ddc_block_ctrl_impl::set_output_rate, this, _1, chan)) + .set(default_output_rate) + ; + _tree->access<double>(get_arg_path("input_rate/value", chan)) + .add_coerced_subscriber(boost::bind(&ddc_block_ctrl_impl::set_input_rate, this, _1, chan)) + ; + + // Legacy properties (for backward compat w/ multi_usrp) + const uhd::fs_path dsp_base_path = _root_path / "legacy_api" / chan; + // Legacy properties + _tree->create<double>(dsp_base_path / "rate/value") + .set_coercer(boost::bind(&lambda_forward_prop, _tree, get_arg_path("output_rate/value", chan), _1)) + .set_publisher(boost::bind(&lambda_forward_prop, _tree, get_arg_path("output_rate/value", chan))) + ; + _tree->create<uhd::meta_range_t>(dsp_base_path / "rate/range") + .set_publisher(boost::bind(&ddc_block_ctrl_impl::get_output_rates, this)) + ; + _tree->create<double>(dsp_base_path / "freq/value") + .set_coercer(boost::bind(&lambda_forward_prop, _tree, get_arg_path("freq/value", chan), _1)) + .set_publisher(boost::bind(&lambda_forward_prop, _tree, get_arg_path("freq/value", chan))) + ; + _tree->create<uhd::meta_range_t>(dsp_base_path / "freq/range") + .set_publisher(boost::bind(&ddc_block_ctrl_impl::get_freq_range, this)) + ; + _tree->access<uhd::time_spec_t>("time/cmd") + .add_coerced_subscriber(boost::bind(&block_ctrl_base::set_command_time, this, _1, chan)) + ; + if (_tree->exists("tick_rate")) { + const double tick_rate = _tree->access<double>("tick_rate").get(); + set_command_tick_rate(tick_rate, chan); + _tree->access<double>("tick_rate") + .add_coerced_subscriber(boost::bind(&block_ctrl_base::set_command_tick_rate, this, _1, chan)) + ; + } + + // Rate 1:1 by default + sr_write("N", 1, chan); + sr_write("M", 1, chan); + sr_write("CONFIG", 1, chan); // Enable clear EOB + } + } // end ctor + virtual ~ddc_block_ctrl_impl() {}; + + double get_output_scale_factor(size_t port=ANY_PORT) + { + port = port == ANY_PORT ? 0 : port; + if (not (_rx_streamer_active.count(port) and _rx_streamer_active.at(port))) { + return SCALE_UNDEFINED; + } + return get_arg<double>("scalar_correction", port); + } + + double get_input_samp_rate(size_t port=ANY_PORT) + { + port = port == ANY_PORT ? 0 : port; + if (not (_tx_streamer_active.count(port) and _tx_streamer_active.at(port))) { + return RATE_UNDEFINED; + } + return get_arg<double>("input_rate", port); + } + + double get_output_samp_rate(size_t port=ANY_PORT) + { + port = port == ANY_PORT ? 0 : port; + if (not (_rx_streamer_active.count(port) and _rx_streamer_active.at(port))) { + return RATE_UNDEFINED; + } + return get_arg<double>("output_rate", port); + } + + + void issue_stream_cmd( + const uhd::stream_cmd_t &stream_cmd_, + const size_t chan + ) { + UHD_RFNOC_BLOCK_TRACE() << "ddc_block_ctrl_base::issue_stream_cmd()" << std::endl; + + if (list_upstream_nodes().count(chan) == 0) { + UHD_MSG(status) << "No upstream blocks." << std::endl; + return; + } + + uhd::stream_cmd_t stream_cmd = stream_cmd_; + if (stream_cmd.stream_mode == uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE or + stream_cmd.stream_mode == uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE) { + size_t decimation = get_arg<double>("input_rate", chan) / get_arg<double>("output_rate", chan); + stream_cmd.num_samps *= decimation; + } + + source_node_ctrl::sptr this_upstream_block_ctrl = + boost::dynamic_pointer_cast<source_node_ctrl>(list_upstream_nodes().at(chan).lock()); + if (this_upstream_block_ctrl) { + this_upstream_block_ctrl->issue_stream_cmd( + stream_cmd, + get_upstream_port(chan) + ); + } + } + +private: + + //! Set the CORDIC frequency shift the signal to \p requested_freq + double set_freq(const double requested_freq, const size_t chan) + { + const double input_rate = get_arg<double>("input_rate"); + double actual_freq; + int32_t freq_word; + get_freq_and_freq_word(requested_freq, input_rate, actual_freq, freq_word); + sr_write("CORDIC_FREQ", uint32_t(freq_word), chan); + return actual_freq; + } + + //! Return a range of valid frequencies the CORDIC can tune to + uhd::meta_range_t get_freq_range(void) + { + const double input_rate = get_arg<double>("input_rate"); + return uhd::meta_range_t( + -input_rate/2, + +input_rate/2, + input_rate/std::pow(2.0, 32) + ); + } + + // FIXME this misses a whole bunch of valid rates. Anything with CIC decim <= 255 + // is OK. + uhd::meta_range_t get_output_rates(void) + { + uhd::meta_range_t range; + const double input_rate = get_arg<double>("input_rate"); + for (int decim = 1024; decim > 512; decim -= 8){ + range.push_back(uhd::range_t(input_rate/decim)); + } + for (int decim = 512; decim > 256; decim -= 4){ + range.push_back(uhd::range_t(input_rate/decim)); + } + for (int decim = 256; decim > 128; decim -= 2){ + range.push_back(uhd::range_t(input_rate/decim)); + } + for (int decim = 128; decim >= 1; decim -= 1){ + range.push_back(uhd::range_t(input_rate/decim)); + } + return range; + } + + double set_output_rate(const int requested_rate, const size_t chan) + { + const double input_rate = get_arg<double>("input_rate"); + const size_t decim_rate = boost::math::iround(input_rate/this->get_output_rates().clip(requested_rate, true)); + size_t decim = decim_rate; + + // The FPGA knows which halfbands to enable for any given value of hb_enable. + uint32_t hb_enable = 0; + while ((decim % 2 == 0) and hb_enable < NUM_HALFBANDS) { + hb_enable++; + decim /= 2; + } + UHD_ASSERT_THROW(hb_enable <= NUM_HALFBANDS); + UHD_ASSERT_THROW(decim <= CIC_MAX_DECIM); + // What we can't cover with halfbands, we do with the CIC + sr_write("DECIM_WORD", (hb_enable << 8) | (decim & 0xff), chan); + + // Rate change = M/N + sr_write("N", std::pow(2.0, double(hb_enable)) * (decim & 0xff), chan); + sr_write("M", 1, chan); + + if (decim > 1 and hb_enable == 0) { + UHD_MSG(warning) << boost::format( + "The requested decimation is odd; the user should expect passband CIC rolloff.\n" + "Select an even decimation to ensure that a halfband filter is enabled.\n" + "Decimations factorable by 4 will enable 2 halfbands, those factorable by 8 will enable 3 halfbands.\n" + "decimation = dsp_rate/samp_rate -> %d = (%f MHz)/(%f MHz)\n" + ) % decim_rate % (input_rate/1e6) % (requested_rate/1e6); + } + + // Caclulate algorithmic gain of CIC for a given decimation. + // For Ettus CIC R=decim, M=1, N=4. Gain = (R * M) ^ N + const double rate_pow = std::pow(double(decim & 0xff), 4); + // Calculate compensation gain values for algorithmic gain of CORDIC and CIC taking into account + // gain compensation blocks already hardcoded in place in DDC (that provide simple 1/2^n gain compensation). + // CORDIC algorithmic gain limits asymptotically around 1.647 after many iterations. + static const double CORDIC_GAIN = 1.648; + // + // The polar rotation of [I,Q] = [1,1] by Pi/8 also yields max magnitude of SQRT(2) (~1.4142) however + // input to the CORDIC thats outside the unit circle can only be sourced from a saturated RF frontend. + // To provide additional dynamic range head room accordingly using scale factor applied at egress from DDC would + // cost us small signal performance, thus we do no provide compensation gain for a saturated front end and allow + // the signal to clip in the H/W as needed. If we wished to avoid the signal clipping in these circumstances then adjust code to read: + // _scaling_adjustment = std::pow(2, ceil_log2(rate_pow))/(CORDIC_GAIN*rate_pow*1.415); + const double scaling_adjustment = std::pow(2, ceil_log2(rate_pow))/(CORDIC_GAIN*rate_pow); + update_scalar(scaling_adjustment, chan); + return input_rate/decim_rate; + } + + //! Set frequency and decimation again + void set_input_rate(const double /* rate */, const size_t chan) + { + const double desired_freq = _tree->access<double>(get_arg_path("freq", chan) / "value").get_desired(); + set_arg<double>("freq", desired_freq, chan); + const double desired_output_rate = _tree->access<double>(get_arg_path("output_rate", chan) / "value").get_desired(); + set_arg<double>("output_rate", desired_output_rate, chan); + } + + // Calculate compensation gain values for algorithmic gain of CORDIC and CIC taking into account + // gain compensation blocks already hardcoded in place in DDC (that provide simple 1/2^n gain compensation). + // Further more factor in OTW format which adds further gain factor to weight output samples correctly. + void update_scalar(const double scalar, const size_t chan) + { + const double target_scalar = (1 << 15) * scalar; + const int32_t actual_scalar = boost::math::iround(target_scalar); + // Calculate the error introduced by using integer representation for the scalar, can be corrected in host later. + const double scalar_correction = + target_scalar / actual_scalar / double(1 << 15) // Rounding error, normalized to 1.0 + * get_arg<double>("fullscale"); // Scaling requested by host + set_arg<double>("scalar_correction", scalar_correction, chan); + // Write DDC with scaling correction for CIC and CORDIC that maximizes dynamic range in 32/16/12/8bits. + sr_write("SCALE_IQ", actual_scalar, chan); + } + +}; + +UHD_RFNOC_BLOCK_REGISTER(ddc_block_ctrl, "DDC"); diff --git a/host/lib/rfnoc/dma_fifo_block_ctrl_impl.cpp b/host/lib/rfnoc/dma_fifo_block_ctrl_impl.cpp new file mode 100644 index 000000000..5f476074b --- /dev/null +++ b/host/lib/rfnoc/dma_fifo_block_ctrl_impl.cpp @@ -0,0 +1,122 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/rfnoc/dma_fifo_block_ctrl.hpp> +#include "dma_fifo_core_3000.hpp" +#include "wb_iface_adapter.hpp" +#include <uhd/convert.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/types/wb_iface.hpp> +#include <boost/make_shared.hpp> +#include <boost/thread/mutex.hpp> + +using namespace uhd; +using namespace uhd::rfnoc; + +//TODO (Ashish): This should come from the framework +static const double BUS_CLK_RATE = 166.67e6; + +class dma_fifo_block_ctrl_impl : public dma_fifo_block_ctrl +{ +public: + static const uint32_t DEFAULT_SIZE = 32*1024*1024; + + UHD_RFNOC_BLOCK_CONSTRUCTOR(dma_fifo_block_ctrl) + { + _perifs.resize(get_input_ports().size()); + for(size_t i = 0; i < _perifs.size(); i++) { + _perifs[i].ctrl = boost::make_shared<wb_iface_adapter>( + // poke32 functor + boost::bind( + static_cast< void (block_ctrl_base::*)(const boost::uint32_t, const boost::uint32_t, const size_t) >(&block_ctrl_base::sr_write), + this, _1, _2, i + ), + // peek32 functor + boost::bind( + static_cast< boost::uint32_t (block_ctrl_base::*)(const boost::uint32_t, const size_t) >(&block_ctrl_base::user_reg_read32), + this, + _1, i + ), + // peek64 functor + boost::bind( + static_cast< boost::uint64_t (block_ctrl_base::*)(const boost::uint32_t, const size_t) >(&block_ctrl_base::user_reg_read64), + this, + _1, i + ) + ); + static const uint32_t USER_SR_BASE = 128*4; + static const uint32_t USER_RB_BASE = 0; //Don't care + _perifs[i].base_addr = DEFAULT_SIZE*i; + _perifs[i].depth = DEFAULT_SIZE; + _perifs[i].core = dma_fifo_core_3000::make(_perifs[i].ctrl, USER_SR_BASE, USER_RB_BASE); + _perifs[i].core->resize(_perifs[i].base_addr, _perifs[i].depth); + UHD_MSG(status) << boost::format("[DMA FIFO] Running BIST for FIFO %d... ") % i; + if (_perifs[i].core->ext_bist_supported()) { + boost::uint32_t bisterr = _perifs[i].core->run_bist(); + if (bisterr != 0) { + throw uhd::runtime_error(str(boost::format("BIST failed! (code: %d)\n") % bisterr)); + } else { + double throughput = _perifs[i].core->get_bist_throughput(BUS_CLK_RATE); + UHD_MSG(status) << (boost::format("pass (Throughput: %.1fMB/s)") % (throughput/1e6)) << std::endl; + } + } else { + if (_perifs[i].core->run_bist() == 0) { + UHD_MSG(status) << "pass\n"; + } else { + throw uhd::runtime_error("BIST failed!\n"); + } + } + _tree->access<int>(get_arg_path("base_addr/value", i)) + .add_coerced_subscriber(boost::bind(&dma_fifo_block_ctrl_impl::resize, this, _1, boost::ref(_perifs[i].depth), i)) + .set(_perifs[i].base_addr) + ; + _tree->access<int>(get_arg_path("depth/value", i)) + .add_coerced_subscriber(boost::bind(&dma_fifo_block_ctrl_impl::resize, this, boost::ref(_perifs[i].base_addr), _1, i)) + .set(_perifs[i].depth) + ; + } + } + + void resize(const uint32_t base_addr, const uint32_t depth, const size_t chan) { + boost::lock_guard<boost::mutex> lock(_config_mutex); + _perifs[chan].base_addr = base_addr; + _perifs[chan].depth = depth; + _perifs[chan].core->resize(base_addr, depth); + } + + uint32_t get_base_addr(const size_t chan) const { + return _perifs[chan].base_addr; + } + + uint32_t get_depth(const size_t chan) const { + return _perifs[chan].depth; + } + +private: + struct fifo_perifs_t + { + wb_iface::sptr ctrl; + dma_fifo_core_3000::sptr core; + uint32_t base_addr; + uint32_t depth; + }; + std::vector<fifo_perifs_t> _perifs; + + boost::mutex _config_mutex; +}; + +UHD_RFNOC_BLOCK_REGISTER(dma_fifo_block_ctrl, "DmaFIFO"); diff --git a/host/lib/rfnoc/duc_block_ctrl_impl.cpp b/host/lib/rfnoc/duc_block_ctrl_impl.cpp new file mode 100644 index 000000000..0340ba0d6 --- /dev/null +++ b/host/lib/rfnoc/duc_block_ctrl_impl.cpp @@ -0,0 +1,268 @@ +// +// Copyright 2016 Ettus Research +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "dsp_core_utils.hpp" +#include <uhd/rfnoc/duc_block_ctrl.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/convert.hpp> +#include <uhd/types/ranges.hpp> +#include <boost/math/special_functions/round.hpp> +#include <cmath> + +using namespace uhd::rfnoc; + +// TODO move this to a central location +template <class T> T ceil_log2(T num){ + return std::ceil(std::log(num)/std::log(T(2))); +} + +// TODO remove this once we have actual lambdas +static double lambda_forward_prop(uhd::property_tree::sptr tree, uhd::fs_path prop, double value) +{ + return tree->access<double>(prop).set(value).get(); +} + +static double lambda_forward_prop(uhd::property_tree::sptr tree, uhd::fs_path prop) +{ + return tree->access<double>(prop).get(); +} + +class duc_block_ctrl_impl : public duc_block_ctrl +{ +public: + static const size_t NUM_HALFBANDS = 2; + static const size_t CIC_MAX_INTERP = 128; + + UHD_RFNOC_BLOCK_CONSTRUCTOR(duc_block_ctrl) + { + // Argument/prop tree hooks + for (size_t chan = 0; chan < get_input_ports().size(); chan++) { + double default_freq = get_arg<double>("freq", chan); + _tree->access<double>(get_arg_path("freq/value", chan)) + .set_coercer(boost::bind(&duc_block_ctrl_impl::set_freq, this, _1, chan)) + .set(default_freq); + ; + double default_input_rate = get_arg<double>("input_rate", chan); + _tree->access<double>(get_arg_path("input_rate/value", chan)) + .set_coercer(boost::bind(&duc_block_ctrl_impl::set_input_rate, this, _1, chan)) + .set(default_input_rate) + ; + _tree->access<double>(get_arg_path("output_rate/value", chan)) + .add_coerced_subscriber(boost::bind(&duc_block_ctrl_impl::set_output_rate, this, _1, chan)) + ; + + // Legacy properties (for backward compat w/ multi_usrp) + const uhd::fs_path dsp_base_path = _root_path / "legacy_api" / chan; + // Legacy properties + _tree->create<double>(dsp_base_path / "rate/value") + .set_coercer(boost::bind(&lambda_forward_prop, _tree, get_arg_path("input_rate/value", chan), _1)) + .set_publisher(boost::bind(&lambda_forward_prop, _tree, get_arg_path("input_rate/value", chan))) + ; + _tree->create<uhd::meta_range_t>(dsp_base_path / "rate/range") + .set_publisher(boost::bind(&duc_block_ctrl_impl::get_input_rates, this)) + ; + _tree->create<double>(dsp_base_path / "freq/value") + .set_coercer(boost::bind(&lambda_forward_prop, _tree, get_arg_path("freq/value", chan), _1)) + .set_publisher(boost::bind(&lambda_forward_prop, _tree, get_arg_path("freq/value", chan))) + ; + _tree->create<uhd::meta_range_t>(dsp_base_path / "freq/range") + .set_publisher(boost::bind(&duc_block_ctrl_impl::get_freq_range, this)) + ; + _tree->access<uhd::time_spec_t>("time/cmd") + .add_coerced_subscriber(boost::bind(&block_ctrl_base::set_command_time, this, _1, chan)) + ; + if (_tree->exists("tick_rate")) { + const double tick_rate = _tree->access<double>("tick_rate").get(); + set_command_tick_rate(tick_rate, chan); + _tree->access<double>("tick_rate") + .add_coerced_subscriber(boost::bind(&block_ctrl_base::set_command_tick_rate, this, _1, chan)) + ; + } + + // Rate 1:1 by default + sr_write("N", 1, chan); + sr_write("M", 1, chan); + sr_write("CONFIG", 1, chan); // Enable clear EOB + } + } // end ctor + virtual ~duc_block_ctrl_impl() {}; + + double get_input_scale_factor(size_t port=ANY_PORT) + { + port = (port == ANY_PORT) ? 0 : port; + if (not (_tx_streamer_active.count(port) and _tx_streamer_active.at(port))) { + return SCALE_UNDEFINED; + } + return get_arg<double>("scalar_correction", port); + } + + double get_input_samp_rate(size_t port=ANY_PORT) + { + port = (port == ANY_PORT) ? 0 : port; + if (not (_tx_streamer_active.count(port) and _tx_streamer_active.at(port))) { + return RATE_UNDEFINED; + } + return get_arg<double>("input_rate", port); + } + + double get_output_samp_rate(size_t port=ANY_PORT) + { + port = (port == ANY_PORT) ? 0 : port; + if (not (_tx_streamer_active.count(port) and _tx_streamer_active.at(port))) { + return RATE_UNDEFINED; + } + return get_arg<double>("output_rate", port == ANY_PORT ? 0 : port); + } + + void issue_stream_cmd( + const uhd::stream_cmd_t &stream_cmd_, + const size_t chan + ) { + UHD_RFNOC_BLOCK_TRACE() << "duc_block_ctrl_base::issue_stream_cmd()" << std::endl; + + uhd::stream_cmd_t stream_cmd = stream_cmd_; + if (stream_cmd.stream_mode == uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE or + stream_cmd.stream_mode == uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE) { + size_t interpolation = get_arg<double>("output_rate", chan) / get_arg<double>("input_rate", chan); + stream_cmd.num_samps *= interpolation; + } + + BOOST_FOREACH(const node_ctrl_base::node_map_pair_t upstream_node, list_upstream_nodes()) { + source_node_ctrl::sptr this_upstream_block_ctrl = + boost::dynamic_pointer_cast<source_node_ctrl>(upstream_node.second.lock()); + this_upstream_block_ctrl->issue_stream_cmd(stream_cmd, chan); + } + } + +private: + + //! Set the CORDIC frequency shift the signal to \p requested_freq + double set_freq(const double requested_freq, const size_t chan) + { + const double output_rate = get_arg<double>("output_rate"); + double actual_freq; + int32_t freq_word; + get_freq_and_freq_word(requested_freq, output_rate, actual_freq, freq_word); + // Xilinx CORDIC uses a different format for the phase increment, hence the divide-by-four: + sr_write("CORDIC_FREQ", uint32_t(freq_word/4), chan); + return actual_freq; + } + + //! Return a range of valid frequencies the CORDIC can tune to + uhd::meta_range_t get_freq_range(void) + { + const double output_rate = get_arg<double>("output_rate"); + return uhd::meta_range_t( + -output_rate/2, + +output_rate/2, + output_rate/std::pow(2.0, 32) + ); + } + + uhd::meta_range_t get_input_rates(void) + { + uhd::meta_range_t range; + const double output_rate = get_arg<double>("output_rate"); + for (int rate = 512; rate > 256; rate -= 4){ + range.push_back(uhd::range_t(output_rate/rate)); + } + for (int rate = 256; rate > 128; rate -= 2){ + range.push_back(uhd::range_t(output_rate/rate)); + } + for (int rate = 128; rate >= 1; rate -= 1){ + range.push_back(uhd::range_t(output_rate/rate)); + } + return range; + } + + double set_input_rate(const int requested_rate, const size_t chan) + { + const double output_rate = get_arg<double>("output_rate", chan); + const size_t interp_rate = boost::math::iround(output_rate/get_input_rates().clip(requested_rate, true)); + size_t interp = interp_rate; + + uint32_t hb_enable = 0; + while ((interp % 2 == 0) and hb_enable < NUM_HALFBANDS) { + hb_enable++; + interp /= 2; + } + UHD_ASSERT_THROW(hb_enable <= NUM_HALFBANDS); + UHD_ASSERT_THROW(interp > 0 and interp <= CIC_MAX_INTERP); + // hacky hack: Unlike the DUC, the DUC actually simply has 2 + // flags to enable either halfband. + uint32_t hb_enable_word = hb_enable; + if (hb_enable == 2) { + hb_enable_word = 3; + } + hb_enable_word <<= 8; + // What we can't cover with halfbands, we do with the CIC + sr_write("INTERP_WORD", hb_enable_word | (interp & 0xff), chan); + + // Rate change = M/N + sr_write("N", 1, chan); + sr_write("M", std::pow(2.0, double(hb_enable)) * (interp & 0xff), chan); + + if (interp > 1 and hb_enable == 0) { + UHD_MSG(warning) << boost::format( + "The requested interpolation is odd; the user should expect passband CIC rolloff.\n" + "Select an even interpolation to ensure that a halfband filter is enabled.\n" + "interpolation = dsp_rate/samp_rate -> %d = (%f MHz)/(%f MHz)\n" + ) % interp_rate % (output_rate/1e6) % (requested_rate/1e6); + } + + // Calculate algorithmic gain of CIC for a given interpolation + // For Ettus CIC R=interp, M=1, N=4. Gain = (R * M) ^ (N - 1) + const int CIC_N = 4; + const double rate_pow = std::pow(double(interp & 0xff), CIC_N - 1); + + // Experimentally determined value to scale the output to [-1, 1] + // This must also encompass the CORDIC gain + static const double CONSTANT_GAIN = 1.1644; + + const double scaling_adjustment = std::pow(2, ceil_log2(rate_pow))/(CONSTANT_GAIN*rate_pow); + update_scalar(scaling_adjustment, chan); + return output_rate/interp_rate; + } + + //! Set frequency and interpolation again + void set_output_rate(const double /* rate */, const size_t chan) + { + const double desired_freq = _tree->access<double>(get_arg_path("freq", chan) / "value").get_desired(); + set_arg<double>("freq", desired_freq, chan); + const double desired_input_rate = _tree->access<double>(get_arg_path("input_rate", chan) / "value").get_desired(); + set_arg<double>("input_rate", desired_input_rate, chan); + } + + // Calculate compensation gain values for algorithmic gain of CORDIC and CIC taking into account + // gain compensation blocks already hardcoded in place in DUC (that provide simple 1/2^n gain compensation). + // Further more factor in OTW format which adds further gain factor to weight output samples correctly. + void update_scalar(const double scalar, const size_t chan) + { + const double target_scalar = (1 << 15) * scalar; + const int32_t actual_scalar = boost::math::iround(target_scalar); + // Calculate the error introduced by using integer representation for the scalar + const double scalar_correction = + actual_scalar / target_scalar * (double(1 << 15) - 1.0) // Rounding error, normalized to 1.0 + * get_arg<double>("fullscale"); // Scaling requested by host + set_arg<double>("scalar_correction", scalar_correction, chan); + // Write DUC with scaling correction for CIC and CORDIC that maximizes dynamic range in 32/16/12/8bits. + sr_write("SCALE_IQ", actual_scalar, chan); + } +}; + +UHD_RFNOC_BLOCK_REGISTER(duc_block_ctrl, "DUC"); + diff --git a/host/lib/rfnoc/graph_impl.cpp b/host/lib/rfnoc/graph_impl.cpp new file mode 100644 index 000000000..fdf4fdc78 --- /dev/null +++ b/host/lib/rfnoc/graph_impl.cpp @@ -0,0 +1,164 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "graph_impl.hpp" +#include <uhd/rfnoc/source_block_ctrl_base.hpp> +#include <uhd/rfnoc/sink_block_ctrl_base.hpp> +#include <uhd/utils/msg.hpp> + +using namespace uhd::rfnoc; + +/**************************************************************************** + * Structors + ***************************************************************************/ +graph_impl::graph_impl( + const std::string &name, + boost::weak_ptr<uhd::device3> device_ptr + //async_msg_handler::sptr msg_handler +) : _name(name) + , _device_ptr(device_ptr) +{ + +} + + +/**************************************************************************** + * Connection API + ***************************************************************************/ +void graph_impl::connect( + const block_id_t &src_block, + size_t src_block_port, + const block_id_t &dst_block, + size_t dst_block_port, + const size_t pkt_size_ +) { + device3::sptr device_ptr = _device_ptr.lock(); + if (not device_ptr) { + throw uhd::runtime_error("Invalid device"); + } + + uhd::rfnoc::source_block_ctrl_base::sptr src = device_ptr->get_block_ctrl<rfnoc::source_block_ctrl_base>(src_block); + uhd::rfnoc::sink_block_ctrl_base::sptr dst = device_ptr->get_block_ctrl<rfnoc::sink_block_ctrl_base>(dst_block); + + /******************************************************************** + * 1. Draw the edges (logically connect the nodes) + ********************************************************************/ + size_t actual_src_block_port = src->connect_downstream( + boost::dynamic_pointer_cast<uhd::rfnoc::node_ctrl_base>(dst), + src_block_port + ); + if (src_block_port == uhd::rfnoc::ANY_PORT) { + src_block_port = actual_src_block_port; + } else if (src_block_port != actual_src_block_port) { + throw uhd::runtime_error(str( + boost::format("Can't connect to port %d on block %s.") + % src_block_port % src->unique_id() + )); + } + size_t actual_dst_block_port = dst->connect_upstream( + boost::dynamic_pointer_cast<uhd::rfnoc::node_ctrl_base>(src), + dst_block_port + ); + if (dst_block_port == uhd::rfnoc::ANY_PORT) { + dst_block_port = actual_dst_block_port; + } else if (dst_block_port != actual_dst_block_port) { + throw uhd::runtime_error(str( + boost::format("Can't connect to port %d on block %s.") + % dst_block_port % dst->unique_id() + )); + } + src->set_downstream_port(actual_src_block_port, actual_dst_block_port); + dst->set_upstream_port(actual_dst_block_port, actual_src_block_port); + // At this point, ports are locked and no one else can simply connect + // into them. + UHD_MSG(status) + << "[" << _name << "] Connecting " + << src_block << ":" << actual_src_block_port << " --> " + << dst_block << ":" << actual_dst_block_port << std::endl; + + /******************************************************************** + * 2. Check IO signatures match + ********************************************************************/ + if (not rfnoc::stream_sig_t::is_compatible( + src->get_output_signature(actual_src_block_port), + dst->get_input_signature(actual_dst_block_port) + )) { + throw uhd::runtime_error(str( + boost::format("Can't connect block %s to %s: IO signature mismatch\n(%s is incompatible with %s).") + % src->get_block_id().get() % dst->get_block_id().get() + % src->get_output_signature(actual_src_block_port) + % dst->get_input_signature(actual_dst_block_port) + )); + } + + /******************************************************************** + * 3. Configure the source block's destination + ********************************************************************/ + // Calculate SID + sid_t sid = dst->get_address(dst_block_port); + sid.set_src(src->get_address(src_block_port)); + + // Set SID on source block + src->set_destination(sid.get(), src_block_port); + + /******************************************************************** + * 4. Configure flow control + ********************************************************************/ + size_t pkt_size = (pkt_size_ != 0) ? pkt_size_ : src->get_output_signature(src_block_port).packet_size; + if (pkt_size == 0) { // Unspecified packet rate. Assume max packet size. + UHD_MSG(status) << "Assuming max packet size for " << src->get_block_id() << std::endl; + pkt_size = uhd::rfnoc::MAX_PACKET_SIZE; + } + // FC window (in packets) depends on FIFO size... ...and packet size. + size_t buf_size_pkts = dst->get_fifo_size(dst_block_port) / pkt_size; + if (buf_size_pkts == 0) { + throw uhd::runtime_error(str( + boost::format("Input FIFO for block %s is too small (%d kiB) for packets of size %d kiB\n" + "coming from block %s.") + % dst->get_block_id().get() % (dst->get_fifo_size(dst_block_port) / 1024) + % (pkt_size / 1024) % src->get_block_id().get() + )); + } + src->configure_flow_control_out(buf_size_pkts, src_block_port); + // On the same crossbar, use lots of FC packets + size_t pkts_per_ack = std::min( + uhd::rfnoc::DEFAULT_FC_XBAR_PKTS_PER_ACK, + buf_size_pkts - 1 + ); + // Over the network, use less or we'd flood the transport + if (sid.get_src_addr() != sid.get_dst_addr()) { + pkts_per_ack = std::max<size_t>(buf_size_pkts / uhd::rfnoc::DEFAULT_FC_TX_RESPONSE_FREQ, 1); + } + dst->configure_flow_control_in( + 0, // Default to not use cycles + pkts_per_ack, + dst_block_port + ); + + /******************************************************************** + * 5. Configure error policy + ********************************************************************/ + dst->set_error_policy("next_burst"); +} + +void graph_impl::connect( + const block_id_t &src_block, + const block_id_t &dst_block +) { + connect(src_block, ANY_PORT, dst_block, ANY_PORT); +} + diff --git a/host/lib/rfnoc/graph_impl.hpp b/host/lib/rfnoc/graph_impl.hpp new file mode 100644 index 000000000..12dbf6357 --- /dev/null +++ b/host/lib/rfnoc/graph_impl.hpp @@ -0,0 +1,76 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_GRAPH_IMPL_HPP +#define INCLUDED_LIBUHD_RFNOC_GRAPH_IMPL_HPP + +#include <uhd/rfnoc/graph.hpp> +#include <uhd/device3.hpp> + +namespace uhd { namespace rfnoc { + +class graph_impl : public graph +{ +public: + /*! + * \param name An optional name to describe this graph + * \param device_ptr Weak pointer to the originating device3 + * \param msg_handler Pointer to the async message handler + */ + graph_impl( + const std::string &name, + boost::weak_ptr<uhd::device3> device_ptr + //async_msg_handler::sptr msg_handler + ); + virtual ~graph_impl() {}; + + /************************************************************************ + * Connection API + ***********************************************************************/ + void connect( + const block_id_t &src_block, + size_t src_block_port, + const block_id_t &dst_block, + size_t dst_block_port, + const size_t pkt_size = 0 + ); + + void connect( + const block_id_t &src_block, + const block_id_t &dst_block + ); + + /************************************************************************ + * Utilities + ***********************************************************************/ + std::string get_name() const { return _name; } + + +private: + + //! Optional: A string to describe this graph + const std::string _name; + + //! Reference to the generating device object + const boost::weak_ptr<uhd::device3> _device_ptr; + +}; + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_GRAPH_IMPL_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/legacy_compat.cpp b/host/lib/rfnoc/legacy_compat.cpp new file mode 100644 index 000000000..915432f22 --- /dev/null +++ b/host/lib/rfnoc/legacy_compat.cpp @@ -0,0 +1,700 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "legacy_compat.hpp" +#include <uhd/property_tree.hpp> +#include <uhd/rfnoc/radio_ctrl.hpp> +#include <uhd/rfnoc/ddc_block_ctrl.hpp> +#include <uhd/rfnoc/graph.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/stream.hpp> +#include <uhd/types/stream_cmd.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/transport/chdr.hpp> +#include <boost/make_shared.hpp> + +#define UHD_LEGACY_LOG() UHD_LOGV(never) + +using namespace uhd::rfnoc; +using uhd::usrp::subdev_spec_t; +using uhd::usrp::subdev_spec_pair_t; +using uhd::stream_cmd_t; + +/************************************************************************ + * Constants + ***********************************************************************/ +static const std::string RADIO_BLOCK_NAME = "Radio"; +static const std::string DFIFO_BLOCK_NAME = "DmaFIFO"; +static const std::string DDC_BLOCK_NAME = "DDC"; +static const std::string DUC_BLOCK_NAME = "DUC"; +static const size_t MAX_BYTES_PER_HEADER = + uhd::transport::vrt::chdr::max_if_hdr_words64 * sizeof(uint64_t); +static const size_t BYTES_PER_SAMPLE = 4; // We currently only support sc16 + +/************************************************************************ + * Static helpers + ***********************************************************************/ +static uhd::fs_path mb_root(const size_t mboard) +{ + return uhd::fs_path("/mboards") / mboard; +} + +size_t num_ports(const uhd::property_tree::sptr &tree, const std::string &block_name, const std::string &in_out) +{ + return tree->list( + uhd::fs_path("/mboards/0/xbar") / + str(boost::format("%s_0") % block_name) / + "ports" / in_out + ).size(); +} + +size_t calc_num_tx_chans_per_radio( + const uhd::property_tree::sptr &tree, + const size_t num_radios_per_board, + const bool has_ducs, + const bool has_dmafifo +) { + const size_t num_radio_ports = num_ports(tree, RADIO_BLOCK_NAME, "in"); + if (has_ducs) { + return std::min( + num_radio_ports, + num_ports(tree, DUC_BLOCK_NAME, "in") + ); + } + + if (not has_dmafifo) { + return num_radio_ports; + } + + const size_t num_dmafifo_ports_per_radio = num_ports(tree, DFIFO_BLOCK_NAME, "in") / num_radios_per_board; + UHD_ASSERT_THROW(num_dmafifo_ports_per_radio); + + return std::min( + num_radio_ports, + num_dmafifo_ports_per_radio + ); +} + +double lambda_const_double(const double d) +{ + return d; +} + +uhd::meta_range_t lambda_const_meta_range(const double start, const double stop, const double step) +{ + return uhd::meta_range_t(start, stop, step); +} +/************************************************************************ + * Class Definition + ***********************************************************************/ +class legacy_compat_impl : public legacy_compat +{ +public: + /************************************************************************ + * Structors and Initialization + ***********************************************************************/ + legacy_compat_impl( + uhd::device3::sptr device, + const uhd::device_addr_t &args + ) : _device(device), + _tree(device->get_tree()), + _has_ducs(not args.has_key("skip_duc") and not device->find_blocks(DUC_BLOCK_NAME).empty()), + _has_ddcs(not args.has_key("skip_ddc") and not device->find_blocks(DDC_BLOCK_NAME).empty()), + _has_dmafifo(not args.has_key("skip_dram") and not device->find_blocks(DFIFO_BLOCK_NAME).empty()), + _num_mboards(_tree->list("/mboards").size()), + _num_radios_per_board(device->find_blocks<radio_ctrl>("0/Radio").size()), // These might throw, maybe we catch that and provide a nicer error message. + _num_tx_chans_per_radio( + calc_num_tx_chans_per_radio(_tree, _num_radios_per_board, _has_ducs, not device->find_blocks(DFIFO_BLOCK_NAME).empty()) + ), + _num_rx_chans_per_radio(_has_ddcs ? + std::min(num_ports(_tree, RADIO_BLOCK_NAME, "out"), num_ports(_tree, DDC_BLOCK_NAME, "out")) + : num_ports(_tree, RADIO_BLOCK_NAME, "out")), + _spp(get_block_ctrl<radio_ctrl>(0, RADIO_BLOCK_NAME, 0)->get_arg<int>("spp")), + _rx_channel_map(_num_mboards, std::vector<radio_port_pair_t>(_num_radios_per_board)), + _tx_channel_map(_num_mboards, std::vector<radio_port_pair_t>(_num_radios_per_board)) + { + _device->clear(); + check_available_periphs(); // Throws if invalid configuration. + setup_prop_tree(); + connect_blocks(); + if (args.has_key("skip_ddc")) { + UHD_LEGACY_LOG() << "[legacy_compat] Skipping DDCs by user request." << std::endl; + } else if (not _has_ddcs) { + UHD_MSG(warning) + << "[legacy_compat] No DDCs detected. You will only be able to receive at the radio frontend rate." + << std::endl; + } + if (args.has_key("skip_duc")) { + UHD_LEGACY_LOG() << "[legacy_compat] Skipping DUCs by user request." << std::endl; + } else if (not _has_ducs) { + UHD_MSG(warning) << "[legacy_compat] No DUCs detected. You will only be able to transmit at the radio frontend rate." << std::endl; + } + if (args.has_key("skip_dram")) { + UHD_LEGACY_LOG() << "[legacy_compat] Skipping DRAM by user request." << std::endl; + } else if (not _has_dmafifo) { + UHD_MSG(warning) << "[legacy_compat] No DMA FIFO detected. You will only be able to transmit at slow rates." << std::endl; + } + + for (size_t mboard = 0; mboard < _num_mboards; mboard++) { + for (size_t radio = 0; radio < _num_radios_per_board; radio++) { + _rx_channel_map[mboard][radio].radio_index = radio; + _tx_channel_map[mboard][radio].radio_index = radio; + } + + const double tick_rate = _tree->access<double>(mb_root(mboard) / "tick_rate").get(); + update_tick_rate_on_blocks(tick_rate, mboard); + } + } + + /************************************************************************ + * API Calls + ***********************************************************************/ + uhd::fs_path rx_dsp_root(const size_t mboard_idx, const size_t chan) + { + // The DSP index is the same as the radio index + size_t dsp_index = _rx_channel_map[mboard_idx][chan].radio_index; + size_t port_index = _rx_channel_map[mboard_idx][chan].port_index; + + if (not _has_ddcs) { + return mb_root(mboard_idx) / "rx_dsps" / dsp_index / port_index; + } + + return mb_root(mboard_idx) / "xbar" / + str(boost::format("%s_%d") % DDC_BLOCK_NAME % dsp_index) / + "legacy_api" / port_index; + } + + uhd::fs_path tx_dsp_root(const size_t mboard_idx, const size_t chan) + { + // The DSP index is the same as the radio index + size_t dsp_index = _tx_channel_map[mboard_idx][chan].radio_index; + size_t port_index = _tx_channel_map[mboard_idx][chan].port_index; + + if (not _has_ducs) { + return mb_root(mboard_idx) / "tx_dsps" / dsp_index / port_index; + } + + return mb_root(mboard_idx) / "xbar" / + str(boost::format("%s_%d") % DUC_BLOCK_NAME % dsp_index) / + "legacy_api" / port_index; + } + + uhd::fs_path rx_fe_root(const size_t mboard_idx, const size_t chan) + { + size_t radio_index = _rx_channel_map[mboard_idx][chan].radio_index; + size_t port_index = _rx_channel_map[mboard_idx][chan].port_index; + return uhd::fs_path(str( + boost::format("/mboards/%d/xbar/%s_%d/rx_fe_corrections/%d/") + % mboard_idx % RADIO_BLOCK_NAME % radio_index % port_index + )); + } + + uhd::fs_path tx_fe_root(const size_t mboard_idx, const size_t chan) + { + size_t radio_index = _tx_channel_map[mboard_idx][chan].radio_index; + size_t port_index = _tx_channel_map[mboard_idx][chan].port_index; + return uhd::fs_path(str( + boost::format("/mboards/%d/xbar/%s_%d/tx_fe_corrections/%d/") + % mboard_idx % RADIO_BLOCK_NAME % radio_index % port_index + )); + } + + void issue_stream_cmd(const stream_cmd_t &stream_cmd, size_t mboard, size_t chan) + { + UHD_LEGACY_LOG() << "[legacy_compat] issue_stream_cmd() " << std::endl; + const size_t &radio_index = _rx_channel_map[mboard][chan].radio_index; + const size_t &port_index = _rx_channel_map[mboard][chan].port_index; + if (_has_ddcs) { + get_block_ctrl<ddc_block_ctrl>(mboard, DDC_BLOCK_NAME, radio_index)->issue_stream_cmd(stream_cmd, port_index); + } else { + get_block_ctrl<radio_ctrl>(mboard, RADIO_BLOCK_NAME, radio_index)->issue_stream_cmd(stream_cmd, port_index); + } + } + + //! Sets block_id<N> and block_port<N> in the streamer args, otherwise forwards the call + uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &args_) + { + uhd::stream_args_t args(args_); + if (args.otw_format.empty()) { + args.otw_format = "sc16"; + } + _update_stream_args_for_streaming<uhd::RX_DIRECTION>(args, _rx_channel_map); + UHD_LEGACY_LOG() << "[legacy_compat] rx stream args: " << args.args.to_string() << std::endl; + return _device->get_rx_stream(args); + } + + //! Sets block_id<N> and block_port<N> in the streamer args, otherwise forwards the call. + // If spp is in the args, update the radios. If it's not set, copy the value from the radios. + uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &args_) + { + uhd::stream_args_t args(args_); + if (args.otw_format.empty()) { + args.otw_format = "sc16"; + } + _update_stream_args_for_streaming<uhd::TX_DIRECTION>(args, _tx_channel_map); + UHD_LEGACY_LOG() << "[legacy_compat] tx stream args: " << args.args.to_string() << std::endl; + return _device->get_tx_stream(args); + } + + double get_tick_rate(const size_t mboard_idx=0) + { + return _tree->access<double>(mb_root(mboard_idx) / "tick_rate").get(); + } + + uhd::meta_range_t lambda_get_samp_rate_range( + const size_t mboard_idx, + const size_t radio_idx, + const size_t chan, + uhd::direction_t dir + ) { + radio_ctrl::sptr radio_sptr = get_block_ctrl<radio_ctrl>(mboard_idx, RADIO_BLOCK_NAME, radio_idx); + const double samp_rate = (dir == uhd::TX_DIRECTION) ? + radio_sptr->get_input_samp_rate(chan) : + radio_sptr->get_output_samp_rate(chan) + ; + + return uhd::meta_range_t(samp_rate, samp_rate, 0.0); + } + + void set_tick_rate(const double tick_rate, const size_t mboard_idx=0) + { + _tree->access<double>(mb_root(mboard_idx) / "tick_rate").set(tick_rate); + update_tick_rate_on_blocks(tick_rate, mboard_idx); + } + +private: // types + struct radio_port_pair_t { + radio_port_pair_t(const size_t radio=0, const size_t port=0) : radio_index(radio), port_index(port) {} + size_t radio_index; + size_t port_index; + }; + //! Map: _rx_channel_map[mboard_idx][chan_idx] => (Radio, Port) + // Container is not a std::map because we need to guarantee contiguous + // ports and correct order anyway. + typedef std::vector< std::vector<radio_port_pair_t> > chan_map_t; + +private: // methods + /************************************************************************ + * Private helpers + ***********************************************************************/ + std::string get_slot_name(const size_t radio_index) + { + return (radio_index == 0) ? "A" : "B"; + } + + size_t get_radio_index(const std::string slot_name) + { + return (slot_name == "A") ? 0 : 1; + } + + template <typename block_type> + inline typename block_type::sptr get_block_ctrl(const size_t mboard_idx, const std::string &name, const size_t block_count) + { + block_id_t block_id(mboard_idx, name, block_count); + return _device->get_block_ctrl<block_type>(block_id); + } + + template <uhd::direction_t dir> + void _update_stream_args_for_streaming( + uhd::stream_args_t &args, + chan_map_t &chan_map + ) { + const size_t args_spp = args.args.cast<size_t>("spp", _spp); + if (args.args.has_key("spp") and args_spp != _spp) { + for (size_t mboard = 0; mboard < _num_mboards; mboard++) { + for (size_t radio = 0; radio < _num_radios_per_board; radio++) { + get_block_ctrl<radio_ctrl>(mboard, RADIO_BLOCK_NAME, radio)->set_arg<int>("spp", args_spp); + } + } + _spp = args_spp; + } else if (dir == uhd::RX_DIRECTION) { + args.args["spp"] = str(boost::format("%d") % _spp); + } + if (args.channels.empty()) { + args.channels = std::vector<size_t>(1, 0); + } + for (size_t i = 0; i < args.channels.size(); i++) { + const size_t stream_arg_chan_idx = args.channels[i]; + // Determine which mboard, and on that mboard, which channel this is: + size_t mboard_idx = 0; + size_t this_mboard_chan_idx = stream_arg_chan_idx; + while (this_mboard_chan_idx >= chan_map[mboard_idx].size()) { + mboard_idx++; + this_mboard_chan_idx -= chan_map[mboard_idx].size(); + } + if (mboard_idx >= chan_map.size()) { + throw uhd::index_error(str( + boost::format("[legacy_compat]: %s channel %u out of range for given frontend configuration.") + % (dir == uhd::TX_DIRECTION ? "TX" : "RX") + % stream_arg_chan_idx + )); + } + // Map that mboard and channel to a block: + const size_t radio_index = chan_map[mboard_idx][this_mboard_chan_idx].radio_index; + size_t port_index = chan_map[mboard_idx][this_mboard_chan_idx].port_index; + const std::string block_name = _get_streamer_block_id_and_port<dir>(mboard_idx, radio_index, port_index); + args.args[str(boost::format("block_id%d") % stream_arg_chan_idx)] = block_name; + args.args[str(boost::format("block_port%d") % stream_arg_chan_idx)] = str(boost::format("%d") % port_index); + } + } + + template <uhd::direction_t dir> + std::string _get_streamer_block_id_and_port( + const size_t mboard_idx, + const size_t radio_index, + size_t &port_index + ) { + if (dir == uhd::TX_DIRECTION) { + if (_has_dmafifo) { + port_index = radio_index; + return block_id_t(mboard_idx, DFIFO_BLOCK_NAME, 0).to_string(); + } else { + if (_has_ducs) { + return block_id_t(mboard_idx, DUC_BLOCK_NAME, radio_index).to_string(); + } else { + return block_id_t(mboard_idx, RADIO_BLOCK_NAME, radio_index).to_string(); + } + } + } else { + if (_has_ddcs) { + return block_id_t(mboard_idx, DDC_BLOCK_NAME, radio_index).to_string(); + } else { + return block_id_t(mboard_idx, RADIO_BLOCK_NAME, radio_index).to_string(); + } + } + } + + /************************************************************************ + * Initialization + ***********************************************************************/ + /*! Check this device has all the required peripherals. + * + * Check rules: + * - Every mboard needs the same number of radios. + * - For every radio block, there must be DDC and a DUC block, + * with matching number of ports. + * + * \throw uhd::runtime_error if any of these checks fail. + */ + void check_available_periphs() + { + if (_num_radios_per_board == 0) { + throw uhd::runtime_error("For legacy APIs, all devices require at least one radio."); + } + block_id_t radio_block_id(0, RADIO_BLOCK_NAME); + block_id_t duc_block_id(0, DUC_BLOCK_NAME); + block_id_t ddc_block_id(0, DDC_BLOCK_NAME); + block_id_t fifo_block_id(0, DFIFO_BLOCK_NAME, 0); + for (size_t i = 0; i < _num_mboards; i++) { + radio_block_id.set_device_no(i); + duc_block_id.set_device_no(i); + ddc_block_id.set_device_no(i); + fifo_block_id.set_device_no(i); + for (size_t k = 0; k < _num_radios_per_board; k++) { + radio_block_id.set_block_count(k); + duc_block_id.set_block_count(k); + ddc_block_id.set_block_count(k); + // Only one FIFO per crossbar, so don't set block count for that block + if (not _device->has_block(radio_block_id) + or (_has_ducs and not _device->has_block(duc_block_id)) + or (_has_ddcs and not _device->has_block(ddc_block_id)) + or (_has_dmafifo and not _device->has_block(fifo_block_id)) + ) { + throw uhd::runtime_error("For legacy APIs, all devices require the same number of radios, DDCs and DUCs."); + } + + const size_t this_spp = get_block_ctrl<radio_ctrl>(i, RADIO_BLOCK_NAME, k)->get_arg<int>("spp"); + if (this_spp != _spp) { + throw uhd::runtime_error(str( + boost::format("[legacy compat] Radios have differing spp values: %s has %d, others have %d") + % radio_block_id.to_string() % this_spp % _spp + )); + } + } + } + } + + /*! Initialize properties in property tree to match legacy mode + */ + void setup_prop_tree() + { + for (size_t mboard_idx = 0; mboard_idx < _num_mboards; mboard_idx++) { + uhd::fs_path root = mb_root(mboard_idx); + // Subdev specs + if (_tree->exists(root / "tx_subdev_spec")) { + _tree->access<subdev_spec_t>(root / "tx_subdev_spec") + .add_coerced_subscriber(boost::bind(&legacy_compat_impl::set_subdev_spec, this, _1, mboard_idx, uhd::TX_DIRECTION)) + .update() + .set_publisher(boost::bind(&legacy_compat_impl::get_subdev_spec, this, mboard_idx, uhd::TX_DIRECTION)); + } else { + _tree->create<subdev_spec_t>(root / "tx_subdev_spec") + .add_coerced_subscriber(boost::bind(&legacy_compat_impl::set_subdev_spec, this, _1, mboard_idx, uhd::TX_DIRECTION)) + .set_publisher(boost::bind(&legacy_compat_impl::get_subdev_spec, this, mboard_idx, uhd::TX_DIRECTION)); + } + + if (_tree->exists(root / "rx_subdev_spec")) { + _tree->access<subdev_spec_t>(root / "rx_subdev_spec") + .add_coerced_subscriber(boost::bind(&legacy_compat_impl::set_subdev_spec, this, _1, mboard_idx, uhd::RX_DIRECTION)) + .update() + .set_publisher(boost::bind(&legacy_compat_impl::get_subdev_spec, this, mboard_idx, uhd::RX_DIRECTION)); + } else { + _tree->create<subdev_spec_t>(root / "rx_subdev_spec") + .add_coerced_subscriber(boost::bind(&legacy_compat_impl::set_subdev_spec, this, _1, mboard_idx, uhd::RX_DIRECTION)) + .set_publisher(boost::bind(&legacy_compat_impl::get_subdev_spec, this, mboard_idx, uhd::RX_DIRECTION)); + } + + if (not _has_ddcs) { + for (size_t radio_idx = 0; radio_idx < _num_radios_per_board; radio_idx++) { + for (size_t chan = 0; chan < _num_rx_chans_per_radio; chan++) { + const uhd::fs_path rx_dsp_base_path(mb_root(mboard_idx) / "rx_dsps" / radio_idx / chan); + _tree->create<double>(rx_dsp_base_path / "rate/value") + .set(0.0) + .set_publisher( + boost::bind( + &radio_ctrl::get_output_samp_rate, + get_block_ctrl<radio_ctrl>(mboard_idx, RADIO_BLOCK_NAME, radio_idx), + chan + ) + ) + ; + _tree->create<uhd::meta_range_t>(rx_dsp_base_path / "rate/range") + .set_publisher( + boost::bind( + &legacy_compat_impl::lambda_get_samp_rate_range, + this, + mboard_idx, radio_idx, chan, + uhd::RX_DIRECTION + ) + ) + ; + _tree->create<double>(rx_dsp_base_path / "freq/value") + .set_publisher(boost::bind(&lambda_const_double, 0.0)) + ; + _tree->create<uhd::meta_range_t>(rx_dsp_base_path / "freq/range") + .set_publisher(boost::bind(&lambda_const_meta_range, 0.0, 0.0, 0.0)) + ; + } + } + } + if (not _has_ducs) { + for (size_t radio_idx = 0; radio_idx < _num_radios_per_board; radio_idx++) { + for (size_t chan = 0; chan < _num_tx_chans_per_radio; chan++) { + const uhd::fs_path tx_dsp_base_path(mb_root(mboard_idx) / "tx_dsps" / radio_idx / chan); + _tree->create<double>(tx_dsp_base_path / "rate/value") + .set(0.0) + .set_publisher( + boost::bind( + &radio_ctrl::get_output_samp_rate, + get_block_ctrl<radio_ctrl>(mboard_idx, RADIO_BLOCK_NAME, radio_idx), + chan + ) + ) + ; + _tree->create<uhd::meta_range_t>(tx_dsp_base_path / "rate/range") + .set_publisher( + boost::bind( + &legacy_compat_impl::lambda_get_samp_rate_range, + this, + mboard_idx, radio_idx, chan, + uhd::TX_DIRECTION + ) + ) + ; + _tree->create<double>(tx_dsp_base_path / "freq/value") + .set_publisher(boost::bind(&lambda_const_double, 0.0)) + ; + _tree->create<uhd::meta_range_t>(tx_dsp_base_path / "freq/range") + .set_publisher(boost::bind(&lambda_const_meta_range, 0.0, 0.0, 0.0)) + ; + } + } + } + } + } + + /*! Default block connections. + * + * Tx connections: + * + * [Host] => DMA FIFO => DUC => Radio + * + * Note: There is only one DMA FIFO per crossbar, with twice the number of ports. + * + * Rx connections: + * + * Radio => DDC => [Host] + * + * Streamers are *not* generated here. + */ + void connect_blocks() + { + _graph = _device->create_graph("legacy"); + size_t bpp = _spp * BYTES_PER_SAMPLE + MAX_BYTES_PER_HEADER; + for (size_t mboard = 0; mboard < _num_mboards; mboard++) { + for (size_t radio = 0; radio < _num_radios_per_board; radio++) { + // Tx Channels + for (size_t chan = 0; chan < _num_tx_chans_per_radio; chan++) { + if (_has_ducs) { + _graph->connect( + block_id_t(mboard, DUC_BLOCK_NAME, radio), chan, + block_id_t(mboard, RADIO_BLOCK_NAME, radio), chan, + bpp + ); + if (_has_dmafifo) { + // We have DMA FIFO *and* DUCs + _graph->connect( + block_id_t(mboard, DFIFO_BLOCK_NAME, 0), radio, + block_id_t(mboard, DUC_BLOCK_NAME, radio), chan, + bpp + ); + } + } else if (_has_dmafifo) { + // We have DMA FIFO, *no* DUCs + _graph->connect( + block_id_t(mboard, DFIFO_BLOCK_NAME, 0), radio, + block_id_t(mboard, RADIO_BLOCK_NAME, radio), chan, + bpp + ); + } + } + // Rx Channels + for (size_t chan = 0; chan < _num_rx_chans_per_radio; chan++) { + if (_has_ddcs) { + _graph->connect( + block_id_t(mboard, RADIO_BLOCK_NAME, radio), chan, + block_id_t(mboard, DDC_BLOCK_NAME, radio), chan, + bpp + ); + } + } + } + } + } + + + /************************************************************************ + * Subdev translation + ***********************************************************************/ + /*! Subdev -> (Radio, Port) + * + * Example: Device is X300, subdev spec is 'A:0 B:0', we have 2 radios. + * Then we map to ((0, 0), (1, 0)). I.e., zero-th port on radio 0 and + * radio 1, respectively. + */ + void set_subdev_spec(const subdev_spec_t &spec, const size_t mboard, const uhd::direction_t dir) + { + UHD_ASSERT_THROW(mboard < _num_mboards); + chan_map_t &chan_map = (dir == uhd::TX_DIRECTION) ? _tx_channel_map : _rx_channel_map; + std::vector<radio_port_pair_t> new_mapping(spec.size()); + for (size_t i = 0; i < spec.size(); i++) { + const size_t new_radio_index = get_radio_index(spec[i].db_name); + radio_ctrl::sptr radio = get_block_ctrl<radio_ctrl>(mboard, "Radio", new_radio_index); + size_t new_port_index = radio->get_chan_from_dboard_fe(spec[i].sd_name, dir); + if (new_port_index >= radio->get_input_ports().size()) { + new_port_index = radio->get_input_ports().at(0); + } + radio_port_pair_t new_radio_port_pair(new_radio_index, new_port_index); + new_mapping[i] = new_radio_port_pair; + } + chan_map[mboard] = new_mapping; + } + + subdev_spec_t get_subdev_spec(const size_t mboard, const uhd::direction_t dir) + { + UHD_ASSERT_THROW(mboard < _num_mboards); + subdev_spec_t subdev_spec; + chan_map_t &chan_map = (dir == uhd::TX_DIRECTION) ? _tx_channel_map : _rx_channel_map; + for (size_t chan_idx = 0; chan_idx < chan_map[mboard].size(); chan_idx++) { + const size_t radio_index = chan_map[mboard][chan_idx].radio_index; + const size_t port_index = chan_map[mboard][chan_idx].port_index; + const std::string new_db_name = get_slot_name(radio_index); + const std::string new_sd_name = + get_block_ctrl<radio_ctrl>(mboard, "Radio", radio_index)->get_dboard_fe_from_chan(port_index, dir); + subdev_spec_pair_t new_pair(new_db_name, new_sd_name); + subdev_spec.push_back(new_pair); + } + + return subdev_spec; + } + + void update_tick_rate_on_blocks(const double tick_rate, const size_t mboard_idx) + { + block_id_t radio_block_id(mboard_idx, RADIO_BLOCK_NAME); + block_id_t duc_block_id(mboard_idx, DUC_BLOCK_NAME); + block_id_t ddc_block_id(mboard_idx, DDC_BLOCK_NAME); + + for (size_t radio = 0; radio < _num_radios_per_board; radio++) { + radio_block_id.set_block_count(radio); + duc_block_id.set_block_count(radio); + ddc_block_id.set_block_count(radio); + radio_ctrl::sptr radio_sptr = _device->get_block_ctrl<radio_ctrl>(radio_block_id); + radio_sptr->set_rate(tick_rate); + for (size_t chan = 0; chan < _num_rx_chans_per_radio and _has_ddcs; chan++) { + const double radio_output_rate = radio_sptr->get_output_samp_rate(chan); + _device->get_block_ctrl(ddc_block_id)->set_arg<double>("input_rate", radio_output_rate, chan); + } + for (size_t chan = 0; chan < _num_tx_chans_per_radio and _has_ducs; chan++) { + const double radio_input_rate = radio_sptr->get_input_samp_rate(chan); + _device->get_block_ctrl(duc_block_id)->set_arg<double>("output_rate", radio_input_rate, chan); + } + } + } + +private: // attributes + uhd::device3::sptr _device; + uhd::property_tree::sptr _tree; + + const bool _has_ducs; + const bool _has_ddcs; + const bool _has_dmafifo; + const size_t _num_mboards; + const size_t _num_radios_per_board; + const size_t _num_tx_chans_per_radio; + const size_t _num_rx_chans_per_radio; + size_t _spp; + + chan_map_t _rx_channel_map; + chan_map_t _tx_channel_map; + + graph::sptr _graph; +}; + +legacy_compat::sptr legacy_compat::make( + uhd::device3::sptr device, + const uhd::device_addr_t &args +) { + UHD_ASSERT_THROW(bool(device)); + static std::map<void *, boost::weak_ptr<legacy_compat> > legacy_cache; + + if (legacy_cache.count(device.get())) { + legacy_compat::sptr legacy_compat_copy = legacy_cache.at(device.get()).lock(); + if (not bool(legacy_compat_copy)) { + throw uhd::runtime_error("Reference to existing legacy compat object expired prematurely!"); + } + + UHD_LEGACY_LOG() << "[legacy_compat] Using existing legacy compat object for this device." << std::endl; + return legacy_compat_copy; + } + + legacy_compat::sptr new_legacy_compat = boost::make_shared<legacy_compat_impl>(device, args); + legacy_cache[device.get()] = new_legacy_compat; + return new_legacy_compat; +} + diff --git a/host/lib/rfnoc/legacy_compat.hpp b/host/lib/rfnoc/legacy_compat.hpp new file mode 100644 index 000000000..29be1bdc2 --- /dev/null +++ b/host/lib/rfnoc/legacy_compat.hpp @@ -0,0 +1,55 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_RFNOC_LEGACY_COMPAT_HPP +#define INCLUDED_RFNOC_LEGACY_COMPAT_HPP + +#include <uhd/device3.hpp> +#include <uhd/stream.hpp> + +namespace uhd { namespace rfnoc { + + /*! Legacy compatibility layer class. + */ + class legacy_compat + { + public: + typedef boost::shared_ptr<legacy_compat> sptr; + + virtual uhd::fs_path rx_dsp_root(const size_t mboard_idx, const size_t chan) = 0; + + virtual uhd::fs_path tx_dsp_root(const size_t mboard_idx, const size_t chan) = 0; + + virtual uhd::fs_path rx_fe_root(const size_t mboard_idx, const size_t chan) = 0; + + virtual uhd::fs_path tx_fe_root(const size_t mboard_idx, const size_t chan) = 0; + + virtual void issue_stream_cmd(const uhd::stream_cmd_t &stream_cmd, size_t mboard, size_t chan) = 0; + + virtual uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &args) = 0; + + virtual uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &args) = 0; + + static sptr make( + uhd::device3::sptr device, + const uhd::device_addr_t &args + ); + }; + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_RFNOC_LEGACY_COMPAT_HPP */ diff --git a/host/lib/rfnoc/nocscript/CMakeLists.txt b/host/lib/rfnoc/nocscript/CMakeLists.txt new file mode 100644 index 000000000..77fcc101c --- /dev/null +++ b/host/lib/rfnoc/nocscript/CMakeLists.txt @@ -0,0 +1,37 @@ +# +# Copyright 2015 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_basic_funcs.py + ${CMAKE_CURRENT_BINARY_DIR}/basic_functions.hpp +) + +IF(ENABLE_MANUAL) + LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_basic_funcs.py + ${CMAKE_BINARY_DIR}/docs/nocscript_functions.dox + ) +ENDIF(ENABLE_MANUAL) + +LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/expression.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/function_table.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/parser.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/block_iface.cpp +) diff --git a/host/lib/rfnoc/nocscript/block_iface.cpp b/host/lib/rfnoc/nocscript/block_iface.cpp new file mode 100644 index 000000000..2034d3438 --- /dev/null +++ b/host/lib/rfnoc/nocscript/block_iface.cpp @@ -0,0 +1,255 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "block_iface.hpp" +#include "function_table.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <boost/assign.hpp> +#include <boost/bind.hpp> +#include <boost/format.hpp> + +#define UHD_NOCSCRIPT_LOG() UHD_LOGV(never) + +using namespace uhd::rfnoc; +using namespace uhd::rfnoc::nocscript; + +block_iface::block_iface(block_ctrl_base *block_ptr) + : _block_ptr(block_ptr) +{ + function_table::sptr ft = function_table::make(); + + // Add the SR_WRITE() function + expression_function::argtype_list_type sr_write_args = boost::assign::list_of + (expression::TYPE_STRING) + (expression::TYPE_INT) + ; + ft->register_function( + "SR_WRITE", + boost::bind(&block_iface::_nocscript__sr_write, this, _1), + expression::TYPE_BOOL, + sr_write_args + ); + + // Add read access to arguments ($foo) + expression_function::argtype_list_type arg_set_args_wo_port = boost::assign::list_of + (expression::TYPE_STRING) + (expression::TYPE_INT) + ; + expression_function::argtype_list_type arg_set_args_w_port = boost::assign::list_of + (expression::TYPE_STRING) + (expression::TYPE_INT) + (expression::TYPE_INT) + ; +#define REGISTER_ARG_SETTER(noctype, setter_func) \ + arg_set_args_wo_port[1] = expression::noctype; \ + arg_set_args_w_port[1] = expression::noctype; \ + ft->register_function( \ + "SET_ARG", \ + boost::bind(&block_iface::setter_func, this, _1), \ + expression::TYPE_BOOL, \ + arg_set_args_wo_port \ + ); \ + ft->register_function( \ + "SET_ARG", \ + boost::bind(&block_iface::setter_func, this, _1), \ + expression::TYPE_BOOL, \ + arg_set_args_w_port \ + ); + REGISTER_ARG_SETTER(TYPE_INT, _nocscript__arg_set_int); + REGISTER_ARG_SETTER(TYPE_STRING, _nocscript__arg_set_string); + REGISTER_ARG_SETTER(TYPE_DOUBLE, _nocscript__arg_set_double); + REGISTER_ARG_SETTER(TYPE_INT_VECTOR, _nocscript__arg_set_intvec); + + + // Add read/write access to local variables + expression_function::argtype_list_type set_var_args = boost::assign::list_of + (expression::TYPE_STRING) + (expression::TYPE_INT) + ; + const expression_function::argtype_list_type get_var_args = boost::assign::list_of + (expression::TYPE_STRING) + ; +#define REGISTER_VAR_ACCESS(noctype, typestr) \ + set_var_args[1] = expression::noctype; \ + ft->register_function( \ + "SET_VAR", \ + boost::bind(&block_iface::_nocscript__var_set, this, _1), \ + expression::TYPE_BOOL, \ + set_var_args \ + ); \ + ft->register_function( \ + "GET_"#typestr, \ + boost::bind(&block_iface::_nocscript__var_get, this, _1), \ + expression::noctype, \ + get_var_args \ + ); + REGISTER_VAR_ACCESS(TYPE_INT, INT); + REGISTER_VAR_ACCESS(TYPE_STRING, STRING); + REGISTER_VAR_ACCESS(TYPE_DOUBLE, DOUBLE); + REGISTER_VAR_ACCESS(TYPE_INT_VECTOR, INT_VECTOR); + + // Create the parser + _parser = parser::make( + ft, + boost::bind(&block_iface::_nocscript__arg_get_type, this, _1), + boost::bind(&block_iface::_nocscript__arg_get_val, this, _1) + ); +} + + +void block_iface::run_and_check(const std::string &code, const std::string &error_message) +{ + boost::mutex::scoped_lock local_interpreter_lock(_lil_mutex); + + UHD_NOCSCRIPT_LOG() << "[NocScript] Executing and asserting code: " << code << std::endl; + expression::sptr e = _parser->create_expr_tree(code); + expression_literal result = e->eval(); + if (not result.to_bool()) { + if (error_message.empty()) { + throw uhd::runtime_error(str( + boost::format("[NocScript] Code returned false: %s") + % code + )); + } else { + throw uhd::runtime_error(str( + boost::format("[NocScript] Error: %s") + % error_message + )); + } + } + + _vars.clear(); // We go out of scope, and so do NocScript variables +} + + +expression_literal block_iface::_nocscript__sr_write(expression_container::expr_list_type args) +{ + const std::string reg_name = args[0]->eval().get_string(); + const boost::uint32_t reg_val = boost::uint32_t(args[1]->eval().get_int()); + bool result = true; + try { + UHD_NOCSCRIPT_LOG() << "[NocScript] Executing SR_WRITE() " << std::endl; + _block_ptr->sr_write(reg_name, reg_val); + } catch (const uhd::exception &e) { + UHD_MSG(error) << boost::format("[NocScript] Error while executing SR_WRITE(%s, 0x%X):\n%s") + % reg_name % reg_val % e.what() + << std::endl; + result = false; + } + + return expression_literal(result); +} + +expression::type_t block_iface::_nocscript__arg_get_type(const std::string &varname) +{ + const std::string var_type = _block_ptr->get_arg_type(varname); + if (var_type == "int") { + return expression::TYPE_INT; + } else if (var_type == "string") { + return expression::TYPE_STRING; + } else if (var_type == "double") { + return expression::TYPE_DOUBLE; + } else if (var_type == "int_vector") { + UHD_THROW_INVALID_CODE_PATH(); // TODO + } else { + UHD_THROW_INVALID_CODE_PATH(); + } +} + +expression_literal block_iface::_nocscript__arg_get_val(const std::string &varname) +{ + const std::string var_type = _block_ptr->get_arg_type(varname); + if (var_type == "int") { + return expression_literal(_block_ptr->get_arg<int>(varname)); + } else if (var_type == "string") { + return expression_literal(_block_ptr->get_arg<std::string>(varname)); + } else if (var_type == "double") { + return expression_literal(_block_ptr->get_arg<double>(varname)); + } else if (var_type == "int_vector") { + UHD_THROW_INVALID_CODE_PATH(); // TODO + } else { + UHD_THROW_INVALID_CODE_PATH(); + } +} + +expression_literal block_iface::_nocscript__arg_set_int(const expression_container::expr_list_type &args) +{ + const std::string var_name = args[0]->eval().get_string(); + const int val = args[1]->eval().get_int(); + size_t port = 0; + if (args.size() == 3) { + port = size_t(args[2]->eval().get_int()); + } + UHD_NOCSCRIPT_LOG() << "[NocScript] Setting $" << var_name << std::endl; + _block_ptr->set_arg<int>(var_name, val, port); + return expression_literal(true); +} + +expression_literal block_iface::_nocscript__arg_set_string(const expression_container::expr_list_type &args) +{ + const std::string var_name = args[0]->eval().get_string(); + const std::string val = args[1]->eval().get_string(); + size_t port = 0; + if (args.size() == 3) { + port = size_t(args[2]->eval().get_int()); + } + UHD_NOCSCRIPT_LOG() << "[NocScript] Setting $" << var_name << std::endl; + _block_ptr->set_arg<std::string>(var_name, val, port); + return expression_literal(true); +} + +expression_literal block_iface::_nocscript__arg_set_double(const expression_container::expr_list_type &args) +{ + const std::string var_name = args[0]->eval().get_string(); + const double val = args[1]->eval().get_double(); + size_t port = 0; + if (args.size() == 3) { + port = size_t(args[2]->eval().get_int()); + } + UHD_NOCSCRIPT_LOG() << "[NocScript] Setting $" << var_name << std::endl; + _block_ptr->set_arg<double>(var_name, val, port); + return expression_literal(true); +} + +expression_literal block_iface::_nocscript__arg_set_intvec(const expression_container::expr_list_type &) +{ + UHD_THROW_INVALID_CODE_PATH(); +} + +block_iface::sptr block_iface::make(uhd::rfnoc::block_ctrl_base* block_ptr) +{ + return sptr(new block_iface(block_ptr)); +} + +expression_literal block_iface::_nocscript__var_get(const expression_container::expr_list_type &args) +{ + expression_literal expr = _vars[args[0]->eval().get_string()]; + //std::cout << "[NocScript] Getting var " << args[0]->eval().get_string() << " == " << expr << std::endl; + //std::cout << "[NocScript] Type " << expr.infer_type() << std::endl; + //return _vars[args[0]->eval().get_string()]; + return expr; +} + +expression_literal block_iface::_nocscript__var_set(const expression_container::expr_list_type &args) +{ + _vars[args[0]->eval().get_string()] = args[1]->eval(); + //std::cout << "[NocScript] Set var " << args[0]->eval().get_string() << " to " << _vars[args[0]->eval().get_string()] << std::endl; + //std::cout << "[NocScript] Type " << _vars[args[0]->eval().get_string()].infer_type() << std::endl; + return expression_literal(true); +} + diff --git a/host/lib/rfnoc/nocscript/block_iface.hpp b/host/lib/rfnoc/nocscript/block_iface.hpp new file mode 100644 index 000000000..6354409f2 --- /dev/null +++ b/host/lib/rfnoc/nocscript/block_iface.hpp @@ -0,0 +1,94 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "expression.hpp" +#include "parser.hpp" +#include <uhd/rfnoc/block_ctrl_base.hpp> +#include <boost/thread/mutex.hpp> + +#ifndef INCLUDED_LIBUHD_NOCSCRIPT_BLOCK_IFACE_HPP +#define INCLUDED_LIBUHD_NOCSCRIPT_BLOCK_IFACE_HPP + +namespace uhd { namespace rfnoc { namespace nocscript { + +/*! NocScript / Block interface class. + * + * This class only exists as a member of an rfnoc::block_ctrl_base class. + * It should never be instantiated anywhere else. It is used to execute + * NocScript function calls that require access to the original block + * controller class. + */ +class block_iface { + + public: + typedef boost::shared_ptr<block_iface> sptr; + + static sptr make(uhd::rfnoc::block_ctrl_base* block_ptr); + + block_iface(uhd::rfnoc::block_ctrl_base* block_ptr); + + /*! Execute \p code and make sure it returns 'true'. + * + * \param code Must be a valid NocScript expression that returns a boolean value. + * If it returns false, this is interpreted as failure. + * \param error_message If the expression fails, this error message is printed. + * \throws uhd::runtime_error if the expression returns false. + * \throws uhd::syntax_error if the expression is invalid. + */ + void run_and_check(const std::string &code, const std::string &error_message=""); + + private: + //! For the local interpreter lock (lil) + boost::mutex _lil_mutex; + + //! Wrapper for block_ctrl_base::sr_write, so we can call it from within NocScript + expression_literal _nocscript__sr_write(expression_container::expr_list_type); + + //! Argument type getter that can be used within NocScript + expression::type_t _nocscript__arg_get_type(const std::string &argname); + + //! Argument value getter that can be used within NocScript + expression_literal _nocscript__arg_get_val(const std::string &argname); + + //! Argument value setters: + expression_literal _nocscript__arg_set_int(const expression_container::expr_list_type &); + expression_literal _nocscript__arg_set_string(const expression_container::expr_list_type &); + expression_literal _nocscript__arg_set_double(const expression_container::expr_list_type &); + expression_literal _nocscript__arg_set_intvec(const expression_container::expr_list_type &); + + //! Variable value getter + expression_literal _nocscript__var_get(const expression_container::expr_list_type &); + + //! Variable value setter + expression_literal _nocscript__var_set(const expression_container::expr_list_type &); + + //! Raw pointer to the block class. Note that since block_iface may + // only live as a member of a block_ctrl_base, we don't really need + // the reference counting. + uhd::rfnoc::block_ctrl_base* _block_ptr; + + //! Pointer to the parser object + parser::sptr _parser; + + //! Container for scoped variables + std::map<std::string, expression_literal> _vars; +}; + +}}} /* namespace uhd::rfnoc::nocscript */ + +#endif /* INCLUDED_LIBUHD_NOCSCRIPT_BLOCK_IFACE_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/nocscript/expression.cpp b/host/lib/rfnoc/nocscript/expression.cpp new file mode 100644 index 000000000..38d6e2128 --- /dev/null +++ b/host/lib/rfnoc/nocscript/expression.cpp @@ -0,0 +1,413 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "expression.hpp" +#include "function_table.hpp" +#include <uhd/utils/cast.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/assign.hpp> +#include <boost/algorithm/string.hpp> + +using namespace uhd::rfnoc::nocscript; + +std::map<expression::type_t, std::string> expression::type_repr = boost::assign::map_list_of + (TYPE_INT, "INT") + (TYPE_DOUBLE, "DOUBLE") + (TYPE_STRING, "STRING") + (TYPE_BOOL, "BOOL") + (TYPE_INT_VECTOR, "INT_VECTOR") +; + +/******************************************************************** + * Literal expressions (constants) + *******************************************************************/ +expression_literal::expression_literal( + const std::string token_val, + expression::type_t type +) : _bool_val(false) + , _int_val(0) + , _double_val(0.0) + , _val(token_val) + , _type(type) +{ + switch (_type) { + case expression::TYPE_STRING: + // Remove the leading and trailing quotes: + _val = _val.substr(1, _val.size()-2); + break; + + case expression::TYPE_INT: + if (_val.substr(0, 2) == "0x") { + _int_val = uhd::cast::hexstr_cast<int>(_val); + } else { + _int_val = boost::lexical_cast<int>(_val); + } + break; + + case expression::TYPE_DOUBLE: + _double_val = boost::lexical_cast<double>(_val); + break; + + case expression::TYPE_BOOL: + if (boost::to_upper_copy(_val) == "TRUE") { + _bool_val = true; + } else { + // lexical cast to bool is too picky + _bool_val = bool(boost::lexical_cast<int>(_val)); + } + break; + + case expression::TYPE_INT_VECTOR: + { + std::string str_vec = _val.substr(1, _val.size()-2); + std::vector<std::string> subtoken_list; + boost::split(subtoken_list, str_vec, boost::is_any_of(", "), boost::token_compress_on); + BOOST_FOREACH(const std::string &t, subtoken_list) { + _int_vector_val.push_back(boost::lexical_cast<int>(t)); + } + break; + } + + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +expression_literal::expression_literal(bool b) + : _bool_val(b) + , _int_val(0) + , _double_val(0.0) + , _val("") + , _type(expression::TYPE_BOOL) +{ + // nop +} + +expression_literal::expression_literal(int i) + : _bool_val(false) + , _int_val(i) + , _double_val(0.0) + , _val("") + , _type(expression::TYPE_INT) +{ + // nop +} + +expression_literal::expression_literal(double d) + : _bool_val(false) + , _int_val(0) + , _double_val(d) + , _val("") + , _type(expression::TYPE_DOUBLE) +{ + // nop +} + +expression_literal::expression_literal(const std::string &s) + : _bool_val(false) + , _int_val(0) + , _double_val(0.0) + , _val(s) + , _type(expression::TYPE_STRING) +{ + // nop +} + +expression_literal::expression_literal(const std::vector<int> v) + : _bool_val(false) + , _int_val(0) + , _double_val(0.0) + , _int_vector_val(v) + , _val("") + , _type(expression::TYPE_INT_VECTOR) +{ + // nop +} + +bool expression_literal::to_bool() const +{ + switch (_type) { + case TYPE_INT: + return bool(boost::lexical_cast<int>(_val)); + case TYPE_STRING: + return not _val.empty(); + case TYPE_DOUBLE: + return bool(boost::lexical_cast<double>(_val)); + case TYPE_BOOL: + return _bool_val; + case TYPE_INT_VECTOR: + return not _int_vector_val.empty(); + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +int expression_literal::get_int() const +{ + if (_type != TYPE_INT) { + throw uhd::type_error("Cannot call get_int() on non-int value."); + } + + return _int_val; +} + +double expression_literal::get_double() const +{ + if (_type != TYPE_DOUBLE) { + throw uhd::type_error("Cannot call get_double() on non-double value."); + } + + return _double_val; +} + +std::string expression_literal::get_string() const +{ + if (_type != TYPE_STRING) { + throw uhd::type_error("Cannot call get_string() on non-string value."); + } + + return _val; +} + +bool expression_literal::get_bool() const +{ + if (_type != TYPE_BOOL) { + throw uhd::type_error("Cannot call get_bool() on non-boolean value."); + } + + return _bool_val; +} + +std::vector<int> expression_literal::get_int_vector() const +{ + if (_type != TYPE_INT_VECTOR) { + throw uhd::type_error("Cannot call get_bool() on non-boolean value."); + } + + return _int_vector_val; +} + +std::string expression_literal::repr() const +{ + switch (_type) { + case TYPE_INT: + return boost::lexical_cast<std::string>(_int_val); + case TYPE_STRING: + return _val; + case TYPE_DOUBLE: + return boost::lexical_cast<std::string>(_double_val); + case TYPE_BOOL: + return _bool_val ? "TRUE" : "FALSE"; + case TYPE_INT_VECTOR: + { + std::stringstream sstr; + sstr << "["; + for (size_t i = 0; i < _int_vector_val.size(); i++) { + if (i > 0) { + sstr << ", "; + } + sstr << _int_vector_val[i]; + } + sstr << "]"; + return sstr.str(); + } + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +bool expression_literal::operator==(const expression_literal &rhs) const +{ + if (rhs.infer_type() != _type) { + return false; + } + + switch (_type) { + case TYPE_INT: + return get_int() == rhs.get_int(); + case TYPE_STRING: + return get_string() == rhs.get_string(); + case TYPE_DOUBLE: + return get_double() == rhs.get_double(); + case TYPE_BOOL: + return get_bool() == rhs.get_bool(); + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +/******************************************************************** + * Containers + *******************************************************************/ +expression_container::sptr expression_container::make() +{ + return sptr(new expression_container); +} + +expression::type_t expression_container::infer_type() const +{ + if (_combiner == COMBINE_OR or _combiner == COMBINE_AND) { + return TYPE_BOOL; + } + + if (_sub_exprs.empty()) { + return TYPE_BOOL; + } + + return _sub_exprs.back()->infer_type(); +} + +void expression_container::add(expression::sptr new_expr) +{ + _sub_exprs.push_back(new_expr); +} + +bool expression_container::empty() const +{ + return _sub_exprs.empty(); +} + +void expression_container::set_combiner_safe(const combiner_type c) +{ + if (_combiner == COMBINE_NOTSET) { + _combiner = c; + return; + } + + throw uhd::syntax_error("Attempting to override combiner type"); +} + +expression_literal expression_container::eval() +{ + if (_sub_exprs.empty()) { + return expression_literal(true); + } + + expression_literal ret_val; + BOOST_FOREACH(const expression::sptr &sub_expr, _sub_exprs) { + ret_val = sub_expr->eval(); + if (_combiner == COMBINE_AND and ret_val.to_bool() == false) { + return ret_val; + } + if (_combiner == COMBINE_OR and ret_val.to_bool() == true) { + return ret_val; + } + // For ALL, we return the last one, so just overwrite it + } + return ret_val; +} + +/******************************************************************** + * Functions + *******************************************************************/ +std::string expression_function::to_string(const std::string &name, const argtype_list_type &types) +{ + std::string s = name; + int arg_count = 0; + BOOST_FOREACH(const expression::type_t type, types) { + if (arg_count == 0) { + s += "("; + } else { + s += ", "; + } + s += type_repr[type]; + arg_count++; + } + s += ")"; + + return s; +} + +expression_function::expression_function( + const std::string &name, + const function_table::sptr func_table +) : _name(name) + , _func_table(func_table) +{ + _combiner = COMBINE_ALL; + if (not _func_table->function_exists(_name)) { + throw uhd::syntax_error(str( + boost::format("Unknown function: %s") + % _name + )); + } +} + +void expression_function::add(expression::sptr new_expr) +{ + expression_container::add(new_expr); + _arg_types.push_back(new_expr->infer_type()); +} + +expression::type_t expression_function::infer_type() const +{ + return _func_table->get_type(_name, _arg_types); +} + +expression_literal expression_function::eval() +{ + return _func_table->eval(_name, _arg_types, _sub_exprs); +} + + +std::string expression_function::repr() const +{ + return to_string(_name, _arg_types); +} + +expression_function::sptr expression_function::make( + const std::string &name, + const function_table::sptr func_table +) { + return sptr(new expression_function(name, func_table)); +} + +/******************************************************************** + * Variables + *******************************************************************/ +expression_variable::expression_variable( + const std::string &token_val, + type_getter_type type_getter, + value_getter_type value_getter +) : _type_getter(type_getter) + , _value_getter(value_getter) +{ + // We can assume this is true because otherwise, it's not a valid token: + UHD_ASSERT_THROW(not token_val.empty() and token_val[0] == '$'); + + _varname = token_val.substr(1); +} + +expression::type_t expression_variable::infer_type() const +{ + return _type_getter(_varname); +} + +expression_literal expression_variable::eval() +{ + return _value_getter(_varname); +} + +expression_variable::sptr expression_variable::make( + const std::string &token_val, + type_getter_type type_getter, + value_getter_type value_getter +) { + return sptr(new expression_variable(token_val, type_getter, value_getter)); +} + diff --git a/host/lib/rfnoc/nocscript/expression.hpp b/host/lib/rfnoc/nocscript/expression.hpp new file mode 100644 index 000000000..83fc5bcbc --- /dev/null +++ b/host/lib/rfnoc/nocscript/expression.hpp @@ -0,0 +1,380 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/exception.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/function.hpp> +#include <boost/make_shared.hpp> +#include <vector> +#include <map> + +#ifndef INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_EXPR_HPP +#define INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_EXPR_HPP + +namespace uhd { namespace rfnoc { namespace nocscript { + +// Forward declaration for expression::eval() +class expression_literal; + +/*! Virtual base class for Noc-Script expressions. + */ +class expression +{ + public: + typedef boost::shared_ptr<expression> sptr; + + //! All the possible return types for expressions within Noc-Script + enum type_t { + TYPE_INT, + TYPE_DOUBLE, + TYPE_STRING, + TYPE_BOOL, + TYPE_INT_VECTOR + }; + + // TODO make this a const and fix the [] usage + static std::map<type_t, std::string> type_repr; + + //! Returns the type of this expression without evaluating it + virtual type_t infer_type() const = 0; + + //! Evaluate current expression and return its return value + virtual expression_literal eval() = 0; +}; + +/*! Literal (constant) expression class + * + * A literal is any value that is literally given in the NoC-Script + * source code, such as '5', '"FOO"', or '2.3'. + */ +class expression_literal : public expression +{ + public: + typedef boost::shared_ptr<expression_literal> sptr; + + template <typename expr_type> + static sptr make(expr_type x) { return boost::make_shared<expression_literal>(x); }; + + /*! Generate the literal expression from its token string representation. + * This includes markup, e.g. a string would still have the quotes, and + * a hex value would still have leading 0x. + */ + expression_literal( + const std::string token_val, + expression::type_t type + ); + + //! Create a boolean literal expression from a C++ bool. + expression_literal(bool b=false); + //! Create an integer literal expression from a C++ int. + expression_literal(int i); + //! Create a double literal expression from a C++ double. + expression_literal(double d); + //! Create a string literal expression from a C++ string. + expression_literal(const std::string &s); + //! Create an int vector literal expression from a C++ vector<int>. + expression_literal(std::vector<int> v); + + expression::type_t infer_type() const + { + return _type; + } + + //! Literals aren't evaluated as such, so the evaluation + // simply returns a copy of itself. + expression_literal eval() + { + return *this; // TODO make sure this is copy + } + + /*! A 'type cast' to bool. Cast rules are similar to most + * scripting languages: + * - Integers and doubles are false if zero, true otherwise + * - Strings are false if empty, true otherwise + * - Vectors are false if empty, true otherwise + */ + bool to_bool() const; + + /*! Convenience function to typecast to C++ int + * + * Note that the current type must be TYPE_INT. + * + * \return C++ int representation of current literal + * \throws uhd::type_error if type didn't match + */ + int get_int() const; + + /*! Convenience function to typecast to C++ double + * + * Note that the current type must be TYPE_DOUBLE. + * + * \return C++ double representation of current literal + * \throws uhd::type_error if type didn't match + */ + double get_double() const; + + /*! Convenience function to typecast to C++ std::string. + * + * Note that the current type must be TYPE_STRING. + * + * \return String representation of current literal. + * \throws uhd::type_error if type didn't match. + */ + std::string get_string() const; + + /*! Convenience function to typecast to C++ int vector. + * + * Note that the current type must be TYPE_INT_VECTOR. + * + * \return String representation of current literal. + * \throws uhd::type_error if type didn't match. + */ + std::vector<int> get_int_vector() const; + + /*! Convenience function to typecast to C++ bool. + * + * Note that the current type must be TYPE_BOOL. + * See also expression_literal::to_bool() for a type-cast + * style function. + * + * \return bool representation of current literal. + * \throws uhd::type_error if type didn't match. + */ + bool get_bool() const; + + //! String representation + std::string repr() const; + + bool operator==(const expression_literal &rhs) const; + + private: + //! For TYPE_BOOL + bool _bool_val; + + //! For TYPE_INT + int _int_val; + + //! For TYPE_DOUBLE + double _double_val; + + //! For TYPE_INT_VECTOR + std::vector<int> _int_vector_val; + + //! Store the token value + std::string _val; + + //! Current expression type + expression::type_t _type; +}; + +UHD_INLINE std::ostream& operator<< (std::ostream& out, const expression_literal &l) +{ + out << l.repr(); + return out; +} + +UHD_INLINE std::ostream& operator<< (std::ostream& out, const expression_literal::sptr &l) +{ + out << l->repr(); + return out; +} + +/*! Contains multiple (sub-)expressions. + */ +class expression_container : public expression +{ + public: + typedef boost::shared_ptr<expression_container> sptr; + typedef std::vector<expression::sptr> expr_list_type; + + //! Return an sptr to an empty container + static sptr make(); + + //! List of valid combination types (see expression_container::eval()). + enum combiner_type { + COMBINE_ALL, + COMBINE_AND, + COMBINE_OR, + COMBINE_NOTSET + }; + + //! Create an empty container + expression_container() : _combiner(COMBINE_NOTSET) {}; + + /*! Type-deduction rules for containers are: + * - If the combination type is COMBINE_ALL or COMBINE_AND, + * return value must be TYPE_BOOL + * - In all other cases, we return the last expression return + * value, and hence its type is relevant + */ + expression::type_t infer_type() const; + + /*! Add another expression container to this container. + */ + virtual void add(expression::sptr new_expr); + + virtual bool empty() const; + + void set_combiner_safe(const combiner_type c); + + void set_combiner(const combiner_type c) { _combiner = c; }; + + combiner_type get_combiner() const { return _combiner; }; + + /*! Evaluate a container by evaluating its sub-expressions. + * + * If a container contains multiple sub-expressions, the rules + * for evaluating them depend on the combiner_type: + * - COMBINE_ALL: Run all the sub-expressions and return the last + * expression's return value + * - COMBINE_AND: Run sub-expressions, in order, until one of them + * returns false. Following expressions are not evaluated (like + * most C++ compilers). + * - COMBINE_OR: Run sub-expressions, in order, until one of them + * returns true. Following expressions are not evaluated. + * + * In the special case where no sub-expressions are contained, always + * returns true. + */ + virtual expression_literal eval(); + + protected: + //! Store all the sub-expressions, in order + expr_list_type _sub_exprs; + combiner_type _combiner; +}; + +// Forward declaration: +class function_table; +/*! A function call is a special type of container. + * + * All arguments are sub-expressions. The combiner type is + * always COMBINE_ALL in this case (changing the combiner type + * does not affect anything). + * + * The actual function maps to a C++ function available through + * a uhd::rfnoc::nocscript::function_table object. + * + * The recommended to use this is: + * 1. Create a function object giving its name (e.g. ADD) + * 2. Use the add() method to add all the function arguments + * in the right order (left to right). + * 3. Once step 2 is complete, the function object can be used. + * Call infer_type() to get the return value, if required. + * 4. Calling eval() will call into the function table. The + * argument expressions are evaluated, if so required, inside + * the function (lazy evalulation). Functions do not need + * to evaluate arguments. + */ +class expression_function : public expression_container +{ + public: + typedef boost::shared_ptr<expression_function> sptr; + typedef std::vector<expression::type_t> argtype_list_type; + + //! Return an sptr to a function object without args + static sptr make( + const std::string &name, + const boost::shared_ptr<function_table> func_table + ); + + static std::string to_string(const std::string &name, const argtype_list_type &types); + + expression_function( + const std::string &name, + const boost::shared_ptr<function_table> func_table + ); + + //! Add an argument expression + virtual void add(expression::sptr new_expr); + + /*! Looks up the function type in the function table. + * + * Note that this will only work after all arguments have been + * added, as they are also used to look up a function's type in the + * function table. + */ + expression::type_t infer_type() const; + + /*! Evaluate all arguments, then the function itself. + */ + expression_literal eval(); + + //! String representation + std::string repr() const; + + private: + std::string _name; + const boost::shared_ptr<function_table> _func_table; + std::vector<expression::type_t> _arg_types; +}; + + +/*! Variable expression + * + * Variables are like literals, only their type and value aren't known + * at parse-time. Instead, we provide a function object to look up + * variable's types and value. + */ +class expression_variable : public expression +{ + public: + typedef boost::shared_ptr<expression_variable> sptr; + typedef boost::function<expression::type_t(const std::string &)> type_getter_type; + typedef boost::function<expression_literal(const std::string &)> value_getter_type; + + static sptr make( + const std::string &token_val, + type_getter_type type_getter, + value_getter_type value_getter + ); + + /*! Create a variable object from its token value + * (e.g. '$spp', i.e. including the '$' symbol). The variable + * does not have to exist at this point. + */ + expression_variable( + const std::string &token_val, + type_getter_type type_getter, + value_getter_type value_getter + ); + + /*! Looks up the variable type in the variable table. + * + * \throws Depending on \p type_getter, this may throw when the variable does not exist. + * Recommended behaviour is to throw uhd::syntax_error. + */ + expression::type_t infer_type() const; + + /*! Look up a variable's value in the variable table. + * + * \throws Depending on \p value_getter, this may throw when the variable does not exist. + * Recommended behaviour is to throw uhd::syntax_error. + */ + expression_literal eval(); + + private: + std::string _varname; + type_getter_type _type_getter; + value_getter_type _value_getter; +}; + +}}} /* namespace uhd::rfnoc::nocscript */ + +#endif /* INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_EXPR_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/nocscript/function_table.cpp b/host/lib/rfnoc/nocscript/function_table.cpp new file mode 100644 index 000000000..bebceb8dc --- /dev/null +++ b/host/lib/rfnoc/nocscript/function_table.cpp @@ -0,0 +1,113 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "function_table.hpp" +#include "basic_functions.hpp" +#include <boost/bind.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <map> + +using namespace uhd::rfnoc::nocscript; + +class function_table_impl : public function_table +{ + public: + struct function_info { + expression::type_t return_type; + function_ptr function; + + function_info() {}; + function_info(const expression::type_t return_type_, const function_ptr &function_) + : return_type(return_type_), function(function_) + {}; + }; + // Should be an unordered_map... sigh, we'll get to C++11 someday. + typedef std::map<std::string, std::map<expression_function::argtype_list_type, function_info> > table_type; + + /************************************************************************ + * Structors + ***********************************************************************/ + function_table_impl() + { + _REGISTER_ALL_FUNCS(); + } + + ~function_table_impl() {}; + + + /************************************************************************ + * Interface implementation + ***********************************************************************/ + bool function_exists(const std::string &name) const { + return bool(_table.count(name)); + } + + bool function_exists( + const std::string &name, + const expression_function::argtype_list_type &arg_types + ) const { + table_type::const_iterator it = _table.find(name); + return (it != _table.end()) and bool(it->second.count(arg_types)); + } + + expression::type_t get_type( + const std::string &name, + const expression_function::argtype_list_type &arg_types + ) const { + table_type::const_iterator it = _table.find(name); + if (it == _table.end() or (it->second.find(arg_types) == it->second.end())) { + throw uhd::syntax_error(str( + boost::format("Unable to retrieve return value for function %s") + % expression_function::to_string(name, arg_types) + )); + } + return it->second.find(arg_types)->second.return_type; + } + + expression_literal eval( + const std::string &name, + const expression_function::argtype_list_type &arg_types, + expression_container::expr_list_type &arguments + ) { + if (not function_exists(name, arg_types)) { + throw uhd::syntax_error(str( + boost::format("Cannot eval() function %s, not a known signature") + % expression_function::to_string(name, arg_types) + )); + } + + return _table[name][arg_types].function(arguments); + } + + void register_function( + const std::string &name, + const function_table::function_ptr &ptr, + const expression::type_t return_type, + const expression_function::argtype_list_type &sig + ) { + _table[name][sig] = function_info(return_type, ptr); + } + + private: + table_type _table; +}; + +function_table::sptr function_table::make() +{ + return sptr(new function_table_impl()); +} diff --git a/host/lib/rfnoc/nocscript/function_table.hpp b/host/lib/rfnoc/nocscript/function_table.hpp new file mode 100644 index 000000000..6c715308e --- /dev/null +++ b/host/lib/rfnoc/nocscript/function_table.hpp @@ -0,0 +1,93 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "expression.hpp" +#include <boost/shared_ptr.hpp> +#include <boost/function.hpp> +#include <vector> + +#ifndef INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_FUNCTABLE_HPP +#define INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_FUNCTABLE_HPP + +namespace uhd { namespace rfnoc { namespace nocscript { + +class function_table +{ + public: + typedef boost::shared_ptr<function_table> sptr; + typedef boost::function<expression_literal(expression_container::expr_list_type&)> function_ptr; + + static sptr make(); + virtual ~function_table() {}; + + /*! Check if any function with a given name exists + * + * \returns True, if any function with name \p name is registered. + */ + virtual bool function_exists(const std::string &name) const = 0; + + /*! Check if a function with a given name and list of argument types exists + * + * \returns True, if such a function is registered. + */ + virtual bool function_exists( + const std::string &name, + const expression_function::argtype_list_type &arg_types + ) const = 0; + + /*! Get the return type of a function with given name and argument type list + * + * \returns The function's return type + * \throws uhd::syntax_error if no such function is registered + */ + virtual expression::type_t get_type( + const std::string &name, + const expression_function::argtype_list_type &arg_types + ) const = 0; + + /*! Calls the function \p name with the argument list \p arguments + * + * \param arg_types A list of types for each argument + * \param arguments An expression list of the arguments + * \returns The return value of the called function + * \throws uhd::syntax_error if no such function is found + */ + virtual expression_literal eval( + const std::string &name, + const expression_function::argtype_list_type &arg_types, + expression_container::expr_list_type &arguments + ) = 0; + + /*! Register a new function + * + * \param name Name of the function (e.g. 'ADD') + * \param ptr Function object + * \param return_type The function's return value + * \param sig The function signature (list of argument types) + */ + virtual void register_function( + const std::string &name, + const function_ptr &ptr, + const expression::type_t return_type, + const expression_function::argtype_list_type &sig + ) = 0; +}; + +}}} /* namespace uhd::rfnoc::nocscript */ + +#endif /* INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_FUNCTABLE_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/nocscript/gen_basic_funcs.py b/host/lib/rfnoc/nocscript/gen_basic_funcs.py new file mode 100755 index 000000000..702b3e884 --- /dev/null +++ b/host/lib/rfnoc/nocscript/gen_basic_funcs.py @@ -0,0 +1,474 @@ +#!/usr/bin/env python +""" +Generate the function list for the basic NocScript functions +""" + +import re +import os +import sys +from mako.template import Template + +############################################################################# +# This is the interesting part: Add new functions in here +# +# Notes: +# - Lines starting with # are considered comments, and will be removed from +# the output +# - C++ comments will be copied onto the generated file if inside functions +# - Docstrings start with //! and are required +# - Function signature is RETURN_TYPE NAME(ARG_TYPE1, ARG_TYPE2, ...) +# - Function body is valid C++ +# - If your function requires special includes, put them in INCLUDE_LIST +# - End of functions is delimited by s/^}/, so take care with the indents! +# - Use these substitutions: +# - ${RETURN}(...): Create a valid return value +# - ${args[n]}: Access the n-th argument +# +INCLUDE_LIST = """ +#include <boost/math/special_functions/round.hpp> +#include <boost/thread/thread.hpp> +""" +FUNCTION_LIST = """ +CATEGORY: Math Functions +//! Returns x + y +INT ADD(INT, INT) +{ + ${RETURN}(${args[0]} + ${args[1]}); +} + +//! Returns x + y +DOUBLE ADD(DOUBLE, DOUBLE) +{ + ${RETURN}(${args[0]} + ${args[1]}); +} + +//! Returns x * y +DOUBLE MULT(DOUBLE, DOUBLE) +{ + ${RETURN}(${args[0]} * ${args[1]}); +} + +//! Returns x * y +INT MULT(INT, INT) +{ + ${RETURN}(${args[0]} * ${args[1]}); +} + +//! Returns x / y +DOUBLE DIV(DOUBLE, DOUBLE) +{ + ${RETURN}(${args[0]} / ${args[1]}); +} + +//! Returns true if x <= y (Less or Equal) +BOOL LE(INT, INT) +{ + ${RETURN}(bool(${args[0]} <= ${args[1]})); +} + +//! Returns true if x <= y (Less or Equal) +BOOL LE(DOUBLE, DOUBLE) +{ + ${RETURN}(bool(${args[0]} <= ${args[1]})); +} + +//! Returns true if x >= y (Greater or Equal) +BOOL GE(INT, INT) +{ + ${RETURN}(bool(${args[0]} >= ${args[1]})); +} + +//! Returns true if x >= y (Greater or Equal) +BOOL GE(DOUBLE, DOUBLE) +{ + ${RETURN}(bool(${args[0]} >= ${args[1]})); +} + +//! Returns true if x < y (Less Than) +BOOL LT(INT, INT) +{ + ${RETURN}(bool(${args[0]} < ${args[1]})); +} + +//! Returns true if x > y (Greater Than) +BOOL GT(INT, INT) +{ + ${RETURN}(bool(${args[0]} > ${args[1]})); +} + +//! Returns true if x < y (Less Than) +BOOL LT(DOUBLE, DOUBLE) +{ + ${RETURN}(bool(${args[0]} < ${args[1]})); +} + +//! Returns true if x > y (Greater Than) +BOOL GT(DOUBLE, DOUBLE) +{ + ${RETURN}(bool(${args[0]} > ${args[1]})); +} + +//! Round x and return it as an integer +INT IROUND(DOUBLE) +{ + ${RETURN}(int(boost::math::iround(${args[0]}))); +} + +//! Returns true if x is a power of 2 +BOOL IS_PWR_OF_2(INT) +{ + if (${args[0]} < 0) return ${FALSE}; + int i = ${args[0]}; + while ( (i & 1) == 0 and (i > 1) ) { + i >>= 1; + } + ${RETURN}(bool(i == 1)); +} + +//! Returns floor(log2(x)). +INT LOG2(INT) +{ + if (${args[0]} < 0) { + throw uhd::runtime_error(str( + boost::format("In NocScript function ${func_name}: Cannot calculate log2() of negative number.") + )); + } + + int power_value = ${args[0]}; + int log2_value = 0; + while ( (power_value & 1) == 0 and (power_value > 1) ) { + power_value >>= 1; + log2_value++; + } + ${RETURN}(log2_value); +} + +//! Returns x % y +INT MODULO(INT, INT) +{ + ${RETURN}(${args[0]} % ${args[1]}); +} + +//! Returns true if x == y +BOOL EQUAL(INT, INT) +{ + ${RETURN}(bool(${args[0]} == ${args[1]})); +} + +//! Returns true if x == y +BOOL EQUAL(DOUBLE, DOUBLE) +{ + ${RETURN}(bool(${args[0]} == ${args[1]})); +} + +//! Returns true if x == y +BOOL EQUAL(STRING, STRING) +{ + ${RETURN}(bool(${args[0]} == ${args[1]})); +} + +CATEGORY: Bitwise Operations +//! Returns x >> y +INT SHIFT_RIGHT(INT, INT) +{ + ${RETURN}(${args[0]} >> ${args[1]}); +} + +//! Returns x << y +INT SHIFT_LEFT(INT, INT) +{ + ${RETURN}(${args[0]} << ${args[1]}); +} + +//! Returns x & y +INT BITWISE_AND(INT, INT) +{ + ${RETURN}(${args[0]} & ${args[1]}); +} + +//! Returns x | y +INT BITWISE_OR(INT, INT) +{ + ${RETURN}(${args[0]} | ${args[1]}); +} + +//! Returns x ^ y +INT BITWISE_XOR(INT, INT) +{ + ${RETURN}(${args[0]} ^ ${args[1]}); +} + +CATEGORY: Boolean Logic +//! Returns x xor y. +BOOL XOR(BOOL, BOOL) +{ + ${RETURN}(${args[0]} xor ${args[1]}); +} + +//! Returns !x +BOOL NOT(BOOL) +{ + ${RETURN}(not ${args[0]}); +} + +//! Always returns true +BOOL TRUE() +{ + return ${TRUE}; +} + +//! Always returns false +BOOL FALSE() +{ + return ${FALSE}; +} + +CATEGORY: Conditional Execution +//! Executes x, if true, execute y. Returns true if x is true. +BOOL IF(BOOL, BOOL) +{ + if (${args[0]}) { + ${args[1]}; + ${RETURN}(true); + } + ${RETURN}(false); +} + +//! Executes x, if true, execute y, otherwise, execute z. Returns true if x is true. +BOOL IF_ELSE(BOOL, BOOL, BOOL) +{ + if (${args[0]}) { + ${args[1]}; + ${RETURN}(true); + } else { + ${args[2]}; + } + ${RETURN}(false); +} + +CATEGORY: Execution Control +//! Sleep for x seconds. Fractions are allowed. Millisecond accuracy. +BOOL SLEEP(DOUBLE) +{ + int ms = ${args[0]} / 1000; + boost::this_thread::sleep(boost::posix_time::milliseconds(ms)); + ${RETURN}(true); +} +""" +# End of interesting part. The rest will take this and turn into a C++ +# header file. +############################################################################# + +HEADER = """<% import time %>// +/////////////////////////////////////////////////////////////////////// +// This file was generated by ${file} on ${time.strftime("%c")} +/////////////////////////////////////////////////////////////////////// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +/**************************************************************************** + * This file is autogenerated! Any manual changes in here will be + * overwritten by calling nocscript_gen_basic_funcs.py! + ***************************************************************************/ + +#include "expression.hpp" +#include "function_table.hpp" +#include <uhd/exception.hpp> +#include <boost/format.hpp> +#include <boost/assign/list_of.hpp> +${INCLUDE_LIST} + +#ifndef INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_BASICFUNCS_HPP +#define INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_BASICFUNCS_HPP + +namespace uhd { namespace rfnoc { namespace nocscript { +""" + +# Not a Mako template: +FOOTER=""" +}}} /* namespace uhd::rfnoc::nocscript */ + +#endif /* INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_BASICFUNCS_HPP */ +""" + +# Not a Mako template: +FUNC_TEMPLATE = """ +expression_literal {NAME}(expression_container::expr_list_type &{ARGS}) +{BODY} +""" + +REGISTER_MACRO_TEMPLATE = """#define _REGISTER_ALL_FUNCS()${registry} +""" + +REGISTER_COMMANDS_TEMPLATE = """ + % if len(arglist): + expression_function::argtype_list_type ${func_name}_args = boost::assign::list_of + % for this_type in arglist: + (expression::TYPE_${this_type}) + % endfor + ; + % else: + expression_function::argtype_list_type ${func_name}_args; + % endif + register_function( + "${name}", + boost::bind(&${func_name}, _1), + expression::TYPE_${retval}, + ${func_name}_args + );""" + +DOXY_TEMPLATE = """/*! \page page_nocscript_funcs NocScript Function Reference +% for cat, func_by_name in func_list_tree.iteritems(): +- ${cat} +% for func_name, func_info_list in func_by_name.iteritems(): + - ${func_name}: ${func_info_list[0]['docstring']} +% for func_info in func_info_list: + - ${func_info['arglist']} -> ${func_info['retval']} +% endfor +% endfor +% endfor + +*/ +""" + +def parse_tmpl(_tmpl_text, **kwargs): + return Template(_tmpl_text).render(**kwargs) + +def make_cxx_func_name(func_dict): + """ + Creates a unique C++ function name from a function description + """ + return "{name}__{retval}__{arglist}".format( + name=func_dict['name'], + retval=func_dict['retval'], + arglist="_".join(func_dict['arglist']) + ) + +def make_cxx_func_body(func_dict): + """ + Formats the function body properly + """ + type_lookup_methods = { + 'INT': 'get_int', + 'DOUBLE': 'get_double', + 'BOOL': 'get_bool', + 'STRING': 'get_string', + } + args_lookup = [] + for idx, arg_type in enumerate(func_dict['arglist']): + args_lookup.append("args[{idx}]->eval().{getter}()".format(idx=idx, getter=type_lookup_methods[arg_type])) + return parse_tmpl( + func_dict['body'], + args=args_lookup, + FALSE='expression_literal(false)', + TRUE='expression_literal(true)', + RETURN='return expression_literal', + **func_dict + ) + +def prep_function_list(): + """ + - Remove all comments + - Split the function list into individual functions + - Split the functions into return value, name, argument list and body + """ + comment_remove_re = re.compile(r'^\s*#.*$', flags=re.MULTILINE) + func_list_wo_comments = comment_remove_re.sub('', FUNCTION_LIST) + func_splitter_re = re.compile(r'(?<=^})\s*$', flags=re.MULTILINE) + func_list_split = func_splitter_re.split(func_list_wo_comments) + func_list_split = [x.strip() for x in func_list_split if len(x.strip())] + func_list = [] + last_category = '' + for func in func_list_split: + split_regex = r'(^CATEGORY: (?P<cat>[^\n]*)\s*)?' \ + r'//!(?P<docstring>[^\n]*)\s*' + \ + r'(?P<retval>[A-Z][A-Z0-9_]*)\s+' + \ + r'(?P<funcname>[A-Z][A-Z0-9_]*)\s*\((?P<arglist>[^\)]*)\)\s*' + \ + r'(?P<funcbody>^{.*)' + split_re = re.compile(split_regex, flags=re.MULTILINE|re.DOTALL) + mo = split_re.match(func) + if mo.group('cat'): + last_category = mo.group('cat').strip() + func_dict = { + 'docstring': mo.group('docstring').strip(), + 'name': mo.group('funcname'), + 'retval': mo.group('retval'), + 'arglist': [x.strip() for x in mo.group('arglist').split(',') if len(x.strip())], + 'body': mo.group('funcbody'), + 'category': last_category, + } + func_dict['func_name'] = make_cxx_func_name(func_dict) + func_list.append(func_dict) + return func_list + +def write_function_header(output_filename): + """ + Create the .hpp file that defines all the NocScript functions in C++. + """ + func_list = prep_function_list() + # Step 1: Write the prototypes + func_prototypes = '' + registry_commands = '' + for func in func_list: + func_prototypes += FUNC_TEMPLATE.format( + NAME=func['func_name'], + BODY=make_cxx_func_body(func), + ARGS="args" if len(func['arglist']) else "" + ) + registry_commands += parse_tmpl( + REGISTER_COMMANDS_TEMPLATE, + **func + ) + # Step 2: Write the registry process + register_func = parse_tmpl(REGISTER_MACRO_TEMPLATE, registry=registry_commands) + register_func = register_func.replace('\n', ' \\\n') + + # Final step: Join parts and write to file + full_file = "\n".join(( + parse_tmpl(HEADER, file = os.path.basename(__file__), INCLUDE_LIST=INCLUDE_LIST), + func_prototypes, + register_func, + FOOTER, + )) + open(output_filename, 'w').write(full_file) + +def write_manual_file(output_filename): + """ + Write the Doxygen file for the NocScript functions. + """ + func_list = prep_function_list() + func_list_tree = {} + for func in func_list: + if not func_list_tree.has_key(func['category']): + func_list_tree[func['category']] = {} + if not func_list_tree[func['category']].has_key(func['name']): + func_list_tree[func['category']][func['name']] = [] + func_list_tree[func['category']][func['name']].append(func) + open(output_filename, 'w').write(parse_tmpl(DOXY_TEMPLATE, func_list_tree=func_list_tree)) + + +def main(): + if len(sys.argv) < 2: + print("No output file specified!") + exit(1) + outfile = sys.argv[1] + if os.path.splitext(outfile)[1] == '.dox': + write_manual_file(outfile) + else: + write_function_header(outfile) + +if __name__ == "__main__": + main() diff --git a/host/lib/rfnoc/nocscript/parser.cpp b/host/lib/rfnoc/nocscript/parser.cpp new file mode 100644 index 000000000..bb7ed6cdf --- /dev/null +++ b/host/lib/rfnoc/nocscript/parser.cpp @@ -0,0 +1,363 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "parser.hpp" +#include <uhd/utils/cast.hpp> +#include <boost/spirit/include/lex_lexertl.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <boost/assign.hpp> +#include <boost/make_shared.hpp> +#include <sstream> +#include <stack> + +using namespace uhd::rfnoc::nocscript; +namespace lex = boost::spirit::lex; + +class parser_impl : public parser +{ + public: + /****************************************************************** + * Structors TODO make them protected + *****************************************************************/ + parser_impl( + function_table::sptr ftable, + expression_variable::type_getter_type var_type_getter, + expression_variable::value_getter_type var_value_getter + ) : _ftable(ftable) + , _var_type_getter(var_type_getter) + , _var_value_getter(var_value_getter) + { + // nop + } + + ~parser_impl() {}; + + + /****************************************************************** + * Parsing + *****************************************************************/ + //! List of parser tokens + enum token_ids + { + ID_WHITESPACE = lex::min_token_id + 42, + ID_KEYWORD, + ID_ARG_SEP, + ID_PARENS_OPEN, + ID_PARENS_CLOSE, + ID_VARIABLE, + ID_LITERAL_DOUBLE, + ID_LITERAL_INT, + ID_LITERAL_HEX, + ID_LITERAL_STR, + ID_LITERAL_VECTOR_INT + }; + + //! The Lexer object used for NocScript + template <typename Lexer> + struct ns_lexer : lex::lexer<Lexer> + { + ns_lexer() + { + this->self.add + ("\\s+", ID_WHITESPACE) + (",", ID_ARG_SEP) + ("[A-Z][A-Z0-9_]*", ID_KEYWORD) + ("\\(", ID_PARENS_OPEN) + ("\\)", ID_PARENS_CLOSE) + ("\\$[a-z][a-z0-9_]*", ID_VARIABLE) + ("-?\\d+\\.\\d+", ID_LITERAL_DOUBLE) + ("-?\\d+", ID_LITERAL_INT) + ("0x[0-9A-F]+", ID_LITERAL_HEX) + ("\\\"[^\\\"]*\\\"", ID_LITERAL_STR) + ("'[^']*'", ID_LITERAL_STR) // both work + ("\\[[0-9]\\]", ID_LITERAL_VECTOR_INT) + ; + } + }; + + private: + struct grammar_props + { + function_table::sptr ftable; + expression_variable::type_getter_type var_type_getter; + expression_variable::value_getter_type var_value_getter; + + //! Store the last keyword + std::string function_name; + std::string error; + std::stack<expression_container::sptr> expr_stack; + + grammar_props( + function_table::sptr ftable_, + expression_variable::type_getter_type var_type_getter_, + expression_variable::value_getter_type var_value_getter_ + ) : ftable(ftable_), var_type_getter(var_type_getter_), var_value_getter(var_value_getter_), + function_name("") + { + UHD_ASSERT_THROW(expr_stack.empty()); + // Push an empty container to the stack to hold the result + expr_stack.push(expression_container::make()); + } + + expression::sptr get_result() + { + UHD_ASSERT_THROW(expr_stack.size() == 1); + return expr_stack.top(); + } + }; + + //! This isn't strictly a grammar, as it also includes semantic + // actions etc. I'm not going to spend ages thinking of a better + // name at this point. + struct grammar + { + // Implementation detail specific to boost::bind (see Boost::Spirit + // examples) + typedef bool result_type; + + static const int VALID_COMMA = 0x1; + static const int VALID_PARENS_OPEN = 0x2; + static const int VALID_PARENS_CLOSE = 0x4; + static const int VALID_EXPRESSION = 0x8 + 0x02; + static const int VALID_OPERATOR = 0x10; + + // !This function operator gets called for each of the matched tokens. + template <typename Token> + bool operator()(Token const& t, grammar_props &P, int &next_valid_state) const + { + //! This is totally not how Boost::Spirit is meant to be used, + // as there's token types etc. But for now let's just convert + // every token to a string, and then handle it as such. + std::stringstream sstr; + sstr << t.value(); + std::string val = sstr.str(); + //std::cout << "VAL: " << val << std::endl; + //std::cout << "Next valid states:\n" + //<< boost::format("VALID_COMMA [%s]\n") % ((next_valid_state & 0x1) ? "x" : " ") + //<< boost::format("VALID_PARENS_OPEN [%s]\n") % ((next_valid_state & 0x2) ? "x" : " ") + //<< boost::format("VALID_PARENS_CLOSE [%s]\n") % ((next_valid_state & 0x4) ? "x" : " ") + //<< boost::format("VALID_EXPRESSION [%s]\n") % ((next_valid_state & (0x8 + 0x02)) ? "x" : " ") + //<< boost::format("VALID_OPERATOR [%s]\n") % ((next_valid_state & 0x10) ? "x" : " ") + //<< std::endl; + + switch (t.id()) { + + case ID_WHITESPACE: + // Ignore + break; + + case ID_KEYWORD: + // Ambiguous, could be an operator (AND, OR) or a function name (ADD, MULT...). + // So first, check which it is: + if (val == "AND" or val == "OR") { + if (not (next_valid_state & VALID_OPERATOR)) { + P.error = str(boost::format("Unexpected operator: %s") % val); + return false; + } + next_valid_state = VALID_EXPRESSION; + try { + if (val == "AND") { + P.expr_stack.top()->set_combiner_safe(expression_container::COMBINE_AND); + } else if (val == "OR") { + P.expr_stack.top()->set_combiner_safe(expression_container::COMBINE_OR); + } + } catch (const uhd::syntax_error &e) { + P.error = str(boost::format("Operator %s is mixing operator types within this container.") % val); + } + // Right now, we can't have multiple operator types within a container. + // We might be able to change that, if there's enough demand. Either + // we keep track of multiple operators, or we open a new container. + // In the latter case, we'd need a way of keeping track of those containers, + // so it's a bit tricky. + break; + } + // If it's not a keyword, it has to be a function, so check the + // function table: + if (not (next_valid_state & VALID_EXPRESSION)) { + P.error = str(boost::format("Unexpected expression: %s") % val); + return false; + } + if (not P.ftable->function_exists(val)) { + P.error = str(boost::format("Unknown function: %s") % val); + return false; + } + P.function_name = val; + next_valid_state = VALID_PARENS_OPEN; + break; + + // Every () creates a new container, either a raw container or + // a function. + case ID_PARENS_OPEN: + if (not (next_valid_state & VALID_PARENS_OPEN)) { + P.error = str(boost::format("Unexpected parentheses.")); + return false; + } + if (not P.function_name.empty()) { + // We've already checked the function name exists + P.expr_stack.push(expression_function::make(P.function_name, P.ftable)); + P.function_name.clear(); + } else { + P.expr_stack.push(expression_container::make()); + } + // Push another empty container to hold the first element/argument + // in this container: + P.expr_stack.push(expression_container::make()); + next_valid_state = VALID_EXPRESSION | VALID_PARENS_CLOSE; + break; + + case ID_PARENS_CLOSE: + { + if (not (next_valid_state & VALID_PARENS_CLOSE)) { + P.error = str(boost::format("Unexpected parentheses.")); + return false; + } + if (P.expr_stack.size() < 2) { + P.error = str(boost::format("Unbalanced closing parentheses.")); + return false; + } + // First pop the last expression inside the parentheses, + // if it's not empty, add it to the top container (this also avoids + // adding arguments to functions if none were provided): + expression_container::sptr c = P.expr_stack.top(); + P.expr_stack.pop(); + if (not c->empty()) { + P.expr_stack.top()->add(c); + } + // At the end of (), either a function or container is complete, + // so pop that and add it to its top container: + expression_container::sptr c2 = P.expr_stack.top(); + P.expr_stack.pop(); + P.expr_stack.top()->add(c2); + next_valid_state = VALID_OPERATOR | VALID_COMMA | VALID_PARENS_CLOSE; + } + break; + + case ID_ARG_SEP: + { + if (not (next_valid_state & VALID_COMMA)) { + P.error = str(boost::format("Unexpected comma.")); + return false; + } + next_valid_state = VALID_EXPRESSION; + // If stack size is 1, we're on the base container, which means we + // simply string stuff. + if (P.expr_stack.size() == 1) { + break; + } + // Otherwise, a ',' always means we add the previous expression to + // the current container: + expression_container::sptr c = P.expr_stack.top(); + P.expr_stack.pop(); + P.expr_stack.top()->add(c); + // It also means another expression is following, so create another + // empty container for that: + P.expr_stack.push(expression_container::make()); + } + break; + + // All the atomic expressions just get added to the current container: + + case ID_VARIABLE: + { + if (not (next_valid_state & VALID_EXPRESSION)) { + P.error = str(boost::format("Unexpected expression.")); + return false; + } + expression_variable::sptr v = expression_variable::make(val, P.var_type_getter, P.var_value_getter); + P.expr_stack.top()->add(v); + next_valid_state = VALID_OPERATOR | VALID_COMMA | VALID_PARENS_CLOSE; + } + break; + + default: + // If we get here, we assume it's a literal expression + { + if (not (next_valid_state & VALID_EXPRESSION)) { + P.error = str(boost::format("Unexpected expression.")); + return false; + } + expression::type_t token_type; + switch (t.id()) { // A map lookup would be more elegant, but we'd need a nicer C++ for that + case ID_LITERAL_DOUBLE: token_type = expression::TYPE_DOUBLE; break; + case ID_LITERAL_INT: token_type = expression::TYPE_INT; break; + case ID_LITERAL_HEX: token_type = expression::TYPE_INT; break; + case ID_LITERAL_STR: token_type = expression::TYPE_STRING; break; + case ID_LITERAL_VECTOR_INT: token_type = expression::TYPE_INT_VECTOR; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + P.expr_stack.top()->add(boost::make_shared<expression_literal>(val, token_type)); + next_valid_state = VALID_OPERATOR | VALID_COMMA | VALID_PARENS_CLOSE; + break; + } + + } // end switch + return true; + } + }; + + public: + expression::sptr create_expr_tree(const std::string &code) + { + // Create empty stack and keyword states + grammar_props P(_ftable, _var_type_getter, _var_value_getter); + int next_valid_state = grammar::VALID_EXPRESSION; + + // Create a lexer instance + ns_lexer<lex::lexertl::lexer<> > lexer_functor; + + // Tokenize the string + char const* first = code.c_str(); + char const* last = &first[code.size()]; + bool r = lex::tokenize( + first, last, // Iterators + lexer_functor, // Lexer + boost::bind(grammar(), _1, boost::ref(P), boost::ref(next_valid_state)) // Function object + ); + + // Check the parsing worked: + if (not r or P.expr_stack.size() != 1) { + std::string rest(first, last); + throw uhd::syntax_error(str( + boost::format("Parsing stopped at: %s\nError message: %s") + % rest % P.error + )); + } + + // Clear stack and return result + return P.get_result(); + } + + private: + + function_table::sptr _ftable; + expression_variable::type_getter_type _var_type_getter; + expression_variable::value_getter_type _var_value_getter; +}; + +parser::sptr parser::make( + function_table::sptr ftable, + expression_variable::type_getter_type var_type_getter, + expression_variable::value_getter_type var_value_getter +) { + return sptr(new parser_impl( + ftable, + var_type_getter, + var_value_getter + )); +} + diff --git a/host/lib/rfnoc/nocscript/parser.hpp b/host/lib/rfnoc/nocscript/parser.hpp new file mode 100644 index 000000000..32fecd2c0 --- /dev/null +++ b/host/lib/rfnoc/nocscript/parser.hpp @@ -0,0 +1,50 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "expression.hpp" +#include "function_table.hpp" +#include <boost/shared_ptr.hpp> + +#ifndef INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_PARSER_HPP +#define INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_PARSER_HPP + +namespace uhd { namespace rfnoc { namespace nocscript { + +class parser +{ + public: + typedef boost::shared_ptr<parser> sptr; + + static sptr make( + function_table::sptr ftable, + expression_variable::type_getter_type var_type_getter, + expression_variable::value_getter_type var_value_getter + ); + + /*! The main parsing call: Turn a string of code into an expression tree. + * + * Evaluating the returned object will execute the code. + * + * \throws uhd::syntax_error if \p code contains syntax errors + */ + virtual expression::sptr create_expr_tree(const std::string &code) = 0; +}; + +}}} /* namespace uhd::rfnoc::nocscript */ + +#endif /* INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_PARSER_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/node_ctrl_base.cpp b/host/lib/rfnoc/node_ctrl_base.cpp new file mode 100644 index 000000000..6e19d276a --- /dev/null +++ b/host/lib/rfnoc/node_ctrl_base.cpp @@ -0,0 +1,103 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/utils/msg.hpp> + +using namespace uhd::rfnoc; + +std::string node_ctrl_base::unique_id() const +{ + // Most instantiations will override this, so we don't need anything + // more elegant here. + return str(boost::format("%08X") % size_t(this)); +} + +void node_ctrl_base::clear() +{ + UHD_RFNOC_BLOCK_TRACE() << "node_ctrl_base::clear() " << std::endl; + // Reset connections: + _upstream_nodes.clear(); + _downstream_nodes.clear(); +} + +void node_ctrl_base::_register_downstream_node( + node_ctrl_base::sptr, + size_t +) { + throw uhd::runtime_error("Attempting to register a downstream block on a non-source node."); +} + +void node_ctrl_base::_register_upstream_node( + node_ctrl_base::sptr, + size_t +) { + throw uhd::runtime_error("Attempting to register an upstream block on a non-sink node."); +} + +void node_ctrl_base::set_downstream_port( + const size_t this_port, + const size_t remote_port +) { + if (not _downstream_nodes.count(this_port) and remote_port != ANY_PORT) { + throw uhd::value_error(str( + boost::format("[%s] Cannot set remote downstream port: Port %d not connected.") + % unique_id() % this_port + )); + } + _downstream_ports[this_port] = remote_port; +} + +size_t node_ctrl_base::get_downstream_port(const size_t this_port) +{ + if (not _downstream_ports.count(this_port) + or not _downstream_nodes.count(this_port) + or _downstream_ports[this_port] == ANY_PORT) { + throw uhd::value_error(str( + boost::format("[%s] Cannot retrieve remote downstream port: Port %d not connected.") + % unique_id() % this_port + )); + } + return _downstream_ports[this_port]; +} + +void node_ctrl_base::set_upstream_port( + const size_t this_port, + const size_t remote_port +) { + if (not _upstream_nodes.count(this_port) and remote_port != ANY_PORT) { + throw uhd::value_error(str( + boost::format("[%s] Cannot set remote upstream port: Port %d not connected.") + % unique_id() % this_port + )); + } + _upstream_ports[this_port] = remote_port; +} + +size_t node_ctrl_base::get_upstream_port(const size_t this_port) +{ + if (not _upstream_ports.count(this_port) + or not _upstream_nodes.count(this_port) + or _upstream_ports[this_port] == ANY_PORT) { + throw uhd::value_error(str( + boost::format("[%s] Cannot retrieve remote upstream port: Port %d not connected.") + % unique_id() % this_port + )); + } + return _upstream_ports[this_port]; +} + diff --git a/host/lib/rfnoc/radio_ctrl_impl.cpp b/host/lib/rfnoc/radio_ctrl_impl.cpp new file mode 100644 index 000000000..6ed09ecdd --- /dev/null +++ b/host/lib/rfnoc/radio_ctrl_impl.cpp @@ -0,0 +1,364 @@ +// +// Copyright 2014-2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "wb_iface_adapter.hpp" +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <uhd/convert.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/direction.hpp> +#include "radio_ctrl_impl.hpp" +#include "../../transport/super_recv_packet_handler.hpp" + +using namespace uhd; +using namespace uhd::rfnoc; + +static const size_t BYTES_PER_SAMPLE = 4; + +/**************************************************************************** + * Structors and init + ***************************************************************************/ +// Note: block_ctrl_base must be called before this, but has to be called by +// the derived class because of virtual inheritance +radio_ctrl_impl::radio_ctrl_impl() : + _tick_rate(rfnoc::rate_node_ctrl::RATE_UNDEFINED) +{ + _num_rx_channels = get_output_ports().size(); + _num_tx_channels = get_input_ports().size(); + _continuous_streaming = std::vector<bool>(2, false); + + for (size_t i = 0; i < _num_rx_channels; i++) { + _rx_streamer_active[i] = false; + } + for (size_t i = 0; i < _num_tx_channels; i++) { + _tx_streamer_active[i] = false; + } + + ///////////////////////////////////////////////////////////////////////// + // Setup peripherals + ///////////////////////////////////////////////////////////////////////// + for (size_t i = 0; i < _get_num_radios(); i++) { + _register_loopback_self_test(i); + _perifs[i].ctrl = boost::make_shared<wb_iface_adapter>( + // poke32 functor + boost::bind( + static_cast< void (block_ctrl_base::*)(const boost::uint32_t, const boost::uint32_t, const size_t) >(&block_ctrl_base::sr_write), + this, _1, _2, i + ), + // peek32 functor + boost::bind( + static_cast< boost::uint32_t (block_ctrl_base::*)(const boost::uint32_t, const size_t) >(&block_ctrl_base::user_reg_read32), + this, + _1, i + ), + // peek64 functor + boost::bind( + static_cast< boost::uint64_t (block_ctrl_base::*)(const boost::uint32_t, const size_t) >(&block_ctrl_base::user_reg_read64), + this, + _1, i + ), + // get_time functor + boost::bind( + static_cast< time_spec_t (block_ctrl_base::*)(const size_t) >(&block_ctrl_base::get_command_time), + this, i + ), + // set_time functor + boost::bind( + static_cast< void (block_ctrl_base::*)(const time_spec_t&, const size_t) >(&block_ctrl_base::set_command_time), + this, + _1, i + ) + ); + + // FIXME there's currently no way to set the underflow policy + + if (i == 0) { + time_core_3000::readback_bases_type time64_rb_bases; + time64_rb_bases.rb_now = regs::RB_TIME_NOW; + time64_rb_bases.rb_pps = regs::RB_TIME_PPS; + _time64 = time_core_3000::make(_perifs[i].ctrl, regs::sr_addr(regs::TIME), time64_rb_bases); + this->set_time_now(0.0); + } + + //Reset the RX control engine + sr_write(regs::RX_CTRL_HALT, 1, i); + } + + //////////////////////////////////////////////////////////////////// + // Register the time keeper + //////////////////////////////////////////////////////////////////// + if (not _tree->exists(fs_path("time") / "now")) { + _tree->create<time_spec_t>(fs_path("time") / "now") + .set_publisher(boost::bind(&radio_ctrl_impl::get_time_now, this)) + ; + } + if (not _tree->exists(fs_path("time") / "pps")) { + _tree->create<time_spec_t>(fs_path("time") / "pps") + .set_publisher(boost::bind(&radio_ctrl_impl::get_time_last_pps, this)) + ; + } + if (not _tree->exists(fs_path("time") / "cmd")) { + _tree->create<time_spec_t>(fs_path("time") / "cmd"); + } + _tree->access<time_spec_t>(fs_path("time") / "now") + .add_coerced_subscriber(boost::bind(&radio_ctrl_impl::set_time_now, this, _1)) + ; + _tree->access<time_spec_t>(fs_path("time") / "pps") + .add_coerced_subscriber(boost::bind(&radio_ctrl_impl::set_time_next_pps, this, _1)) + ; + for (size_t i = 0; i < _get_num_radios(); i++) { + _tree->access<time_spec_t>("time/cmd") + .add_coerced_subscriber(boost::bind(&block_ctrl_base::set_command_tick_rate, this, boost::ref(_tick_rate), i)) + .add_coerced_subscriber(boost::bind(&block_ctrl_base::set_command_time, this, _1, i)) + ; + } + // spp gets created in the XML file + _tree->access<int>(get_arg_path("spp") / "value") + .add_coerced_subscriber(boost::bind(&radio_ctrl_impl::_update_spp, this, _1)) + .update() + ; +} + +void radio_ctrl_impl::_register_loopback_self_test(size_t chan) +{ + UHD_MSG(status) << "[RFNoC Radio] Performing register loopback test... " << std::flush; + size_t hash = size_t(time(NULL)); + for (size_t i = 0; i < 100; i++) + { + boost::hash_combine(hash, i); + sr_write(regs::TEST, boost::uint32_t(hash), chan); + boost::uint32_t result = user_reg_read32(regs::RB_TEST, chan); + if (result != boost::uint32_t(hash)) { + UHD_MSG(status) << "fail" << std::endl; + UHD_MSG(status) << boost::format("expected: %x result: %x") % boost::uint32_t(hash) % result << std::endl; + return; // exit on any failure + } + } + UHD_MSG(status) << "pass" << std::endl; +} + +/**************************************************************************** + * API calls + ***************************************************************************/ +double radio_ctrl_impl::set_rate(double rate) +{ + boost::mutex::scoped_lock lock(_mutex); + _tick_rate = rate; + _time64->set_tick_rate(_tick_rate); + _time64->self_test(); + return _tick_rate; +} + +void radio_ctrl_impl::set_tx_antenna(const std::string &ant, const size_t chan) +{ + _tx_antenna[chan] = ant; +} + +void radio_ctrl_impl::set_rx_antenna(const std::string &ant, const size_t chan) +{ + _rx_antenna[chan] = ant; +} + +double radio_ctrl_impl::set_tx_frequency(const double freq, const size_t chan) +{ + return _tx_freq[chan] = freq; +} + +double radio_ctrl_impl::set_rx_frequency(const double freq, const size_t chan) +{ + return _rx_freq[chan] = freq; +} + +double radio_ctrl_impl::set_tx_gain(const double gain, const size_t chan) +{ + return _tx_gain[chan] = gain; +} + +double radio_ctrl_impl::set_rx_gain(const double gain, const size_t chan) +{ + return _rx_gain[chan] = gain; +} + +void radio_ctrl_impl::set_time_sync(const uhd::time_spec_t &time) +{ + _time64->set_time_sync(time); +} + +double radio_ctrl_impl::get_rate() const +{ + return _tick_rate; +} + +std::string radio_ctrl_impl::get_tx_antenna(const size_t chan) /* const */ +{ + return _tx_antenna[chan]; +} + +std::string radio_ctrl_impl::get_rx_antenna(const size_t chan) /* const */ +{ + return _rx_antenna[chan]; +} + +double radio_ctrl_impl::get_tx_frequency(const size_t chan) /* const */ +{ + return _tx_freq[chan]; +} + +double radio_ctrl_impl::get_rx_frequency(const size_t chan) /* const */ +{ + return _rx_freq[chan]; +} + +double radio_ctrl_impl::get_tx_gain(const size_t chan) /* const */ +{ + return _tx_gain[chan]; +} + +double radio_ctrl_impl::get_rx_gain(const size_t chan) /* const */ +{ + return _rx_gain[chan]; +} + +/*********************************************************************** + * RX Streamer-related methods (from source_block_ctrl_base) + **********************************************************************/ +//! Pass stream commands to the radio +void radio_ctrl_impl::issue_stream_cmd(const uhd::stream_cmd_t &stream_cmd, const size_t chan) +{ + boost::mutex::scoped_lock lock(_mutex); + UHD_RFNOC_BLOCK_TRACE() << "radio_ctrl_impl::issue_stream_cmd() " << chan << " " << char(stream_cmd.stream_mode) << std::endl; + UHD_ASSERT_THROW(stream_cmd.num_samps <= 0x0fffffff); + _continuous_streaming[chan] = (stream_cmd.stream_mode == stream_cmd_t::STREAM_MODE_START_CONTINUOUS); + + //setup the mode to instruction flags + typedef boost::tuple<bool, bool, bool, bool> inst_t; + static const uhd::dict<stream_cmd_t::stream_mode_t, inst_t> mode_to_inst = boost::assign::map_list_of + //reload, chain, samps, stop + (stream_cmd_t::STREAM_MODE_START_CONTINUOUS, inst_t(true, true, false, false)) + (stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS, inst_t(false, false, false, true)) + (stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE, inst_t(false, false, true, false)) + (stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE, inst_t(false, true, true, false)) + ; + + //setup the instruction flag values + bool inst_reload, inst_chain, inst_samps, inst_stop; + boost::tie(inst_reload, inst_chain, inst_samps, inst_stop) = mode_to_inst[stream_cmd.stream_mode]; + + //calculate the word from flags and length + boost::uint32_t cmd_word = 0; + cmd_word |= boost::uint32_t((stream_cmd.stream_now)? 1 : 0) << 31; + cmd_word |= boost::uint32_t((inst_chain)? 1 : 0) << 30; + cmd_word |= boost::uint32_t((inst_reload)? 1 : 0) << 29; + cmd_word |= boost::uint32_t((inst_stop)? 1 : 0) << 28; + cmd_word |= (inst_samps)? stream_cmd.num_samps : ((inst_stop)? 0 : 1); + + //issue the stream command + const boost::uint64_t ticks = (stream_cmd.stream_now)? 0 : stream_cmd.time_spec.to_ticks(get_rate()); + sr_write(regs::RX_CTRL_CMD, cmd_word, chan); + sr_write(regs::RX_CTRL_TIME_HI, boost::uint32_t(ticks >> 32), chan); + sr_write(regs::RX_CTRL_TIME_LO, boost::uint32_t(ticks >> 0), chan); //latches the command +} + +std::vector<size_t> radio_ctrl_impl::get_active_rx_ports() +{ + std::vector<size_t> active_rx_ports; + typedef std::map<size_t, bool> map_t; + BOOST_FOREACH(map_t::value_type &m, _rx_streamer_active) { + if (m.second) { + active_rx_ports.push_back(m.first); + } + } + return active_rx_ports; +} + +/*********************************************************************** + * Radio controls (radio_ctrl specific) + **********************************************************************/ +void radio_ctrl_impl::set_rx_streamer(bool active, const size_t port) +{ + UHD_RFNOC_BLOCK_TRACE() << "radio_ctrl_impl::set_rx_streamer() " << port << " -> " << active << std::endl; + if (port > _num_rx_channels) { + throw uhd::value_error(str( + boost::format("[%s] Can't (un)register RX streamer on port %d (invalid port)") + % unique_id() % port + )); + } + _rx_streamer_active[port] = active; + if (not check_radio_config()) { + throw std::runtime_error(str( + boost::format("[%s]: Invalid radio configuration.") + % unique_id() + )); + } +} + +void radio_ctrl_impl::set_tx_streamer(bool active, const size_t port) +{ + UHD_RFNOC_BLOCK_TRACE() << "radio_ctrl_impl::set_tx_streamer() " << port << " -> " << active << std::endl; + if (port > _num_tx_channels) { + throw uhd::value_error(str( + boost::format("[%s] Can't (un)register TX streamer on port %d (invalid port)") + % unique_id() % port + )); + } + _tx_streamer_active[port] = active; + if (not check_radio_config()) { + throw std::runtime_error(str( + boost::format("[%s]: Invalid radio configuration.") + % unique_id() + )); + } +} + +// Subscribers to block args: +// TODO move to nocscript +void radio_ctrl_impl::_update_spp(int spp) +{ + boost::mutex::scoped_lock lock(_mutex); + UHD_RFNOC_BLOCK_TRACE() << "radio_ctrl_impl::_update_spp(): Requested spp: " << spp << std::endl; + if (spp == 0) { + spp = DEFAULT_PACKET_SIZE / BYTES_PER_SAMPLE; + } + UHD_RFNOC_BLOCK_TRACE() << "radio_ctrl_impl::_update_spp(): Setting spp to: " << spp << std::endl; + for (size_t i = 0; i < _num_rx_channels; i++) { + sr_write(regs::RX_CTRL_MAXLEN, uint32_t(spp), i); + } +} + +void radio_ctrl_impl::set_time_now(const time_spec_t &time_spec) +{ + _time64->set_time_now(time_spec); +} + +void radio_ctrl_impl::set_time_next_pps(const time_spec_t &time_spec) +{ + _time64->set_time_next_pps(time_spec); +} + + +time_spec_t radio_ctrl_impl::get_time_now() +{ + return _time64->get_time_now(); +} + +time_spec_t radio_ctrl_impl::get_time_last_pps() +{ + return _time64->get_time_last_pps(); +} + diff --git a/host/lib/rfnoc/radio_ctrl_impl.hpp b/host/lib/rfnoc/radio_ctrl_impl.hpp new file mode 100644 index 000000000..4224ec5c4 --- /dev/null +++ b/host/lib/rfnoc/radio_ctrl_impl.hpp @@ -0,0 +1,209 @@ +// +// Copyright 2014-2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_RADIO_CTRL_IMPL_HPP +#define INCLUDED_LIBUHD_RFNOC_RADIO_CTRL_IMPL_HPP + +#include "rx_vita_core_3000.hpp" +#include "tx_vita_core_3000.hpp" +#include "time_core_3000.hpp" +#include "gpio_atr_3000.hpp" +#include <uhd/rfnoc/radio_ctrl.hpp> +#include <uhd/types/direction.hpp> +#include <boost/thread.hpp> + +//! Shorthand for radio block constructor +#define UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR_DECL(CLASS_NAME) \ + CLASS_NAME##_impl(const make_args_t &make_args); + +#define UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR(CLASS_NAME) \ + CLASS_NAME##_impl::CLASS_NAME##_impl( \ + const make_args_t &make_args \ + ) : block_ctrl_base(make_args), radio_ctrl_impl() + +namespace uhd { + namespace rfnoc { + +/*! \brief Provide access to a radio. + * + */ +class radio_ctrl_impl : public radio_ctrl +{ +public: + /************************************************************************ + * Structors + ***********************************************************************/ + radio_ctrl_impl(); + virtual ~radio_ctrl_impl() {}; + + /************************************************************************ + * Public Radio API calls + ***********************************************************************/ + virtual double set_rate(double rate); + virtual void set_tx_antenna(const std::string &ant, const size_t chan); + virtual void set_rx_antenna(const std::string &ant, const size_t chan); + virtual double set_tx_frequency(const double freq, const size_t chan); + virtual double set_rx_frequency(const double freq, const size_t chan); + virtual double set_tx_gain(const double gain, const size_t chan); + virtual double set_rx_gain(const double gain, const size_t chan); + virtual void set_time_sync(const uhd::time_spec_t &time); + + virtual double get_rate() const; + virtual std::string get_tx_antenna(const size_t chan) /* const */; + virtual std::string get_rx_antenna(const size_t chan) /* const */; + virtual double get_tx_frequency(const size_t) /* const */; + virtual double get_rx_frequency(const size_t) /* const */; + virtual double get_tx_gain(const size_t) /* const */; + virtual double get_rx_gain(const size_t) /* const */; + + void set_time_now(const time_spec_t &time_spec); + void set_time_next_pps(const time_spec_t &time_spec); + time_spec_t get_time_now(); + time_spec_t get_time_last_pps(); + + /*********************************************************************** + * Block control API calls + **********************************************************************/ + void set_rx_streamer(bool active, const size_t port); + void set_tx_streamer(bool active, const size_t port); + + void issue_stream_cmd(const uhd::stream_cmd_t &stream_cmd, const size_t port); + + virtual double get_input_samp_rate(size_t /* port */) { return get_rate(); } + virtual double get_output_samp_rate(size_t /* port */) { return get_rate(); } + double _get_tick_rate() { return get_rate(); } + + std::vector<size_t> get_active_rx_ports(); + bool in_continuous_streaming_mode(const size_t chan) { return _continuous_streaming.at(chan); } + void rx_ctrl_clear_cmds(const size_t port) { sr_write(regs::RX_CTRL_CLEAR_CMDS, 0, port); } + +protected: // TODO see what's protected and what's private + void _register_loopback_self_test(size_t chan); + + /*********************************************************************** + * Registers + **********************************************************************/ + struct regs { + static inline boost::uint32_t sr_addr(const boost::uint32_t offset) + { + return offset * 4; + } + + static const uint32_t BASE = 128; + + // defined in radio_core_regs.vh + static const uint32_t TIME = 128; // time hi - 128, time lo - 129, ctrl - 130 + static const uint32_t CLEAR_CMDS = 131; // Any write to this reg clears the command FIFO + static const uint32_t LOOPBACK = 132; + static const uint32_t TEST = 133; + static const uint32_t CODEC_IDLE = 134; + static const uint32_t TX_CTRL_ERROR_POLICY = 144; + static const uint32_t RX_CTRL_CMD = 152; + static const uint32_t RX_CTRL_TIME_HI = 153; + static const uint32_t RX_CTRL_TIME_LO = 154; + static const uint32_t RX_CTRL_HALT = 155; + static const uint32_t RX_CTRL_MAXLEN = 156; + static const uint32_t RX_CTRL_CLEAR_CMDS = 157; + static const uint32_t MISC_OUTS = 160; + static const uint32_t DACSYNC = 161; + static const uint32_t SPI = 168; + static const uint32_t LEDS = 176; + static const uint32_t FP_GPIO = 184; + static const uint32_t GPIO = 192; + // NOTE: Upper 32 registers (224-255) are reserved for the output settings bus for use with + // device specific front end control + + // frontend control: needs rethinking TODO + //static const uint32_t TX_FRONT = BASE + 96; + //static const uint32_t RX_FRONT = BASE + 112; + //static const uint32_t READBACK = BASE + 127; + + static const uint32_t RB_TIME_NOW = 0; + static const uint32_t RB_TIME_PPS = 1; + static const uint32_t RB_TEST = 2; + static const uint32_t RB_CODEC_READBACK = 3; + static const uint32_t RB_RADIO_NUM = 4; + static const uint32_t RB_MISC_IO = 16; + static const uint32_t RB_SPI = 17; + static const uint32_t RB_LEDS = 18; + static const uint32_t RB_DB_GPIO = 19; + static const uint32_t RB_FP_GPIO = 20; + }; + + /*********************************************************************** + * Block control API calls + **********************************************************************/ + void _update_spp(int spp); + + inline size_t _get_num_radios() const { + return std::max(_num_rx_channels, _num_tx_channels); + } + + inline timed_wb_iface::sptr _get_ctrl(size_t radio_num) const { + return _perifs.at(radio_num).ctrl; + } + + inline bool _is_streamer_active(uhd::direction_t dir, const size_t chan) const { + switch (dir) { + case uhd::TX_DIRECTION: + return _tx_streamer_active.at(chan); + case uhd::RX_DIRECTION: + return _rx_streamer_active.at(chan); + case uhd::DX_DIRECTION: + return _rx_streamer_active.at(chan) and _tx_streamer_active.at(chan); + default: + return false; + } + } + + virtual bool check_radio_config() { return true; }; + + //! There is always only one time core per radio + time_core_3000::sptr _time64; + + boost::mutex _mutex; + +private: + /************************************************************************ + * Peripherals + ***********************************************************************/ + //! Stores pointers to all streaming-related radio cores + struct radio_perifs_t + { + timed_wb_iface::sptr ctrl; + }; + std::map<size_t, radio_perifs_t> _perifs; + + size_t _num_tx_channels; + size_t _num_rx_channels; + + // Cached values + double _tick_rate; + std::map<size_t, std::string> _tx_antenna; + std::map<size_t, std::string> _rx_antenna; + std::map<size_t, double> _tx_freq; + std::map<size_t, double> _rx_freq; + std::map<size_t, double> _tx_gain; + std::map<size_t, double> _rx_gain; + + std::vector<bool> _continuous_streaming; +}; /* class radio_ctrl_impl */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_RADIO_CTRL_IMPL_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/rate_node_ctrl.cpp b/host/lib/rfnoc/rate_node_ctrl.cpp new file mode 100644 index 000000000..5e3117e23 --- /dev/null +++ b/host/lib/rfnoc/rate_node_ctrl.cpp @@ -0,0 +1,67 @@ +// +// Copyright 2014-2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/rfnoc/rate_node_ctrl.hpp> +#include <boost/bind.hpp> + +using namespace uhd::rfnoc; + +const double rate_node_ctrl::RATE_UNDEFINED = -1.0; + +static double _get_input_samp_rate(rate_node_ctrl::sptr node, size_t port) +{ + return node->get_input_samp_rate(port); +} + +static double _get_output_samp_rate(rate_node_ctrl::sptr node, size_t port) +{ + return node->get_output_samp_rate(port); +} + + +// FIXME add recursion limiters (i.e. list of explored nodes) +double rate_node_ctrl::get_input_samp_rate( + size_t /* port */ +) { + try { + return find_downstream_unique_property<rate_node_ctrl, double>( + boost::bind(_get_input_samp_rate, _1, _2), + RATE_UNDEFINED + ); + } catch (const uhd::runtime_error &ex) { + throw uhd::runtime_error(str( + boost::format("Multiple sampling rates downstream of %s: %s.") + % unique_id() % ex.what() + )); + } +} + +double rate_node_ctrl::get_output_samp_rate( + size_t /* port */ +) { + try { + return find_upstream_unique_property<rate_node_ctrl, double>( + boost::bind(_get_output_samp_rate, _1, _2), + RATE_UNDEFINED + ); + } catch (const uhd::runtime_error &ex) { + throw uhd::runtime_error(str( + boost::format("Multiple sampling rates upstream of %s: %s.") + % unique_id() % ex.what() + )); + } +} diff --git a/host/lib/rfnoc/rx_stream_terminator.cpp b/host/lib/rfnoc/rx_stream_terminator.cpp new file mode 100644 index 000000000..b2a2d5a64 --- /dev/null +++ b/host/lib/rfnoc/rx_stream_terminator.cpp @@ -0,0 +1,131 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "rx_stream_terminator.hpp" +#include "radio_ctrl_impl.hpp" +#include "../transport/super_recv_packet_handler.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/rfnoc/source_node_ctrl.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> + +using namespace uhd::rfnoc; + +size_t rx_stream_terminator::_count = 0; + +rx_stream_terminator::rx_stream_terminator() : + _term_index(_count), + _samp_rate(rate_node_ctrl::RATE_UNDEFINED), + _tick_rate(tick_node_ctrl::RATE_UNDEFINED) +{ + _count++; +} + +std::string rx_stream_terminator::unique_id() const +{ + return str(boost::format("RX Terminator %d") % _term_index); +} + +void rx_stream_terminator::set_tx_streamer(bool, const size_t) +{ + /* nop */ +} + +void rx_stream_terminator::set_rx_streamer(bool active, const size_t) +{ + // TODO this is identical to source_node_ctrl::set_rx_streamer() -> factor out + UHD_RFNOC_BLOCK_TRACE() << "rx_stream_terminator::set_rx_streamer() " << active << std::endl; + BOOST_FOREACH(const node_ctrl_base::node_map_pair_t upstream_node, _upstream_nodes) { + source_node_ctrl::sptr curr_upstream_block_ctrl = + boost::dynamic_pointer_cast<source_node_ctrl>(upstream_node.second.lock()); + if (curr_upstream_block_ctrl) { + curr_upstream_block_ctrl->set_rx_streamer( + active, + get_upstream_port(upstream_node.first) + ); + } + } +} + +void rx_stream_terminator::handle_overrun(boost::weak_ptr<uhd::rx_streamer> streamer, const size_t) +{ + std::vector<boost::shared_ptr<uhd::rfnoc::radio_ctrl_impl> > upstream_radio_nodes = + find_upstream_node<uhd::rfnoc::radio_ctrl_impl>(); + const size_t n_radios = upstream_radio_nodes.size(); + if (n_radios == 0) { + return; + } + + UHD_RFNOC_BLOCK_TRACE() << "rx_stream_terminator::handle_overrun()" << std::endl; + boost::shared_ptr<uhd::transport::sph::recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<uhd::transport::sph::recv_packet_streamer>(streamer.lock()); + if (not my_streamer) return; //If the rx_streamer has expired then overflow handling makes no sense. + + bool in_continuous_streaming_mode = true; + int num_channels = 0; + BOOST_FOREACH(const boost::shared_ptr<uhd::rfnoc::radio_ctrl_impl> &node, upstream_radio_nodes) { + num_channels += node->get_active_rx_ports().size(); + BOOST_FOREACH(const size_t port, node->get_active_rx_ports()) { + in_continuous_streaming_mode = in_continuous_streaming_mode && node->in_continuous_streaming_mode(port); + } + } + if (num_channels == 0) { + return; + } + + if (num_channels == 1 and in_continuous_streaming_mode) { + std::vector<size_t> active_rx_ports = upstream_radio_nodes[0]->get_active_rx_ports(); + if (active_rx_ports.empty()) { + return; + } + const size_t port = active_rx_ports[0]; + upstream_radio_nodes[0]->issue_stream_cmd(stream_cmd_t::STREAM_MODE_START_CONTINUOUS, port); + return; + } + + ///////////////////////////////////////////////////////////// + // MIMO overflow recovery time + ///////////////////////////////////////////////////////////// + BOOST_FOREACH(const boost::shared_ptr<uhd::rfnoc::radio_ctrl_impl> &node, upstream_radio_nodes) { + BOOST_FOREACH(const size_t port, node->get_active_rx_ports()) { + // check all the ports on all the radios + node->rx_ctrl_clear_cmds(port); + node->issue_stream_cmd(stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS, port); + } + } + //flush transports + my_streamer->flush_all(0.001); // TODO flushing will probably have to go away. + //restart streaming on all channels + if (in_continuous_streaming_mode) { + stream_cmd_t stream_cmd(stream_cmd_t::STREAM_MODE_START_CONTINUOUS); + stream_cmd.stream_now = false; + stream_cmd.time_spec = upstream_radio_nodes[0]->get_time_now() + time_spec_t(0.05); + + BOOST_FOREACH(const boost::shared_ptr<uhd::rfnoc::radio_ctrl_impl> &node, upstream_radio_nodes) { + BOOST_FOREACH(const size_t port, node->get_active_rx_ports()) { + node->issue_stream_cmd(stream_cmd, port); + } + } + } +} + +rx_stream_terminator::~rx_stream_terminator() +{ + UHD_RFNOC_BLOCK_TRACE() << "rx_stream_terminator::~rx_stream_terminator() " << std::endl; + set_rx_streamer(false, 0); +} + diff --git a/host/lib/rfnoc/rx_stream_terminator.hpp b/host/lib/rfnoc/rx_stream_terminator.hpp new file mode 100644 index 000000000..5159cd34a --- /dev/null +++ b/host/lib/rfnoc/rx_stream_terminator.hpp @@ -0,0 +1,86 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_TERMINATOR_RECV_HPP +#define INCLUDED_LIBUHD_RFNOC_TERMINATOR_RECV_HPP + +#include <uhd/rfnoc/sink_node_ctrl.hpp> +#include <uhd/rfnoc/rate_node_ctrl.hpp> +#include <uhd/rfnoc/tick_node_ctrl.hpp> +#include <uhd/rfnoc/scalar_node_ctrl.hpp> +#include <uhd/rfnoc/terminator_node_ctrl.hpp> +#include <uhd/rfnoc/block_ctrl_base.hpp> // For the block macros + +namespace uhd { + namespace rfnoc { + +/*! \brief Terminator node for Rx streamers. + * + * This node is only used by rx_streamers. It terminates the flow graph + * inside the streamer and does not have a counterpart on the FPGA. + */ +class rx_stream_terminator : + public sink_node_ctrl, + public rate_node_ctrl, + public tick_node_ctrl, + public scalar_node_ctrl, + public terminator_node_ctrl +{ +public: + UHD_RFNOC_BLOCK_OBJECT(rx_stream_terminator) + + static sptr make() + { + return sptr(new rx_stream_terminator); + } + + // If this is called, then by a send terminator at the other end + // of a flow graph. + double get_input_samp_rate(size_t) { return _samp_rate; }; + + // Same for the scaling factor + double get_input_scale_factor(size_t) { return scalar_node_ctrl::SCALE_UNDEFINED; }; + + std::string unique_id() const; + + void set_rx_streamer(bool active, const size_t port); + + void set_tx_streamer(bool active, const size_t port); + + virtual ~rx_stream_terminator(); + + void handle_overrun(boost::weak_ptr<uhd::rx_streamer>, const size_t); + +protected: + rx_stream_terminator(); + + virtual double _get_tick_rate() { return _tick_rate; }; + +private: + //! Every terminator has a unique index + const size_t _term_index; + static size_t _count; + + double _samp_rate; + double _tick_rate; + +}; /* class rx_stream_terminator */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_TERMINATOR_RECV_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/scalar_node_ctrl.cpp b/host/lib/rfnoc/scalar_node_ctrl.cpp new file mode 100644 index 000000000..56cc4dcf2 --- /dev/null +++ b/host/lib/rfnoc/scalar_node_ctrl.cpp @@ -0,0 +1,66 @@ +// +// Copyright 2014-2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/rfnoc/scalar_node_ctrl.hpp> +#include <boost/bind.hpp> + +using namespace uhd::rfnoc; + +const double scalar_node_ctrl::SCALE_UNDEFINED = -1.0; + +static double _get_input_factor(scalar_node_ctrl::sptr node, size_t port) +{ + return node->get_input_scale_factor(port); +} + +static double _get_output_factor(scalar_node_ctrl::sptr node, size_t port) +{ + return node->get_output_scale_factor(port); +} + +// FIXME add recursion limiters (i.e. list of explored nodes) +double scalar_node_ctrl::get_input_scale_factor( + size_t /* port */ +) { + try { + return find_downstream_unique_property<scalar_node_ctrl, double>( + boost::bind(_get_input_factor, _1, _2), + SCALE_UNDEFINED + ); + } catch (const uhd::runtime_error &ex) { + throw uhd::runtime_error(str( + boost::format("Multiple scaling factors rates downstream of %s: %s.") + % unique_id() % ex.what() + )); + } +} + +double scalar_node_ctrl::get_output_scale_factor( + size_t /* port */ +) { + try { + return find_upstream_unique_property<scalar_node_ctrl, double>( + boost::bind(_get_output_factor, _1, _2), + SCALE_UNDEFINED + ); + } catch (const uhd::runtime_error &ex) { + throw uhd::runtime_error(str( + boost::format("Multiple scaling factors rates upstream of %s: %s.") + % unique_id() % ex.what() + )); + } +} diff --git a/host/lib/rfnoc/sink_block_ctrl_base.cpp b/host/lib/rfnoc/sink_block_ctrl_base.cpp new file mode 100644 index 000000000..56755a269 --- /dev/null +++ b/host/lib/rfnoc/sink_block_ctrl_base.cpp @@ -0,0 +1,111 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "utils.hpp" +#include <uhd/rfnoc/sink_block_ctrl_base.hpp> +#include <uhd/rfnoc/constants.hpp> +#include <uhd/utils/msg.hpp> + +using namespace uhd; +using namespace uhd::rfnoc; + + +/*********************************************************************** + * Stream signatures + **********************************************************************/ +stream_sig_t sink_block_ctrl_base::get_input_signature(size_t block_port) const +{ + if (not _tree->exists(_root_path / "ports" / "in" / block_port)) { + throw uhd::runtime_error(str( + boost::format("Invalid port number %d for block %s") + % block_port % unique_id() + )); + } + + return _resolve_port_def( + _tree->access<blockdef::port_t>(_root_path / "ports" / "in" / block_port).get() + ); +} + +std::vector<size_t> sink_block_ctrl_base::get_input_ports() const +{ + std::vector<size_t> input_ports; + input_ports.reserve(_tree->list(_root_path / "ports" / "in").size()); + BOOST_FOREACH(const std::string port, _tree->list(_root_path / "ports" / "in")) { + input_ports.push_back(boost::lexical_cast<size_t>(port)); + } + return input_ports; +} + +/*********************************************************************** + * FPGA Configuration + **********************************************************************/ +size_t sink_block_ctrl_base::get_fifo_size(size_t block_port) const { + if (_tree->exists(_root_path / "input_buffer_size" / str(boost::format("%d") % block_port))) { + return _tree->access<size_t>(_root_path / "input_buffer_size" / str(boost::format("%d") % block_port)).get(); + } + return 0; +} + +void sink_block_ctrl_base::configure_flow_control_in( + size_t cycles, + size_t packets, + size_t block_port +) { + UHD_RFNOC_BLOCK_TRACE() << boost::format("sink_block_ctrl_base::configure_flow_control_in(cycles=%d, packets=%d)") % cycles % packets << std::endl; + boost::uint32_t cycles_word = 0; + if (cycles) { + cycles_word = (1<<31) | cycles; + } + sr_write(SR_FLOW_CTRL_CYCS_PER_ACK, cycles_word, block_port); + + boost::uint32_t packets_word = 0; + if (packets) { + packets_word = (1<<31) | packets; + } + sr_write(SR_FLOW_CTRL_PKTS_PER_ACK, packets_word, block_port); +} + +void sink_block_ctrl_base::set_error_policy( + const std::string &policy +) { + if (policy == "next_packet") + { + sr_write(SR_ERROR_POLICY, (1 << 1) | 1); + } + else if (policy == "next_burst") + { + sr_write(SR_ERROR_POLICY, (1 << 2) | 1); + } + else if (policy == "wait") + { + sr_write(SR_ERROR_POLICY, 1); + } + else throw uhd::value_error("Block input cannot handle requested error policy: " + policy); +} + +/*********************************************************************** + * Hooks + **********************************************************************/ +size_t sink_block_ctrl_base::_request_input_port( + const size_t suggested_port, + const uhd::device_addr_t & +) const { + const std::set<size_t> valid_input_ports = utils::str_list_to_set<size_t>(_tree->list(_root_path / "ports" / "in")); + return utils::node_map_find_first_free(_upstream_nodes, suggested_port, valid_input_ports); +} +// vim: sw=4 et: diff --git a/host/lib/rfnoc/sink_node_ctrl.cpp b/host/lib/rfnoc/sink_node_ctrl.cpp new file mode 100644 index 000000000..8398641fd --- /dev/null +++ b/host/lib/rfnoc/sink_node_ctrl.cpp @@ -0,0 +1,92 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "utils.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/rfnoc/sink_node_ctrl.hpp> +#include <uhd/rfnoc/source_node_ctrl.hpp> + +using namespace uhd::rfnoc; + +size_t sink_node_ctrl::connect_upstream( + node_ctrl_base::sptr upstream_node, + size_t port, + const uhd::device_addr_t &args +) { + boost::mutex::scoped_lock lock(_input_mutex); + port = _request_input_port(port, args); + _register_upstream_node(upstream_node, port); + return port; +} + +void sink_node_ctrl::set_tx_streamer(bool active, const size_t port) +{ + UHD_RFNOC_BLOCK_TRACE() << "sink_node_ctrl::set_tx_streamer() " << active << " " << port << std::endl; + + /* Enable all downstream connections: + BOOST_FOREACH(const node_ctrl_base::node_map_pair_t downstream_node, list_downstream_nodes()) { + sptr curr_downstream_block_ctrl = + boost::dynamic_pointer_cast<sink_node_ctrl>(downstream_node.second.lock()); + if (curr_downstream_block_ctrl) { + curr_downstream_block_ctrl->set_tx_streamer( + active, + get_downstream_port(downstream_node.first) + ); + } + } + */ + + // Only enable 1:1 + if (list_downstream_nodes().count(port)) { + sink_node_ctrl::sptr this_downstream_block_ctrl = + boost::dynamic_pointer_cast<sink_node_ctrl>(list_downstream_nodes().at(port).lock()); + if (this_downstream_block_ctrl) { + this_downstream_block_ctrl->set_tx_streamer( + active, + get_downstream_port(port) + ); + } + } + + _tx_streamer_active[port] = active; +} + +size_t sink_node_ctrl::_request_input_port( + const size_t suggested_port, + const uhd::device_addr_t & +) const { + return utils::node_map_find_first_free(_upstream_nodes, suggested_port); +} + +void sink_node_ctrl::_register_upstream_node( + node_ctrl_base::sptr upstream_node, + size_t port +) { + // Do all the checks: + if (port == ANY_PORT) { + throw uhd::type_error("Invalid input port number."); + } + if (_upstream_nodes.count(port) and not _upstream_nodes[port].expired()) { + throw uhd::runtime_error(str(boost::format("On node %s, input port %d is already connected.") % unique_id() % port)); + } + if (not boost::dynamic_pointer_cast<source_node_ctrl>(upstream_node)) { + throw uhd::type_error("Attempting to register a non-source block as upstream."); + } + // Alles klar, Herr Kommissar :) + + _upstream_nodes[port] = boost::weak_ptr<node_ctrl_base>(upstream_node); +} diff --git a/host/lib/rfnoc/source_block_ctrl_base.cpp b/host/lib/rfnoc/source_block_ctrl_base.cpp new file mode 100644 index 000000000..72845ad64 --- /dev/null +++ b/host/lib/rfnoc/source_block_ctrl_base.cpp @@ -0,0 +1,137 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "utils.hpp" +#include <uhd/rfnoc/source_block_ctrl_base.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/rfnoc/constants.hpp> + +using namespace uhd; +using namespace uhd::rfnoc; + +/*********************************************************************** + * Streaming operations + **********************************************************************/ +void source_block_ctrl_base::issue_stream_cmd( + const uhd::stream_cmd_t &stream_cmd, + const size_t chan +) { + UHD_RFNOC_BLOCK_TRACE() << "source_block_ctrl_base::issue_stream_cmd()" << std::endl; + if (_upstream_nodes.empty()) { + UHD_MSG(warning) << "issue_stream_cmd() not implemented for " << get_block_id() << std::endl; + return; + } + + BOOST_FOREACH(const node_ctrl_base::node_map_pair_t upstream_node, _upstream_nodes) { + source_node_ctrl::sptr this_upstream_block_ctrl = + boost::dynamic_pointer_cast<source_node_ctrl>(upstream_node.second.lock()); + this_upstream_block_ctrl->issue_stream_cmd(stream_cmd, chan); + } +} + +/*********************************************************************** + * Stream signatures + **********************************************************************/ +stream_sig_t source_block_ctrl_base::get_output_signature(size_t block_port) const +{ + if (not _tree->exists(_root_path / "ports" / "out" / block_port)) { + throw uhd::runtime_error(str( + boost::format("Invalid port number %d for block %s") + % block_port % unique_id() + )); + } + + return _resolve_port_def( + _tree->access<blockdef::port_t>(_root_path / "ports" / "out" / block_port).get() + ); +} + +std::vector<size_t> source_block_ctrl_base::get_output_ports() const +{ + std::vector<size_t> output_ports; + output_ports.reserve(_tree->list(_root_path / "ports" / "out").size()); + BOOST_FOREACH(const std::string port, _tree->list(_root_path / "ports" / "out")) { + output_ports.push_back(boost::lexical_cast<size_t>(port)); + } + return output_ports; +} + +/*********************************************************************** + * FPGA Configuration + **********************************************************************/ +void source_block_ctrl_base::set_destination( + boost::uint32_t next_address, + size_t output_block_port +) { + UHD_RFNOC_BLOCK_TRACE() << "source_block_ctrl_base::set_destination() " << uhd::sid_t(next_address) << std::endl; + sid_t new_sid(next_address); + new_sid.set_src(get_address(output_block_port)); + UHD_RFNOC_BLOCK_TRACE() << " Setting SID: " << new_sid << std::endl << " "; + sr_write(SR_NEXT_DST_SID, (1<<16) | next_address, output_block_port); +} + +void source_block_ctrl_base::configure_flow_control_out( + size_t buf_size_pkts, + size_t block_port, + UHD_UNUSED(const uhd::sid_t &sid) +) { + UHD_RFNOC_BLOCK_TRACE() << "source_block_ctrl_base::configure_flow_control_out() buf_size_pkts==" << buf_size_pkts << std::endl; + if (buf_size_pkts < 2) { + throw uhd::runtime_error(str( + boost::format("Invalid window size %d for block %s. Window size must at least be 2.") + % buf_size_pkts % unique_id() + )); + } + + //Disable the window and let all upstream data flush out + //We need to do this every time the window is changed because + //a) We don't know what state the flow-control module was left in + // in the previous run (it should still be enabled) + //b) Changing the window size where data is buffered upstream may + // result in stale packets entering the stream. + sr_write(SR_FLOW_CTRL_WINDOW_EN, 0, block_port); + + //Wait for data to flush out. + //In the FPGA we are guaranteed that all buffered packets are more-or-less consecutive. + //1ms@200MHz = 200,000 cycles of "flush time". + //200k cycles = 200k * 8 bytes (64 bits) = 1.6MB of data that can be flushed. + //Typically in the FPGA we have buffering in the order of kilobytes so waiting for 1MB + //to flush is more than enough time. + //TODO: Enhancement. We should get feedback from the FPGA about when the source_flow_control + // module is done flushing. + boost::this_thread::sleep(boost::posix_time::milliseconds(1)); + + //Resize the FC window. + //Precondition: No data can be buffered upstream. + sr_write(SR_FLOW_CTRL_WINDOW_SIZE, buf_size_pkts, block_port); + + //Enable the FC window. + //Precondition: The window size must be set. + sr_write(SR_FLOW_CTRL_WINDOW_EN, (buf_size_pkts != 0), block_port); +} + +/*********************************************************************** + * Hooks + **********************************************************************/ +size_t source_block_ctrl_base::_request_output_port( + const size_t suggested_port, + const uhd::device_addr_t & +) const { + const std::set<size_t> valid_output_ports = utils::str_list_to_set<size_t>(_tree->list(_root_path / "ports" / "out")); + return utils::node_map_find_first_free(_downstream_nodes, suggested_port, valid_output_ports); +} +// vim: sw=4 et: diff --git a/host/lib/rfnoc/source_node_ctrl.cpp b/host/lib/rfnoc/source_node_ctrl.cpp new file mode 100644 index 000000000..c97c72354 --- /dev/null +++ b/host/lib/rfnoc/source_node_ctrl.cpp @@ -0,0 +1,96 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "utils.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/rfnoc/source_node_ctrl.hpp> +#include <uhd/rfnoc/sink_node_ctrl.hpp> + +using namespace uhd::rfnoc; + +size_t source_node_ctrl::connect_downstream( + node_ctrl_base::sptr downstream_node, + size_t port, + const uhd::device_addr_t &args +) { + boost::mutex::scoped_lock lock(_output_mutex); + port = _request_output_port(port, args); + _register_downstream_node(downstream_node, port); + return port; +} + +void source_node_ctrl::set_rx_streamer(bool active, const size_t port) +{ + UHD_RFNOC_BLOCK_TRACE() << "source_node_ctrl::set_rx_streamer() " << port << " -> " << active << std::endl; + + /* This will enable all upstream blocks: + BOOST_FOREACH(const node_ctrl_base::node_map_pair_t upstream_node, list_upstream_nodes()) { + sptr curr_upstream_block_ctrl = + boost::dynamic_pointer_cast<source_node_ctrl>(upstream_node.second.lock()); + if (curr_upstream_block_ctrl) { + curr_upstream_block_ctrl->set_rx_streamer( + active, + get_upstream_port(upstream_node.first) + ); + } + } + */ + + // This only enables 1:1 (if output 1 is enabled, enable what's connected to input 1) + if (list_upstream_nodes().count(port)) { + source_node_ctrl::sptr this_upstream_block_ctrl = + boost::dynamic_pointer_cast<source_node_ctrl>(list_upstream_nodes().at(port).lock()); + if (this_upstream_block_ctrl) { + this_upstream_block_ctrl->set_rx_streamer( + active, + get_upstream_port(port) + ); + } + } + + _rx_streamer_active[port] = active; +} + +size_t source_node_ctrl::_request_output_port( + const size_t suggested_port, + const uhd::device_addr_t & +) const { + return utils::node_map_find_first_free(_downstream_nodes, suggested_port); +} + +void source_node_ctrl::_register_downstream_node( + node_ctrl_base::sptr downstream_node, + size_t port +) { + // Do all the checks: + if (port == ANY_PORT) { + throw uhd::type_error(str( + boost::format("[%s] Invalid output port number (ANY).") + % unique_id() + )); + } + if (_downstream_nodes.count(port) and not _downstream_nodes[port].expired()) { + throw uhd::runtime_error(str(boost::format("On node %s, output port %d is already connected.") % unique_id() % port)); + } + if (not boost::dynamic_pointer_cast<sink_node_ctrl>(downstream_node)) { + throw uhd::type_error("Attempting to register a non-sink block as downstream."); + } + // Alles klar, Herr Kommissar :) + + _downstream_nodes[port] = boost::weak_ptr<node_ctrl_base>(downstream_node); +} + diff --git a/host/lib/rfnoc/stream_sig.cpp b/host/lib/rfnoc/stream_sig.cpp new file mode 100644 index 000000000..3d953bcb2 --- /dev/null +++ b/host/lib/rfnoc/stream_sig.cpp @@ -0,0 +1,85 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/rfnoc/stream_sig.hpp> +#include <uhd/convert.hpp> +#include <boost/format.hpp> + +using namespace uhd::rfnoc; + +stream_sig_t::stream_sig_t() : + item_type(""), + vlen(0), + packet_size(0), + is_bursty(false) +{ + // nop +} + +std::string stream_sig_t::to_string() +{ + return str( + boost::format( + "%s,vlen=%d,packet_size=%d" + ) % item_type % vlen % packet_size + ); +} + +std::string stream_sig_t::to_pp_string() +{ + return str( + boost::format( + "Data type: %s | Vector Length: %d | Packet size: %d" + ) % item_type % vlen % packet_size + ); +} + +size_t stream_sig_t::get_bytes_per_item() const +{ + if (item_type == "") { + return 0; + } + + return uhd::convert::get_bytes_per_item(item_type); +} + +bool stream_sig_t::is_compatible(const stream_sig_t &output_sig, const stream_sig_t &input_sig) +{ + /// Item types: + if (not (input_sig.item_type.empty() or output_sig.item_type.empty()) + and input_sig.item_type != output_sig.item_type) { + return false; + } + + /// Vector lengths + if (output_sig.vlen and input_sig.vlen) { + if (input_sig.vlen != output_sig.vlen) { + return false; + } + } + + /// Packet sizes + if (output_sig.packet_size and input_sig.packet_size) { + if (input_sig.packet_size != output_sig.packet_size) { + return false; + } + } + + // You may pass + return true; +} +// vim: sw=4 et: diff --git a/host/lib/rfnoc/tick_node_ctrl.cpp b/host/lib/rfnoc/tick_node_ctrl.cpp new file mode 100644 index 000000000..fa5c7b6a1 --- /dev/null +++ b/host/lib/rfnoc/tick_node_ctrl.cpp @@ -0,0 +1,75 @@ +// +// Copyright 2014-2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/rfnoc/tick_node_ctrl.hpp> + +using namespace uhd::rfnoc; + +const double tick_node_ctrl::RATE_UNDEFINED = 0; + +double tick_node_ctrl::get_tick_rate( + const std::set< node_ctrl_base::sptr > &_explored_nodes +) { + // First, see if we've implemented _get_tick_rate() + { + double my_tick_rate = _get_tick_rate(); + if (my_tick_rate != RATE_UNDEFINED) { + return my_tick_rate; + } + } + + // If not, we ask all our neighbours for the tick rate. + // This will fail if we get different values. + std::set< node_ctrl_base::sptr > explored_nodes(_explored_nodes); + explored_nodes.insert(shared_from_this()); + // Here, we need all up- and downstream nodes + std::vector< sptr > neighbouring_tick_nodes = find_downstream_node<tick_node_ctrl>(); + { + std::vector< sptr > upstream_neighbouring_tick_nodes = find_upstream_node<tick_node_ctrl>(); + neighbouring_tick_nodes.insert( + neighbouring_tick_nodes.end(), + upstream_neighbouring_tick_nodes.begin(), + upstream_neighbouring_tick_nodes.end() + ); + } // neighbouring_tick_nodes is now initialized + double ret_val = RATE_UNDEFINED; + BOOST_FOREACH(const sptr &node, neighbouring_tick_nodes) { + if (_explored_nodes.count(node)) { + continue; + } + double tick_rate = node->get_tick_rate(explored_nodes); + if (tick_rate == RATE_UNDEFINED) { + continue; + } + if (ret_val == RATE_UNDEFINED) { + ret_val = tick_rate; + // TODO: Remember name of this node so we can make the throw message more descriptive. + continue; + } + if (tick_rate != ret_val) { + throw uhd::runtime_error( + str( + // TODO add node names + boost::format("Conflicting tick rates: One neighbouring block specifies %d MHz, another %d MHz.") + % tick_rate % ret_val + ) + ); + } + } + return ret_val; +} + diff --git a/host/lib/rfnoc/tx_stream_terminator.cpp b/host/lib/rfnoc/tx_stream_terminator.cpp new file mode 100644 index 000000000..2746fc4d8 --- /dev/null +++ b/host/lib/rfnoc/tx_stream_terminator.cpp @@ -0,0 +1,65 @@ +// +// Copyright 2014-2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "tx_stream_terminator.hpp" +#include <boost/format.hpp> +#include <uhd/rfnoc/sink_node_ctrl.hpp> + +using namespace uhd::rfnoc; + +size_t tx_stream_terminator::_count = 0; + +tx_stream_terminator::tx_stream_terminator() : + _term_index(_count), + _samp_rate(rate_node_ctrl::RATE_UNDEFINED), + _tick_rate(tick_node_ctrl::RATE_UNDEFINED) +{ + _count++; +} + +std::string tx_stream_terminator::unique_id() const +{ + return str(boost::format("TX Terminator %d") % _term_index); +} + +void tx_stream_terminator::set_rx_streamer(bool, const size_t) +{ + /* nop */ +} + +void tx_stream_terminator::set_tx_streamer(bool active, const size_t /* port */) +{ + // TODO this is identical to sink_node_ctrl::set_tx_streamer() -> factor out + UHD_RFNOC_BLOCK_TRACE() << "tx_stream_terminator::set_tx_streamer() " << active << std::endl; + BOOST_FOREACH(const node_ctrl_base::node_map_pair_t downstream_node, _downstream_nodes) { + sink_node_ctrl::sptr curr_downstream_block_ctrl = + boost::dynamic_pointer_cast<sink_node_ctrl>(downstream_node.second.lock()); + if (curr_downstream_block_ctrl) { + curr_downstream_block_ctrl->set_tx_streamer( + active, + get_downstream_port(downstream_node.first) + ); + } + } +} + +tx_stream_terminator::~tx_stream_terminator() +{ + UHD_RFNOC_BLOCK_TRACE() << "tx_stream_terminator::~tx_stream_terminator() " << std::endl; + set_tx_streamer(false, 0); +} + diff --git a/host/lib/rfnoc/tx_stream_terminator.hpp b/host/lib/rfnoc/tx_stream_terminator.hpp new file mode 100644 index 000000000..169d7cd6a --- /dev/null +++ b/host/lib/rfnoc/tx_stream_terminator.hpp @@ -0,0 +1,90 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_TERMINATOR_SEND_HPP +#define INCLUDED_LIBUHD_RFNOC_TERMINATOR_SEND_HPP + +#include <uhd/rfnoc/source_node_ctrl.hpp> +#include <uhd/rfnoc/rate_node_ctrl.hpp> +#include <uhd/rfnoc/tick_node_ctrl.hpp> +#include <uhd/rfnoc/scalar_node_ctrl.hpp> +#include <uhd/rfnoc/terminator_node_ctrl.hpp> +#include <uhd/rfnoc/block_ctrl_base.hpp> // For the block macros +#include <uhd/utils/msg.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Terminator node for Tx streamers. + * + * This node is only used by tx_streamers. It terminates the flow graph + * inside the streamer and does not have a counterpart on the FPGA. + */ +class tx_stream_terminator : + public source_node_ctrl, + public rate_node_ctrl, + public tick_node_ctrl, + public scalar_node_ctrl, + public terminator_node_ctrl +{ +public: + UHD_RFNOC_BLOCK_OBJECT(tx_stream_terminator) + + static sptr make() + { + return sptr(new tx_stream_terminator); + } + + void issue_stream_cmd(const uhd::stream_cmd_t &, const size_t) + { + UHD_RFNOC_BLOCK_TRACE() << "tx_stream_terminator::issue_stream_cmd()" << std::endl; + } + + // If this is called, then by a send terminator at the other end + // of a flow graph. + double get_output_samp_rate(size_t) { return _samp_rate; }; + + // Same for the scaling factor + double get_output_scale_factor(size_t) { return scalar_node_ctrl::SCALE_UNDEFINED; }; + + std::string unique_id() const; + + void set_rx_streamer(bool active, const size_t port); + + void set_tx_streamer(bool active, const size_t port); + + virtual ~tx_stream_terminator(); + +protected: + tx_stream_terminator(); + + virtual double _get_tick_rate() { return _tick_rate; }; + +private: + //! Every terminator has a unique index + const size_t _term_index; + static size_t _count; + + double _samp_rate; + double _tick_rate; + +}; /* class tx_stream_terminator */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_TERMINATOR_SEND_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/utils.hpp b/host/lib/rfnoc/utils.hpp new file mode 100644 index 000000000..ecd3d7cfb --- /dev/null +++ b/host/lib/rfnoc/utils.hpp @@ -0,0 +1,77 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_UTILS_HPP +#define INCLUDED_LIBUHD_RFNOC_UTILS_HPP + +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <boost/lexical_cast.hpp> +#include <set> + +namespace uhd { namespace rfnoc { namespace utils { + + /*! If \p suggested_port equals ANY_PORT, return the first available + * port number on \p nodes. Otherwise, return \p suggested_port. + * + * If \p allowed_ports is given, another condition is that the port + * number must be listed in here. + * If \p allowed_ports is not specified or empty, the assumption is + * that all ports are valid. + * + * On failure, ANY_PORT is returned. + */ + static size_t node_map_find_first_free( + node_ctrl_base::node_map_t nodes, + const size_t suggested_port, + const std::set<size_t> allowed_ports=std::set<size_t>() + ) { + size_t port = suggested_port; + if (port == ANY_PORT) { + if (allowed_ports.empty()) { + port = 0; + while (nodes.count(port) and (port != ANY_PORT)) { + port++; + } + } else { + BOOST_FOREACH(const size_t allowed_port, allowed_ports) { + if (not nodes.count(port)) { + return allowed_port; + } + return ANY_PORT; + } + } + } else { + if (not (allowed_ports.empty() or allowed_ports.count(port))) { + return ANY_PORT; + } + } + return port; + } + + template <typename T> + static std::set<T> str_list_to_set(const std::vector<std::string> &list) { + std::set<T> return_set; + BOOST_FOREACH(const std::string &S, list) { + return_set.insert(boost::lexical_cast<T>(S)); + } + return return_set; + } + +}}}; /* namespace uhd::rfnoc::utils */ + +#endif /* INCLUDED_LIBUHD_RFNOC_UTILS_HPP */ +// vim: sw=4 et: diff --git a/host/lib/rfnoc/wb_iface_adapter.cpp b/host/lib/rfnoc/wb_iface_adapter.cpp new file mode 100644 index 000000000..6688fe86b --- /dev/null +++ b/host/lib/rfnoc/wb_iface_adapter.cpp @@ -0,0 +1,71 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "wb_iface_adapter.hpp" + +using namespace uhd::rfnoc; + +wb_iface_adapter::wb_iface_adapter( + const poke32_type &poke32_functor_, + const peek32_type &peek32_functor_, + const peek64_type &peek64_functor_, + const gettime_type &gettime_functor_, + const settime_type &settime_functor_ +) : poke32_functor(poke32_functor_) + , peek32_functor(peek32_functor_) + , peek64_functor(peek64_functor_) + , gettime_functor(gettime_functor_) + , settime_functor(settime_functor_) +{ + // nop +} + +wb_iface_adapter::wb_iface_adapter( + const poke32_type &poke32_functor_, + const peek32_type &peek32_functor_, + const peek64_type &peek64_functor_ +) : poke32_functor(poke32_functor_) + , peek32_functor(peek32_functor_) + , peek64_functor(peek64_functor_) +{ + // nop +} + +void wb_iface_adapter::poke32(const wb_addr_type addr, const boost::uint32_t data) +{ + poke32_functor(addr / 4, data); // FIXME remove the requirement for /4 +} + +boost::uint32_t wb_iface_adapter::peek32(const wb_addr_type addr) +{ + return peek32_functor(addr); +} + +boost::uint64_t wb_iface_adapter::peek64(const wb_addr_type addr) +{ + return peek64_functor(addr); +} + +uhd::time_spec_t wb_iface_adapter::get_time(void) +{ + return gettime_functor(); +} + +void wb_iface_adapter::set_time(const uhd::time_spec_t& t) +{ + settime_functor(t); +} diff --git a/host/lib/rfnoc/wb_iface_adapter.hpp b/host/lib/rfnoc/wb_iface_adapter.hpp new file mode 100644 index 000000000..04623d203 --- /dev/null +++ b/host/lib/rfnoc/wb_iface_adapter.hpp @@ -0,0 +1,70 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_RFNOC_WB_IFACE_ADAPTER_HPP +#define INCLUDED_RFNOC_WB_IFACE_ADAPTER_HPP + +#include <uhd/config.hpp> +#include <uhd/types/wb_iface.hpp> +#include <boost/function.hpp> + +namespace uhd { + namespace rfnoc { + +class UHD_API wb_iface_adapter : public uhd::timed_wb_iface +{ +public: + typedef boost::shared_ptr<wb_iface_adapter> sptr; + typedef boost::function<void(wb_addr_type, boost::uint32_t)> poke32_type; + typedef boost::function<boost::uint32_t(wb_addr_type)> peek32_type; + typedef boost::function<boost::uint64_t(wb_addr_type)> peek64_type; + typedef boost::function<time_spec_t(void)> gettime_type; + typedef boost::function<void(const time_spec_t&)> settime_type; + + wb_iface_adapter( + const poke32_type &, + const peek32_type &, + const peek64_type &, + const gettime_type &, + const settime_type & + ); + + wb_iface_adapter( + const poke32_type &, + const peek32_type &, + const peek64_type & + ); + + virtual ~wb_iface_adapter(void) {}; + + virtual void poke32(const wb_addr_type addr, const boost::uint32_t data); + virtual boost::uint32_t peek32(const wb_addr_type addr); + virtual boost::uint64_t peek64(const wb_addr_type addr); + virtual time_spec_t get_time(void); + virtual void set_time(const time_spec_t& t); + +private: + const poke32_type poke32_functor; + const peek32_type peek32_functor; + const peek64_type peek64_functor; + const gettime_type gettime_functor; + const settime_type settime_functor; +}; + +}} // namespace uhd::rfnoc + +#endif /* INCLUDED_RFNOC_WB_IFACE_ADAPTER_HPP */ diff --git a/host/lib/rfnoc/xports.hpp b/host/lib/rfnoc/xports.hpp new file mode 100644 index 000000000..7872f2e1b --- /dev/null +++ b/host/lib/rfnoc/xports.hpp @@ -0,0 +1,36 @@ +// +// Copyright 2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/types/sid.hpp> +#include <uhd/transport/zero_copy.hpp> + +namespace uhd { + + /*! Holds all necessary items for a bidirectional link + */ + struct both_xports_t + { + uhd::transport::zero_copy_if::sptr recv; + uhd::transport::zero_copy_if::sptr send; + size_t recv_buff_size; + size_t send_buff_size; + uhd::sid_t send_sid; + uhd::sid_t recv_sid; + }; + +}; + diff --git a/host/lib/transport/super_recv_packet_handler.hpp b/host/lib/transport/super_recv_packet_handler.hpp index 541d9f3bc..5ca1da687 100644 --- a/host/lib/transport/super_recv_packet_handler.hpp +++ b/host/lib/transport/super_recv_packet_handler.hpp @@ -28,6 +28,9 @@ #include <uhd/types/metadata.hpp> #include <uhd/transport/vrt_if_packet.hpp> #include <uhd/transport/zero_copy.hpp> +#ifdef DEVICE3_STREAMER +# include "../rfnoc/rx_stream_terminator.hpp" +#endif #include <boost/dynamic_bitset.hpp> #include <boost/foreach.hpp> #include <boost/function.hpp> @@ -112,6 +115,35 @@ public: _header_offset_words32 = header_offset_words32; } + ////////////////// RFNOC /////////////////////////// + //! Set the stream ID for a specific channel (or no SID) + void set_xport_chan_sid(const size_t xport_chan, const bool has_sid, const boost::uint32_t sid = 0){ + _props.at(xport_chan).has_sid = has_sid; + _props.at(xport_chan).sid = sid; + } + + //! Get the stream ID for a specific channel (or zero if no SID) + boost::uint32_t get_xport_chan_sid(const size_t xport_chan) const { + if (_props.at(xport_chan).has_sid) { + return _props.at(xport_chan).sid; + } else { + return 0; + } + } + + #ifdef DEVICE3_STREAMER + void set_terminator(uhd::rfnoc::rx_stream_terminator::sptr terminator) + { + _terminator = terminator; + } + + uhd::rfnoc::rx_stream_terminator::sptr get_terminator() + { + return _terminator; + } + #endif + ////////////////// RFNOC /////////////////////////// + /*! * Set the threshold for alignment failure. * How many packets throw out before giving up? @@ -194,11 +226,12 @@ public: //! Overload call to issue stream commands void issue_stream_cmd(const stream_cmd_t &stream_cmd) { - if (stream_cmd.stream_now - and stream_cmd.stream_mode != stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS - and _props.size() > 1) { - throw uhd::runtime_error("Attempting to do multi-channel receive with stream_now == true will result in misaligned channels. Aborting."); - } + // RFNoC: This needs to be checked by the radio block, once it's done. TODO remove this. + //if (stream_cmd.stream_now + //and stream_cmd.stream_mode != stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS + //and _props.size() > 1) { + //throw uhd::runtime_error("Attempting to do multi-channel receive with stream_now == true will result in misaligned channels. Aborting."); + //} for (size_t i = 0; i < _props.size(); i++) { @@ -288,6 +321,10 @@ private: handle_overflow_type handle_overflow; handle_flowctrl_type handle_flowctrl; size_t fc_update_window; + /////// RFNOC /////////// + bool has_sid; + boost::uint32_t sid; + /////// RFNOC /////////// }; std::vector<xport_chan_props_type> _props; size_t _num_outputs; @@ -360,6 +397,10 @@ private: int recvd_packets; #endif + #ifdef DEVICE3_STREAMER + uhd::rfnoc::rx_stream_terminator::sptr _terminator; + #endif + /******************************************************************* * Get and process a single packet from the transport: * Receive a single packet at the given index. @@ -426,6 +467,7 @@ private: const size_t expected_packet_count = _props[index].packet_count; _props[index].packet_count = (info.ifpi.packet_count + 1) & seq_mask; if (expected_packet_count != info.ifpi.packet_count){ + //UHD_MSG(status) << "expected: " << expected_packet_count << " got: " << info.ifpi.packet_count << std::endl; if (_props[index].handle_flowctrl) { // Always update flow control in this case, because we don't // know which packet was dropped and what state the upstream @@ -447,6 +489,10 @@ private: void _flush_all(double timeout) { + get_prev_buffer_info().reset(); + get_curr_buffer_info().reset(); + get_next_buffer_info().reset(); + for (size_t i = 0; i < _props.size(); i++) { per_buffer_info_type prev_buffer_info, curr_buffer_info; @@ -467,9 +513,6 @@ private: curr_buffer_info.reset(); } } - get_prev_buffer_info().reset(); - get_curr_buffer_info().reset(); - get_next_buffer_info().reset(); } /******************************************************************* @@ -567,16 +610,20 @@ private: curr_info.metadata.time_spec = next_info[index].time; curr_info.metadata.error_code = rx_metadata_t::error_code_t(get_context_code(next_info[index].vrt_hdr, next_info[index].ifpi)); if (curr_info.metadata.error_code == rx_metadata_t::ERROR_CODE_OVERFLOW){ + // Not sending flow control would cause timeouts due to source flow control locking up. + // Send first as the overrun handler may flush the receive buffers which could contain + // packets with sequence numbers after this packet's sequence number! + if(_props[index].handle_flowctrl) { + _props[index].handle_flowctrl(next_info[index].ifpi.packet_count); + } + rx_metadata_t metadata = curr_info.metadata; _props[index].handle_overflow(); curr_info.metadata = metadata; UHD_MSG(fastpath) << "O"; - - // Not sending flow control would cause timeouts due to source flow control locking up - if(_props[index].handle_flowctrl) { - _props[index].handle_flowctrl(next_info[index].ifpi.packet_count); - } } + curr_info[index].buff.reset(); + curr_info[index].copy_buff = NULL; return; case PACKET_TIMEOUT_ERROR: diff --git a/host/lib/transport/super_send_packet_handler.hpp b/host/lib/transport/super_send_packet_handler.hpp index 7f43168a0..5e81fb442 100644 --- a/host/lib/transport/super_send_packet_handler.hpp +++ b/host/lib/transport/super_send_packet_handler.hpp @@ -28,6 +28,9 @@ #include <uhd/types/metadata.hpp> #include <uhd/transport/vrt_if_packet.hpp> #include <uhd/transport/zero_copy.hpp> +#ifdef DEVICE3_STREAMER +# include "../rfnoc/tx_stream_terminator.hpp" +#endif #include <boost/thread/thread.hpp> #include <boost/thread/thread_time.hpp> #include <boost/foreach.hpp> @@ -102,6 +105,29 @@ public: _props.at(xport_chan).sid = sid; } + ///////// RFNOC /////////////////// + //! Get the stream ID for a specific channel (or zero if no SID) + boost::uint32_t get_xport_chan_sid(const size_t xport_chan) const { + if (_props.at(xport_chan).has_sid) { + return _props.at(xport_chan).sid; + } else { + return 0; + } + } + + #ifdef DEVICE3_STREAMER + void set_terminator(uhd::rfnoc::tx_stream_terminator::sptr terminator) + { + _terminator = terminator; + } + + uhd::rfnoc::tx_stream_terminator::sptr get_terminator() + { + return _terminator; + } + #endif + ///////// RFNOC /////////////////// + void set_enable_trailer(const bool enable) { _has_tlr = enable; @@ -296,6 +322,10 @@ private: bool _cached_metadata; uhd::tx_metadata_t _metadata_cache; + #ifdef DEVICE3_STREAMER + uhd::rfnoc::tx_stream_terminator::sptr _terminator; + #endif + #ifdef UHD_TXRX_DEBUG_PRINTS struct dbg_send_stat_t { dbg_send_stat_t(long wc, size_t nspb, size_t nss, uhd::tx_metadata_t md, double to, double rate): diff --git a/host/lib/usrp/CMakeLists.txt b/host/lib/usrp/CMakeLists.txt index d20cc966b..f769417d9 100644 --- a/host/lib/usrp/CMakeLists.txt +++ b/host/lib/usrp/CMakeLists.txt @@ -52,6 +52,7 @@ ENDIF(ENABLE_GPSD) INCLUDE_SUBDIRECTORY(cores) INCLUDE_SUBDIRECTORY(dboard) INCLUDE_SUBDIRECTORY(common) +INCLUDE_SUBDIRECTORY(device3) INCLUDE_SUBDIRECTORY(usrp1) INCLUDE_SUBDIRECTORY(usrp2) INCLUDE_SUBDIRECTORY(b100) diff --git a/host/lib/usrp/cores/CMakeLists.txt b/host/lib/usrp/cores/CMakeLists.txt index 404fc6137..1e16dd39e 100644 --- a/host/lib/usrp/cores/CMakeLists.txt +++ b/host/lib/usrp/cores/CMakeLists.txt @@ -30,6 +30,7 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/rx_dsp_core_200.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tx_dsp_core_200.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rx_frontend_core_200.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rx_frontend_core_3000.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tx_frontend_core_200.cpp ${CMAKE_CURRENT_SOURCE_DIR}/user_settings_core_200.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rx_vita_core_3000.cpp diff --git a/host/lib/usrp/cores/dma_fifo_core_3000.cpp b/host/lib/usrp/cores/dma_fifo_core_3000.cpp index 1a9d5dd5c..5df28f7c2 100644 --- a/host/lib/usrp/cores/dma_fifo_core_3000.cpp +++ b/host/lib/usrp/cores/dma_fifo_core_3000.cpp @@ -240,6 +240,10 @@ public: flush(); } + virtual ~dma_fifo_core_3000_impl() + { + } + virtual void flush() { //Clear the FIFO and hold it in that state _fifo_ctrl_reg.write(fifo_ctrl_reg_t::CLEAR_FIFO, 1); diff --git a/host/lib/usrp/cores/rx_frontend_core_3000.cpp b/host/lib/usrp/cores/rx_frontend_core_3000.cpp new file mode 100644 index 000000000..23197cf5a --- /dev/null +++ b/host/lib/usrp/cores/rx_frontend_core_3000.cpp @@ -0,0 +1,186 @@ +// +// Copyright 2011-2012,2014-2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "rx_frontend_core_3000.hpp" +#include "dsp_core_utils.hpp" +#include <boost/math/special_functions/round.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/bind.hpp> +#include <uhd/types/dict.hpp> + +using namespace uhd; + +#define REG_RX_FE_MAG_CORRECTION (_base + 0 ) //18 bits +#define REG_RX_FE_PHASE_CORRECTION (_base + 4 ) //18 bits +#define REG_RX_FE_OFFSET_I (_base + 8 ) //18 bits +#define REG_RX_FE_OFFSET_Q (_base + 12) //18 bits +#define REG_RX_FE_MAPPING (_base + 16) +#define REG_RX_FE_HET_CORDIC_PHASE (_base + 20) + +#define FLAG_DSP_RX_MAPPING_SWAP_IQ (1 << 0) +#define FLAG_DSP_RX_MAPPING_REAL_MODE (1 << 1) +#define FLAG_DSP_RX_MAPPING_INVERT_Q (1 << 2) +#define FLAG_DSP_RX_MAPPING_INVERT_I (1 << 3) +#define FLAG_DSP_RX_MAPPING_REAL_DECIM (1 << 4) +//#define FLAG_DSP_RX_MAPPING_RESERVED (1 << 5) +//#define FLAG_DSP_RX_MAPPING_RESERVED (1 << 6) +#define FLAG_DSP_RX_MAPPING_BYPASS_ALL (1 << 7) + +#define OFFSET_FIXED (1ul << 31) +#define OFFSET_SET (1ul << 30) +#define FLAG_MASK (OFFSET_FIXED | OFFSET_SET) + +using namespace uhd::usrp; + +static boost::uint32_t fs_to_bits(const double num, const size_t bits){ + return boost::int32_t(boost::math::round(num * (1 << (bits-1)))); +} + +rx_frontend_core_3000::~rx_frontend_core_3000(void){ + /* NOP */ +} + +const std::complex<double> rx_frontend_core_3000::DEFAULT_DC_OFFSET_VALUE = std::complex<double>(0.0, 0.0); +const bool rx_frontend_core_3000::DEFAULT_DC_OFFSET_ENABLE = true; +const std::complex<double> rx_frontend_core_3000::DEFAULT_IQ_BALANCE_VALUE = std::complex<double>(0.0, 0.0); + +class rx_frontend_core_3000_impl : public rx_frontend_core_3000{ +public: + rx_frontend_core_3000_impl(wb_iface::sptr iface, const size_t base): + _i_dc_off(0), _q_dc_off(0), + _adc_rate(0.0), + _fe_conn(fe_connection_t("IQ")), + _iface(iface), _base(base) + { + //NOP + } + + void set_adc_rate(const double rate) { + _adc_rate = rate; + } + + void bypass_all(bool bypass_en) { + if (bypass_en) { + _iface->poke32(REG_RX_FE_MAPPING, FLAG_DSP_RX_MAPPING_BYPASS_ALL); + } else { + set_fe_connection(_fe_conn); + } + } + + void set_fe_connection(const fe_connection_t& fe_conn) { + boost::uint32_t mapping_reg_val = 0; + switch (fe_conn.get_sampling_mode()) { + case fe_connection_t::REAL: + case fe_connection_t::HETERODYNE: + mapping_reg_val = FLAG_DSP_RX_MAPPING_REAL_MODE|FLAG_DSP_RX_MAPPING_REAL_DECIM; + break; + default: + mapping_reg_val = 0; + break; + } + + if (fe_conn.is_iq_swapped()) mapping_reg_val |= FLAG_DSP_RX_MAPPING_SWAP_IQ; + if (fe_conn.is_i_inverted()) mapping_reg_val |= FLAG_DSP_RX_MAPPING_INVERT_I; + if (fe_conn.is_q_inverted()) mapping_reg_val |= FLAG_DSP_RX_MAPPING_INVERT_Q; + + _iface->poke32(REG_RX_FE_MAPPING, mapping_reg_val); + + UHD_ASSERT_THROW(_adc_rate!=0.0) + double cordic_freq = 0.0, actual_cordic_freq = 0.0; + if (fe_conn.get_sampling_mode() == fe_connection_t::HETERODYNE) { + //1. Remember the sign of the IF frequency. + // It will be discarded in the next step + int if_freq_sign = boost::math::sign(fe_conn.get_if_freq()); + //2. Map IF frequency to the range [0, _adc_rate) + double if_freq = std::abs(std::fmod(fe_conn.get_if_freq(), _adc_rate)); + //3. Map IF frequency to the range [-_adc_rate/2, _adc_rate/2) + // This is the aliased frequency + if (if_freq > (_adc_rate / 2.0)) { + if_freq -= _adc_rate; + } + //4. Set DSP offset to spin the signal in the opposite + // direction as the aliased frequency + cordic_freq = if_freq * (-if_freq_sign); + } + int32_t freq_word; + get_freq_and_freq_word(cordic_freq, _adc_rate, actual_cordic_freq, freq_word); + _iface->poke32(REG_RX_FE_HET_CORDIC_PHASE, boost::uint32_t(freq_word)); + + _fe_conn = fe_conn; + } + + void set_dc_offset_auto(const bool enb) { + _set_dc_offset(enb ? 0 : OFFSET_FIXED); + } + + std::complex<double> set_dc_offset(const std::complex<double> &off) { + static const double scaler = double(1ul << 29); + _i_dc_off = boost::math::iround(off.real()*scaler); + _q_dc_off = boost::math::iround(off.imag()*scaler); + + _set_dc_offset(OFFSET_SET | OFFSET_FIXED); + + return std::complex<double>(_i_dc_off/scaler, _q_dc_off/scaler); + } + + void _set_dc_offset(const boost::uint32_t flags) { + _iface->poke32(REG_RX_FE_OFFSET_I, flags | (_i_dc_off & ~FLAG_MASK)); + _iface->poke32(REG_RX_FE_OFFSET_Q, flags | (_q_dc_off & ~FLAG_MASK)); + } + + void set_iq_balance(const std::complex<double> &cor) { + _iface->poke32(REG_RX_FE_MAG_CORRECTION, fs_to_bits(cor.real(), 18)); + _iface->poke32(REG_RX_FE_PHASE_CORRECTION, fs_to_bits(cor.imag(), 18)); + } + + void populate_subtree(uhd::property_tree::sptr subtree) { + subtree->create<std::complex<double> >("dc_offset/value") + .set(DEFAULT_DC_OFFSET_VALUE) + .set_coercer(boost::bind(&rx_frontend_core_3000::set_dc_offset, this, _1)) + ; + subtree->create<bool>("dc_offset/enable") + .set(DEFAULT_DC_OFFSET_ENABLE) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_3000::set_dc_offset_auto, this, _1)) + ; + subtree->create<std::complex<double> >("iq_balance/value") + .set(DEFAULT_IQ_BALANCE_VALUE) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_3000::set_iq_balance, this, _1)) + ; + } + + double get_output_rate() { + switch (_fe_conn.get_sampling_mode()) { + case fe_connection_t::REAL: + case fe_connection_t::HETERODYNE: + return _adc_rate / 2; + default: + return _adc_rate; + } + return _adc_rate; + } + +private: + boost::int32_t _i_dc_off, _q_dc_off; + double _adc_rate; + fe_connection_t _fe_conn; + wb_iface::sptr _iface; + const size_t _base; +}; + +rx_frontend_core_3000::sptr rx_frontend_core_3000::make(wb_iface::sptr iface, const size_t base){ + return sptr(new rx_frontend_core_3000_impl(iface, base)); +} diff --git a/host/lib/usrp/cores/rx_frontend_core_3000.hpp b/host/lib/usrp/cores/rx_frontend_core_3000.hpp new file mode 100644 index 000000000..baa58331e --- /dev/null +++ b/host/lib/usrp/cores/rx_frontend_core_3000.hpp @@ -0,0 +1,69 @@ +// +// Copyright 2011,2014-2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_TX_FRONTEND_CORE_3000_HPP +#define INCLUDED_LIBUHD_USRP_TX_FRONTEND_CORE_3000_HPP + +#include <uhd/config.hpp> +#include <uhd/types/wb_iface.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/usrp/fe_connection.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include <complex> +#include <string> + +class rx_frontend_core_3000 : boost::noncopyable{ +public: + static const std::complex<double> DEFAULT_DC_OFFSET_VALUE; + static const bool DEFAULT_DC_OFFSET_ENABLE; + static const std::complex<double> DEFAULT_IQ_BALANCE_VALUE; + + typedef boost::shared_ptr<rx_frontend_core_3000> sptr; + + virtual ~rx_frontend_core_3000(void) = 0; + + static sptr make(uhd::wb_iface::sptr iface, const size_t base); + + /*! Set the input sampling rate (i.e. ADC rate) + */ + virtual void set_adc_rate(const double rate) = 0; + + virtual void bypass_all(bool bypass_en) = 0; + + virtual void set_fe_connection(const uhd::usrp::fe_connection_t& fe_conn) = 0; + + virtual void set_dc_offset_auto(const bool enb) = 0; + + virtual std::complex<double> set_dc_offset(const std::complex<double> &off) = 0; + + virtual void set_iq_balance(const std::complex<double> &cor) = 0; + + virtual void populate_subtree(uhd::property_tree::sptr subtree) = 0; + + /*! Return the sampling rate at the output + * + * In real mode, the frontend core will decimate the sampling rate by a + * factor of 2. + * + * \returns RX sampling rate + */ + virtual double get_output_rate(void) = 0; + +}; + +#endif /* INCLUDED_LIBUHD_USRP_TX_FRONTEND_CORE_3000_HPP */ diff --git a/host/lib/usrp/cores/rx_vita_core_3000.cpp b/host/lib/usrp/cores/rx_vita_core_3000.cpp index 52121837e..54c57c2d5 100644 --- a/host/lib/usrp/cores/rx_vita_core_3000.cpp +++ b/host/lib/usrp/cores/rx_vita_core_3000.cpp @@ -82,7 +82,9 @@ struct rx_vita_core_3000_impl : rx_vita_core_3000 void clear(void) { - this->configure_flow_control(0); //disable fc + // FC should never be disabled, this will actually become + // impossible in the future + //this->configure_flow_control(0); //disable fc } void set_nsamps_per_packet(const size_t nsamps) diff --git a/host/lib/usrp/device3/CMakeLists.txt b/host/lib/usrp/device3/CMakeLists.txt new file mode 100644 index 000000000..83f01a2e7 --- /dev/null +++ b/host/lib/usrp/device3/CMakeLists.txt @@ -0,0 +1,25 @@ +# +# Copyright 2014 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/device3_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/device3_io_impl.cpp +) diff --git a/host/lib/usrp/device3/device3_impl.cpp b/host/lib/usrp/device3/device3_impl.cpp new file mode 100644 index 000000000..7fcbc01b2 --- /dev/null +++ b/host/lib/usrp/device3/device3_impl.cpp @@ -0,0 +1,188 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "device3_impl.hpp" +#include "graph_impl.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/rfnoc/block_ctrl_base.hpp> +#include <boost/make_shared.hpp> +#include <algorithm> + +#define UHD_DEVICE3_LOG() UHD_LOGV(never) + +using namespace uhd::usrp; + +device3_impl::device3_impl() + : _sid_framer(0) +{ + _type = uhd::device::USRP; + _async_md.reset(new async_md_type(1000/*messages deep*/)); + _tree = uhd::property_tree::make(); +}; + +//! Returns true if the integer value stored in lhs is smaller than that in rhs +bool _compare_string_indexes(const std::string &lhs, const std::string &rhs) +{ + return boost::lexical_cast<size_t>(lhs) < boost::lexical_cast<size_t>(rhs); +} + +void device3_impl::merge_channel_defs( + const std::vector<uhd::rfnoc::block_id_t> &chan_ids, + const std::vector<uhd::device_addr_t> &chan_args, + const uhd::direction_t dir +) { + UHD_ASSERT_THROW(chan_ids.size() == chan_args.size()); + if (dir == uhd::DX_DIRECTION) { + merge_channel_defs(chan_ids, chan_args, RX_DIRECTION); + merge_channel_defs(chan_ids, chan_args, TX_DIRECTION); + return; + } + + uhd::fs_path chans_root = uhd::fs_path("/channels/") / (dir == RX_DIRECTION ? "rx" : "tx"); + // Store the new positions of the channels: + std::vector<size_t> chan_idxs; + + // 1. Get sorted list of currently defined channels + std::vector<std::string> curr_channels; + if (_tree->exists(chans_root)) { + curr_channels = _tree->list(chans_root); + std::sort(curr_channels.begin(), curr_channels.end(), _compare_string_indexes); + } + + // 2. Cycle through existing channels to find out where to merge + // the new channels. Rules are: + // - The order of chan_ids must be preserved + // - All block indices that are in chan_ids may be overwritten in the channel definition + // - If the channels in chan_ids are not yet in the property tree channel list, + // they are appended. + BOOST_FOREACH(const std::string &chan_idx, curr_channels) { + if (_tree->exists(chans_root / chan_idx)) { + rfnoc::block_id_t chan_block_id = _tree->access<rfnoc::block_id_t>(chans_root / chan_idx).get(); + if (std::find(chan_ids.begin(), chan_ids.end(), chan_block_id) != chan_ids.end()) { + chan_idxs.push_back(boost::lexical_cast<size_t>(chan_idx)); + } + } + } + size_t last_chan_idx = curr_channels.empty() ? 0 : (boost::lexical_cast<size_t>(curr_channels.back()) + 1); + while (chan_idxs.size() < chan_ids.size()) { + chan_idxs.push_back(last_chan_idx); + last_chan_idx++; + } + + // 3. Write the new channels + for (size_t i = 0; i < chan_ids.size(); i++) { + if (not _tree->exists(chans_root / chan_idxs[i])) { + _tree->create<rfnoc::block_id_t>(chans_root / chan_idxs[i]); + } + _tree->access<rfnoc::block_id_t>(chans_root / chan_idxs[i]).set(chan_ids[i]); + if (not _tree->exists(chans_root / chan_idxs[i] / "args")) { + _tree->create<uhd::device_addr_t>(chans_root / chan_idxs[i] / "args"); + } + _tree->access<uhd::device_addr_t>(chans_root / chan_idxs[i] / "args").set(chan_args[i]); + } +} + +/*********************************************************************** + * RFNoC-Specific + **********************************************************************/ +void device3_impl::enumerate_rfnoc_blocks( + size_t device_index, + size_t n_blocks, + size_t base_port, + const uhd::sid_t &base_sid, + uhd::device_addr_t transport_args, + uhd::endianness_t endianness +) { + // entries that are already connected to this block + uhd::sid_t ctrl_sid = base_sid; + uhd::property_tree::sptr subtree = _tree->subtree(uhd::fs_path("/mboards") / device_index); + // 1) Clean property tree entries + // TODO put this back once radios are actual rfnoc blocks!!!!!! + //if (subtree->exists("xbar")) { + //subtree->remove("xbar"); + //} + // 2) Destroy existing block controllers + // TODO: Clear out all the old block control classes + // 3) Create new block controllers + for (size_t i = 0; i < n_blocks; i++) { + UHD_DEVICE3_LOG() << "[RFNOC] ------- Block Setup -----------" << std::endl; + // First, make a transport for port number zero, because we always need that: + ctrl_sid.set_dst_xbarport(base_port + i); + ctrl_sid.set_dst_blockport(0); + both_xports_t xport = this->make_transport( + ctrl_sid, + CTRL, + transport_args + ); + UHD_DEVICE3_LOG() << str(boost::format("Setting up NoC-Shell Control for port #0 (SID: %s)...") % xport.send_sid.to_pp_string_hex()); + uhd::rfnoc::ctrl_iface::sptr ctrl = uhd::rfnoc::ctrl_iface::make( + endianness == ENDIANNESS_BIG, + xport.send, + xport.recv, + xport.send_sid, + str(boost::format("CE_%02d_Port_%02X") % i % ctrl_sid.get_dst_endpoint()) + ); + UHD_DEVICE3_LOG() << "OK" << std::endl; + uint64_t noc_id = ctrl->peek64(uhd::rfnoc::SR_READBACK_REG_ID); + UHD_DEVICE3_LOG() << str(boost::format("Port %d: Found NoC-Block with ID %016X.") % int(ctrl_sid.get_dst_endpoint()) % noc_id) << std::endl; + uhd::rfnoc::make_args_t make_args; + uhd::rfnoc::blockdef::sptr block_def = uhd::rfnoc::blockdef::make_from_noc_id(noc_id); + if (not block_def) { + UHD_DEVICE3_LOG() << "Using default block configuration." << std::endl; + block_def = uhd::rfnoc::blockdef::make_from_noc_id(uhd::rfnoc::DEFAULT_NOC_ID); + } + UHD_ASSERT_THROW(block_def); + make_args.ctrl_ifaces[0] = ctrl; + BOOST_FOREACH(const size_t port_number, block_def->get_all_port_numbers()) { + if (port_number == 0) { // We've already set this up + continue; + } + ctrl_sid.set_dst_blockport(port_number); + both_xports_t xport1 = this->make_transport( + ctrl_sid, + CTRL, + transport_args + ); + UHD_DEVICE3_LOG() << str(boost::format("Setting up NoC-Shell Control for port #%d (SID: %s)...") % port_number % xport1.send_sid.to_pp_string_hex()); + uhd::rfnoc::ctrl_iface::sptr ctrl1 = uhd::rfnoc::ctrl_iface::make( + endianness == ENDIANNESS_BIG, + xport1.send, + xport1.recv, + xport1.send_sid, + str(boost::format("CE_%02d_Port_%02d") % i % ctrl_sid.get_dst_endpoint()) + ); + UHD_DEVICE3_LOG() << "OK" << std::endl; + make_args.ctrl_ifaces[port_number] = ctrl1; + } + + make_args.base_address = xport.send_sid.get_dst(); + make_args.device_index = device_index; + make_args.tree = subtree; + make_args.is_big_endian = (endianness == ENDIANNESS_BIG); + _rfnoc_block_ctrl.push_back(uhd::rfnoc::block_ctrl_base::make(make_args, noc_id)); + } +} + + +uhd::rfnoc::graph::sptr device3_impl::create_graph(const std::string &name) +{ + return boost::make_shared<uhd::rfnoc::graph_impl>( + name, + shared_from_this() + ); +} + diff --git a/host/lib/usrp/device3/device3_impl.hpp b/host/lib/usrp/device3/device3_impl.hpp new file mode 100644 index 000000000..0d94ae21c --- /dev/null +++ b/host/lib/usrp/device3/device3_impl.hpp @@ -0,0 +1,210 @@ +// +// Copyright 2014-2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +// Declares the device3_impl class which is a layer between device3 and +// the different 3-rd gen device impls (e.g. x300_impl) + +#ifndef INCLUDED_DEVICE3_IMPL_HPP +#define INCLUDED_DEVICE3_IMPL_HPP + +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/transport/chdr.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <uhd/types/sid.hpp> +#include <uhd/types/metadata.hpp> +#include <uhd/types/endianness.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/utils/tasks.hpp> +#include <uhd/device3.hpp> +#include "xports.hpp" +// Common FPGA cores: +#include "ctrl_iface.hpp" +#include "rx_dsp_core_3000.hpp" +#include "tx_dsp_core_3000.hpp" +#include "rx_vita_core_3000.hpp" +#include "tx_vita_core_3000.hpp" +#include "rx_frontend_core_200.hpp" +#include "tx_frontend_core_200.hpp" +#include "time_core_3000.hpp" +#include "gpio_atr_3000.hpp" +// RFNoC-specific includes: +#include "radio_ctrl_impl.hpp" + +namespace uhd { namespace usrp { + +/*********************************************************************** + * Default settings (any device3 may override these) + **********************************************************************/ +static const size_t DEVICE3_RX_FC_REQUEST_FREQ = 32; //per flow-control window +static const size_t DEVICE3_TX_FC_RESPONSE_FREQ = 8; +static const size_t DEVICE3_TX_FC_RESPONSE_CYCLES = 0; // Cycles: Off. + +static const size_t DEVICE3_TX_MAX_HDR_LEN = uhd::transport::vrt::chdr::max_if_hdr_words64 * sizeof(boost::uint64_t); // Bytes +static const size_t DEVICE3_RX_MAX_HDR_LEN = uhd::transport::vrt::chdr::max_if_hdr_words64 * sizeof(boost::uint64_t); // Bytes + +class device3_impl : public uhd::device3, public boost::enable_shared_from_this<device3_impl> +{ +public: + /*********************************************************************** + * device3-specific Types + **********************************************************************/ + typedef uhd::transport::bounded_buffer<uhd::async_metadata_t> async_md_type; + + //! The purpose of a transport + enum xport_type_t { + CTRL = 0, + TX_DATA, + RX_DATA + }; + + enum xport_t {AXI, ETH, PCIE}; + + //! Stores all streaming-related options + struct stream_options_t + { + //! Max size of the header in bytes for TX + size_t tx_max_len_hdr; + //! Max size of the header in bytes for RX + size_t rx_max_len_hdr; + //! How often we send ACKs to the upstream block per one full FC window + size_t rx_fc_request_freq; + //! How often the downstream block should send ACKs per one full FC window + size_t tx_fc_response_freq; + //! How often the downstream block should send ACKs in cycles + size_t tx_fc_response_cycles; + stream_options_t(void) + : tx_max_len_hdr(DEVICE3_TX_MAX_HDR_LEN) + , rx_max_len_hdr(DEVICE3_RX_MAX_HDR_LEN) + , rx_fc_request_freq(DEVICE3_RX_FC_REQUEST_FREQ) + , tx_fc_response_freq(DEVICE3_TX_FC_RESPONSE_FREQ) + , tx_fc_response_cycles(DEVICE3_TX_FC_RESPONSE_CYCLES) + {}; + }; + + /*********************************************************************** + * I/O Interface + **********************************************************************/ + uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &); + uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &); + bool recv_async_msg(uhd::async_metadata_t &async_metadata, double timeout); + + /*********************************************************************** + * Other public APIs + **********************************************************************/ + rfnoc::graph::sptr create_graph(const std::string &name=""); + +protected: + /*********************************************************************** + * Structors + **********************************************************************/ + device3_impl(); + virtual ~device3_impl() {}; + + /*********************************************************************** + * Streaming-related + **********************************************************************/ + // The 'rate' argument is so we can use these as subscribers to rate changes +public: // TODO make these protected again + void update_rx_streamers(double rate=-1.0); + void update_tx_streamers(double rate=-1.0); +protected: + + /*********************************************************************** + * Transport-related + **********************************************************************/ + stream_options_t stream_options; + + /*! \brief Create a transport to a given endpoint. + * + * \param address The endpoint address of the block we're creating a transport to. + * The source address in this value is not considered, only the + * destination address. + * \param xport_type Specify which kind of transport this is. + * \param args Additional arguments for the transport generation. See \ref page_transport + * for valid arguments. + */ + virtual uhd::both_xports_t make_transport( + const uhd::sid_t &address, + const xport_type_t xport_type, + const uhd::device_addr_t& args + ) = 0; + + virtual uhd::device_addr_t get_tx_hints(size_t) { return uhd::device_addr_t(); }; + virtual uhd::device_addr_t get_rx_hints(size_t) { return uhd::device_addr_t(); }; + virtual uhd::endianness_t get_transport_endianness(size_t mb_index) = 0; + + //! Is called after a streamer is generated + virtual void post_streamer_hooks(uhd::direction_t) {}; + + /*********************************************************************** + * Channel-related + **********************************************************************/ + /*! Merge a list of channels into the existing channel definition. + * + * Intelligently merge the channels described in \p chan_ids + * into the current channel definition. If none of the channels in + * \p chan_ids is in the current definition, they simply get appended. + * Otherwise, they get overwritten in the order of \p chan_ids. + * + * \param chan_ids List of block IDs for the channels. + * \param chan_args New channel args. Must have same length as chan_ids. + * + */ + void merge_channel_defs( + const std::vector<rfnoc::block_id_t> &chan_ids, + const std::vector<uhd::device_addr_t> &chan_args, + const uhd::direction_t dir + ); + + /*********************************************************************** + * RFNoC-Specific + **********************************************************************/ + void enumerate_rfnoc_blocks( + size_t device_index, + size_t n_blocks, + size_t base_port, + const uhd::sid_t &base_sid, + uhd::device_addr_t transport_args, + uhd::endianness_t endianness + ); + + /*********************************************************************** + * Members + **********************************************************************/ + //! A counter, designed to create unique SIDs + size_t _sid_framer; + + // TODO: Maybe move these to private + uhd::dict<std::string, boost::weak_ptr<uhd::rx_streamer> > _rx_streamers; + uhd::dict<std::string, boost::weak_ptr<uhd::tx_streamer> > _tx_streamers; + +private: + /*********************************************************************** + * Private Members + **********************************************************************/ + //! Buffer for async metadata + boost::shared_ptr<async_md_type> _async_md; + + //! This mutex locks the get_xx_stream() functions. + boost::mutex _transport_setup_mutex; +}; + +}} /* namespace uhd::usrp */ + +#endif /* INCLUDED_DEVICE3_IMPL_HPP */ +// vim: sw=4 expandtab: diff --git a/host/lib/usrp/device3/device3_io_impl.cpp b/host/lib/usrp/device3/device3_io_impl.cpp new file mode 100644 index 000000000..8c61f8f15 --- /dev/null +++ b/host/lib/usrp/device3/device3_io_impl.cpp @@ -0,0 +1,851 @@ +// +// Copyright 2014-2016 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +// Provides streaming-related functions which are used by device3 objects. + +#define DEVICE3_STREAMER // For the super_*_packet_handlers + +#include "device3_impl.hpp" +#include <uhd/rfnoc/constants.hpp> +#include <uhd/rfnoc/source_block_ctrl_base.hpp> +#include <uhd/rfnoc/sink_block_ctrl_base.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include "../common/async_packet_handler.hpp" +#include "../../transport/super_recv_packet_handler.hpp" +#include "../../transport/super_send_packet_handler.hpp" +#include "../../rfnoc/rx_stream_terminator.hpp" +#include "../../rfnoc/tx_stream_terminator.hpp" +#include <uhd/rfnoc/rate_node_ctrl.hpp> +#include <uhd/rfnoc/radio_ctrl.hpp> + +#define UHD_STREAMER_LOG() UHD_LOGV(never) + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +//! CVITA uses 12-Bit sequence numbers +static const boost::uint32_t HW_SEQ_NUM_MASK = 0xfff; + + +/*********************************************************************** + * Helper functions for get_?x_stream() + **********************************************************************/ +static uhd::stream_args_t sanitize_stream_args(const uhd::stream_args_t &args_) +{ + uhd::stream_args_t args = args_; + if (args.channels.empty()) { + args.channels = std::vector<size_t>(1, 0); + } + + return args; +} + +static void check_stream_sig_compatible(const rfnoc::stream_sig_t &stream_sig, stream_args_t &args, const std::string &tx_rx) +{ + if (args.otw_format.empty()) { + if (stream_sig.item_type.empty()) { + throw uhd::runtime_error(str( + boost::format("[%s Streamer] No otw_format defined!") % tx_rx + )); + } else { + args.otw_format = stream_sig.item_type; + } + } else if (not stream_sig.item_type.empty() and stream_sig.item_type != args.otw_format) { + throw uhd::runtime_error(str( + boost::format("[%s Streamer] Conflicting OTW types defined: args.otw_format = '%s' <=> stream_sig.item_type = '%s'") + % tx_rx % args.otw_format % stream_sig.item_type + )); + } + const size_t bpi = convert::get_bytes_per_item(args.otw_format); // bytes per item + if (stream_sig.packet_size) { + if (args.args.has_key("spp")) { + size_t args_spp = args.args.cast<size_t>("spp", 0); + if (args_spp * bpi != stream_sig.packet_size) { + throw uhd::runtime_error(str( + boost::format("[%s Streamer] Conflicting packet sizes defined: args yields %d bytes but stream_sig.packet_size is %d bytes") + % tx_rx % (args_spp * bpi) % stream_sig.packet_size + )); + } + } else { + args.args["spp"] = str(boost::format("%d") % (stream_sig.packet_size / bpi)); + } + } +} + +/*! \brief Returns a list of rx or tx channels for a streamer. + * + * If the given stream args contain instructions to set up channels, + * those are used. Otherwise, the current device's channel definition + * is consulted. + * + * \param args_ Stream args. + * \param[out] chan_list The list of channels in the correct order. + * \param[out] chan_args Channel args for every channel. `chan_args.size() == chan_list.size()` + */ +void generate_channel_list( + const uhd::stream_args_t &args_, + std::vector<uhd::rfnoc::block_id_t> &chan_list, + std::vector<device_addr_t> &chan_args +) { + uhd::stream_args_t args = args_; + BOOST_FOREACH(const size_t chan_idx, args.channels) { + //// Find block ID for this channel: + if (args.args.has_key(str(boost::format("block_id%d") % chan_idx))) { + chan_list.push_back( + uhd::rfnoc::block_id_t( + args.args.pop(str(boost::format("block_id%d") % chan_idx)) + ) + ); + chan_args.push_back(args.args); + } else if (args.args.has_key("block_id")) { + chan_list.push_back(args.args.get("block_id")); + chan_args.push_back(args.args); + chan_args.back().pop("block_id"); + } else { + throw uhd::runtime_error(str( + boost::format("Cannot create streamers: No block_id specified for channel %d.") + % chan_idx + )); + } + //// Find block port for this channel + if (args.args.has_key(str(boost::format("block_port%d") % chan_idx))) { + chan_args.back()["block_port"] = args.args.pop(str(boost::format("block_port%d") % chan_idx)); + } else if (args.args.has_key("block_port")) { + // We have to write it again, because the chan args from the + // property tree might have overwritten this + chan_args.back()["block_port"] = args.args.get("block_port"); + } + } +} + + +/*********************************************************************** + * RX Flow Control Functions + **********************************************************************/ +//! Stores the state of RX flow control +struct rx_fc_cache_t +{ + rx_fc_cache_t(): + last_seq_in(0){} + size_t last_seq_in; +}; + +/*! Determine the size of the flow control window in number of packets. + * + * This value depends on three things: + * - The packet size (in bytes), P + * - The size of the software buffer (in bytes), B + * - The desired buffer fullness, F + * + * The FC window size is thus X = floor(B*F/P). + * + * \param pkt_size The maximum packet size in bytes + * \param sw_buff_size Software buffer size in bytes + * \param rx_args If this has a key 'recv_buff_fullness', this value will + * be used for said fullness. Must be between 0.01 and 1. + * + * \returns The size of the flow control window in number of packets + */ +static size_t get_rx_flow_control_window( + size_t pkt_size, + size_t sw_buff_size, + const device_addr_t& rx_args +) { + double fullness_factor = rx_args.cast<double>( + "recv_buff_fullness", + uhd::rfnoc::DEFAULT_FC_RX_SW_BUFF_FULL_FACTOR + ); + + if (fullness_factor < 0.01 || fullness_factor > 1) { + throw uhd::value_error("recv_buff_fullness must be in [0.01, 1] inclusive (1% to 100%)"); + } + + size_t window_in_pkts = (static_cast<size_t>(sw_buff_size * fullness_factor) / pkt_size); + if (rx_args.has_key("max_recv_window")) { + window_in_pkts = std::min( + window_in_pkts, + rx_args.cast<size_t>("max_recv_window", window_in_pkts) + ); + } + if (window_in_pkts == 0) { + throw uhd::value_error("recv_buff_size must be larger than the recv_frame_size."); + } + UHD_ASSERT_THROW(size_t(sw_buff_size * fullness_factor) >= pkt_size * window_in_pkts); + return window_in_pkts; +} + + +/*! Send out RX flow control packets. + * + * For an rx stream, this function takes care of sending back + * a flow control packet to the source telling it which + * packets have been consumed. + * + * This function should only be called by the function handling + * the rx stream, usually recv() in super_recv_packet_handler. + * + * \param sid The SID that goes into this packet. This is the reversed() + * version of the data stream's SID. + * \param xport A transport object over which to send the data + * \param big_endian Endianness of the transport + * \param seq32_state Pointer to a variable that saves the 32-Bit state + * of the sequence numbers, since we only have 12 Bit + * sequence numbers in CHDR. + * \param last_seq The value to send: The last consumed packet's sequence number. + */ +static void handle_rx_flowctrl( + const sid_t &sid, + zero_copy_if::sptr xport, + endianness_t endianness, + boost::shared_ptr<rx_fc_cache_t> fc_cache, + const size_t last_seq +) { + static const size_t RXFC_PACKET_LEN_IN_WORDS = 2; + static const size_t RXFC_CMD_CODE_OFFSET = 0; + static const size_t RXFC_SEQ_NUM_OFFSET = 1; + + managed_send_buffer::sptr buff = xport->get_send_buff(0.0); + if (not buff) { + throw uhd::runtime_error("handle_rx_flowctrl timed out getting a send buffer"); + } + boost::uint32_t *pkt = buff->cast<boost::uint32_t *>(); + + // Recover sequence number. The sequence numbers handled by the streamers + // are 12 Bits, but we want to know the 32-Bit sequence number. + size_t &seq32 = fc_cache->last_seq_in; + const size_t seq12 = seq32 & HW_SEQ_NUM_MASK; + if (last_seq < seq12) + seq32 += (HW_SEQ_NUM_MASK + 1); + seq32 &= ~HW_SEQ_NUM_MASK; + seq32 |= last_seq; + + // Super-verbose mode: + //static size_t fc_pkt_count = 0; + //UHD_MSG(status) << "sending flow ctrl packet " << fc_pkt_count++ << ", acking " << str(boost::format("%04d\tseq_sw==0x%08x") % last_seq % seq32) << std::endl; + + //load packet info + vrt::if_packet_info_t packet_info; + packet_info.packet_type = vrt::if_packet_info_t::PACKET_TYPE_FC; + packet_info.num_payload_words32 = RXFC_PACKET_LEN_IN_WORDS; + packet_info.num_payload_bytes = packet_info.num_payload_words32*sizeof(boost::uint32_t); + packet_info.packet_count = seq32; + packet_info.sob = false; + packet_info.eob = false; + packet_info.sid = sid.get(); + packet_info.has_sid = true; + packet_info.has_cid = false; + packet_info.has_tsi = false; + packet_info.has_tsf = false; + packet_info.has_tlr = false; + + if (endianness == ENDIANNESS_BIG) { + // Load Header: + vrt::chdr::if_hdr_pack_be(pkt, packet_info); + // Load Payload: (the sequence number) + pkt[packet_info.num_header_words32+RXFC_CMD_CODE_OFFSET] = uhd::htonx<boost::uint32_t>(0); + pkt[packet_info.num_header_words32+RXFC_SEQ_NUM_OFFSET] = uhd::htonx<boost::uint32_t>(seq32); + } else { + // Load Header: + vrt::chdr::if_hdr_pack_le(pkt, packet_info); + // Load Payload: (the sequence number) + pkt[packet_info.num_header_words32+RXFC_CMD_CODE_OFFSET] = uhd::htowx<boost::uint32_t>(0); + pkt[packet_info.num_header_words32+RXFC_SEQ_NUM_OFFSET] = uhd::htowx<boost::uint32_t>(seq32); + } + + //std::cout << " SID=" << std::hex << sid << " hdr bits=" << packet_info.packet_type << " seq32=" << seq32 << std::endl; + //std::cout << "num_packet_words32: " << packet_info.num_packet_words32 << std::endl; + //for (size_t i = 0; i < packet_info.num_packet_words32; i++) { + //std::cout << str(boost::format("0x%08x") % pkt[i]) << " "; + //if (i % 2) { + //std::cout << std::endl; + //} + //} + + //send the buffer over the interface + buff->commit(sizeof(boost::uint32_t)*(packet_info.num_packet_words32)); +} + +/*********************************************************************** + * TX Flow Control Functions + **********************************************************************/ +//! Stores the state of TX flow control +struct tx_fc_cache_t +{ + tx_fc_cache_t(void): + stream_channel(0), + device_channel(0), + last_seq_out(0), + last_seq_ack(0), + seq_queue(1){} + size_t stream_channel; + size_t device_channel; + size_t last_seq_out; + size_t last_seq_ack; + uhd::transport::bounded_buffer<size_t> seq_queue; + boost::shared_ptr<device3_impl::async_md_type> async_queue; + boost::shared_ptr<device3_impl::async_md_type> old_async_queue; +}; + +/*! Return the size of the flow control window in packets. + * + * If the return value of this function is F, the last tx'd packet + * has index N and the last ack'd packet has index M, the amount of + * FC credit we have is C = F + M - N (i.e. we can send C more packets + * before getting another ack). + * + * Note: If `send_buff_size` is set in \p tx_hints, this will + * override hw_buff_size_. + */ +static size_t get_tx_flow_control_window( + size_t pkt_size, + const double hw_buff_size_, + const device_addr_t& tx_hints +) { + double hw_buff_size = tx_hints.cast<double>("send_buff_size", hw_buff_size_); + size_t window_in_pkts = (static_cast<size_t>(hw_buff_size) / pkt_size); + if (window_in_pkts == 0) { + throw uhd::value_error("send_buff_size must be larger than the send_frame_size."); + } + return window_in_pkts; +} + +static managed_send_buffer::sptr get_tx_buff_with_flowctrl( + task::sptr /*holds ref*/, + boost::shared_ptr<tx_fc_cache_t> fc_cache, + zero_copy_if::sptr xport, + size_t fc_window, + const double timeout +){ + while (true) + { + // delta is the amount of FC credit we've used up + const size_t delta = (fc_cache->last_seq_out & HW_SEQ_NUM_MASK) - (fc_cache->last_seq_ack & HW_SEQ_NUM_MASK); + // If we want to send another packet, we must have FC credit left + if ((delta & HW_SEQ_NUM_MASK) < fc_window) + break; + + // If credit is all used up, we check seq_queue for more. + const bool ok = fc_cache->seq_queue.pop_with_timed_wait(fc_cache->last_seq_ack, timeout); + if (not ok) { + return managed_send_buffer::sptr(); //timeout waiting for flow control + } + } + + managed_send_buffer::sptr buff = xport->get_send_buff(timeout); + if (buff) { + fc_cache->last_seq_out++; //update seq, this will actually be a send + } + return buff; +} + +#define DEVICE3_ASYNC_EVENT_CODE_FLOW_CTRL 0 +/*! Handle incoming messages. If they're flow control, update the TX FC cache. + * Otherwise, send them to the async message queue for the user to poll. + * + * This is run inside a uhd::task as long as this streamer lives. + */ +static void handle_tx_async_msgs( + boost::shared_ptr<tx_fc_cache_t> fc_cache, + zero_copy_if::sptr xport, + endianness_t endianness, + boost::function<double(void)> get_tick_rate +) { + managed_recv_buffer::sptr buff = xport->get_recv_buff(); + if (not buff) + return; + + //extract packet info + vrt::if_packet_info_t if_packet_info; + if_packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); + const boost::uint32_t *packet_buff = buff->cast<const boost::uint32_t *>(); + + //unpacking can fail + boost::uint32_t (*endian_conv)(boost::uint32_t) = uhd::ntohx; + try + { + if (endianness == ENDIANNESS_BIG) + { + vrt::chdr::if_hdr_unpack_be(packet_buff, if_packet_info); + endian_conv = uhd::ntohx; + } + else + { + vrt::chdr::if_hdr_unpack_le(packet_buff, if_packet_info); + endian_conv = uhd::wtohx; + } + } + catch(const std::exception &ex) + { + UHD_MSG(error) << "Error parsing async message packet: " << ex.what() << std::endl; + return; + } + + double tick_rate = get_tick_rate(); + if (tick_rate == rfnoc::tick_node_ctrl::RATE_UNDEFINED) { + tick_rate = 1; + } + + //fill in the async metadata + async_metadata_t metadata; + load_metadata_from_buff( + endian_conv, + metadata, + if_packet_info, + packet_buff, + tick_rate, + fc_cache->stream_channel + ); + + // TODO: Shouldn't we be polling if_packet_info.packet_type == PACKET_TYPE_FC? + // Thing is, on X300, packet_type == 0, so that wouldn't work. But it seems it should. + //The FC response and the burst ack are two indicators that the radio + //consumed packets. Use them to update the FC metadata + if (metadata.event_code == DEVICE3_ASYNC_EVENT_CODE_FLOW_CTRL) { + const size_t seq = metadata.user_payload[0]; + fc_cache->seq_queue.push_with_pop_on_full(seq); + } + + //FC responses don't propagate up to the user so filter them here + if (metadata.event_code != DEVICE3_ASYNC_EVENT_CODE_FLOW_CTRL) { + fc_cache->async_queue->push_with_pop_on_full(metadata); + metadata.channel = fc_cache->device_channel; + fc_cache->old_async_queue->push_with_pop_on_full(metadata); + standard_async_msg_prints(metadata); + } +} + + + +/*********************************************************************** + * Async Data + **********************************************************************/ +bool device3_impl::recv_async_msg( + async_metadata_t &async_metadata, double timeout +) +{ + return _async_md->pop_with_timed_wait(async_metadata, timeout); +} + +/*********************************************************************** + * Receive streamer + **********************************************************************/ +void device3_impl::update_rx_streamers(double /* rate */) +{ + BOOST_FOREACH(const std::string &block_id, _rx_streamers.keys()) { + UHD_STREAMER_LOG() << "[Device3] updating RX streamer to " << block_id << std::endl; + boost::shared_ptr<sph::recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_rx_streamers[block_id].lock()); + if (my_streamer) { + double tick_rate = my_streamer->get_terminator()->get_tick_rate(); + if (tick_rate == rfnoc::tick_node_ctrl::RATE_UNDEFINED) { + tick_rate = 1.0; + } + my_streamer->set_tick_rate(tick_rate); + double samp_rate = my_streamer->get_terminator()->get_output_samp_rate(); + if (samp_rate == rfnoc::rate_node_ctrl::RATE_UNDEFINED) { + samp_rate = 1.0; + } + // This formula is not derived by any scientific means -- we just need to + // increase the failure threshold as we increase rates. For 1 Msps, we use + // the default. + const size_t alignment_failure_factor = std::max(size_t(1), size_t(samp_rate * 1000 / tick_rate)); + double scaling = my_streamer->get_terminator()->get_output_scale_factor(); + if (scaling == rfnoc::scalar_node_ctrl::SCALE_UNDEFINED) { + scaling = 1/32767.; + } + UHD_STREAMER_LOG() << " New tick_rate == " << tick_rate << " New samp_rate == " << samp_rate << " New scaling == " << scaling << std::endl; + + my_streamer->set_tick_rate(tick_rate); + my_streamer->set_samp_rate(samp_rate); + // 1000 packets is the default alignment failure threshold + my_streamer->set_alignment_failure_threshold(1000 * alignment_failure_factor); + my_streamer->set_scale_factor(scaling); + } + } +} + +rx_streamer::sptr device3_impl::get_rx_stream(const stream_args_t &args_) +{ + boost::mutex::scoped_lock lock(_transport_setup_mutex); + stream_args_t args = sanitize_stream_args(args_); + + // I. Generate the channel list + std::vector<uhd::rfnoc::block_id_t> chan_list; + std::vector<device_addr_t> chan_args; + generate_channel_list(args, chan_list, chan_args); + // Note: All 'args.args' are merged into chan_args now. + + // II. Iterate over all channels + boost::shared_ptr<sph::recv_packet_streamer> my_streamer; + // The terminator's lifetime is coupled to the streamer. + // There is only one terminator. If the streamer has multiple channels, + // it will be connected to each upstream block. + rfnoc::rx_stream_terminator::sptr recv_terminator = rfnoc::rx_stream_terminator::make(); + for (size_t stream_i = 0; stream_i < chan_list.size(); stream_i++) { + // Get block ID and mb index + uhd::rfnoc::block_id_t block_id = chan_list[stream_i]; + UHD_STREAMER_LOG() << "[RX Streamer] chan " << stream_i << " connecting to " << block_id << std::endl; + // Update args so args.args is always valid for this particular channel: + args.args = chan_args[stream_i]; + size_t mb_index = block_id.get_device_no(); + size_t suggested_block_port = args.args.cast<size_t>("block_port", rfnoc::ANY_PORT); + + // Access to this channel's block control + uhd::rfnoc::source_block_ctrl_base::sptr blk_ctrl = + boost::dynamic_pointer_cast<uhd::rfnoc::source_block_ctrl_base>(get_block_ctrl(block_id)); + + // Connect the terminator with this channel's block. + size_t block_port = blk_ctrl->connect_downstream( + recv_terminator, + suggested_block_port, + args.args + ); + const size_t terminator_port = recv_terminator->connect_upstream(blk_ctrl); + blk_ctrl->set_downstream_port(block_port, terminator_port); + recv_terminator->set_upstream_port(terminator_port, block_port); + + // Check if the block connection is compatible (spp and item type) + check_stream_sig_compatible(blk_ctrl->get_output_signature(block_port), args, "RX"); + + // Setup the DSP transport hints + device_addr_t rx_hints = get_rx_hints(mb_index); + + //allocate sid and create transport + uhd::sid_t stream_address = blk_ctrl->get_address(block_port); + UHD_STREAMER_LOG() << "[RX Streamer] creating rx stream " << rx_hints.to_string() << std::endl; + both_xports_t xport = make_transport(stream_address, RX_DATA, rx_hints); + UHD_STREAMER_LOG() << std::hex << "[RX Streamer] data_sid = " << xport.send_sid << std::dec << " actual recv_buff_size = " << xport.recv_buff_size << std::endl; + + // Configure the block + blk_ctrl->set_destination(xport.send_sid.get_src(), block_port); + + blk_ctrl->sr_write(uhd::rfnoc::SR_RESP_OUT_DST_SID, xport.send_sid.get_src(), block_port); + UHD_STREAMER_LOG() << "[RX Streamer] resp_out_dst_sid == " << xport.send_sid.get_src() << std::endl; + + // Find all upstream radio nodes and set their response in SID to the host + std::vector<boost::shared_ptr<uhd::rfnoc::radio_ctrl> > upstream_radio_nodes = blk_ctrl->find_upstream_node<uhd::rfnoc::radio_ctrl>(); + UHD_STREAMER_LOG() << "[RX Streamer] Number of upstream radio nodes: " << upstream_radio_nodes.size() << std::endl; + BOOST_FOREACH(const boost::shared_ptr<uhd::rfnoc::radio_ctrl> &node, upstream_radio_nodes) { + node->sr_write(uhd::rfnoc::SR_RESP_OUT_DST_SID, xport.send_sid.get_src(), block_port); + } + + // To calculate the max number of samples per packet, we assume the maximum header length + // to avoid fragmentation should the entire header be used. + const size_t bpp = xport.recv->get_recv_frame_size() - stream_options.rx_max_len_hdr; // bytes per packet + const size_t bpi = convert::get_bytes_per_item(args.otw_format); // bytes per item + const size_t spp = std::min(args.args.cast<size_t>("spp", bpp/bpi), bpp/bpi); // samples per packet + UHD_STREAMER_LOG() << "[RX Streamer] spp == " << spp << std::endl; + + //make the new streamer given the samples per packet + if (not my_streamer) + my_streamer = boost::make_shared<sph::recv_packet_streamer>(spp); + my_streamer->resize(chan_list.size()); + + //init some streamer stuff + std::string conv_endianness; + if (get_transport_endianness(mb_index) == ENDIANNESS_BIG) { + my_streamer->set_vrt_unpacker(&vrt::chdr::if_hdr_unpack_be); + conv_endianness = "be"; + } else { + my_streamer->set_vrt_unpacker(&vrt::chdr::if_hdr_unpack_le); + conv_endianness = "le"; + } + + //set the converter + uhd::convert::id_type id; + id.input_format = args.otw_format + "_item32_" + conv_endianness; + id.num_inputs = 1; + id.output_format = args.cpu_format; + id.num_outputs = 1; + my_streamer->set_converter(id); + + //flow control setup + const size_t pkt_size = spp * bpi + stream_options.rx_max_len_hdr; + const size_t fc_window = get_rx_flow_control_window(pkt_size, xport.recv_buff_size, rx_hints); + const size_t fc_handle_window = std::max<size_t>(1, fc_window / stream_options.rx_fc_request_freq); + UHD_STREAMER_LOG()<< "[RX Streamer] Flow Control Window (minus one) = " << fc_window-1 << ", Flow Control Handler Window = " << fc_handle_window << std::endl; + blk_ctrl->configure_flow_control_out( + fc_window-1, // Leave one space for overrun packets TODO make this obsolete + block_port + ); + + //Give the streamer a functor to get the recv_buffer + //bind requires a zero_copy_if::sptr to add a streamer->xport lifetime dependency + my_streamer->set_xport_chan_get_buff( + stream_i, + boost::bind(&zero_copy_if::get_recv_buff, xport.recv, _1), + true /*flush*/ + ); + + //Give the streamer a functor to handle overruns + //bind requires a weak_ptr to break the a streamer->streamer circular dependency + //Using "this" is OK because we know that this device3_impl will outlive the streamer + my_streamer->set_overflow_handler( + stream_i, + boost::bind( + &uhd::rfnoc::rx_stream_terminator::handle_overrun, recv_terminator, + boost::weak_ptr<uhd::rx_streamer>(my_streamer), stream_i + ) + ); + + //Give the streamer a functor to send flow control messages + //handle_rx_flowctrl is static and has no lifetime issues + boost::shared_ptr<rx_fc_cache_t> fc_cache(new rx_fc_cache_t()); + my_streamer->set_xport_handle_flowctrl( + stream_i, boost::bind( + &handle_rx_flowctrl, + xport.send_sid, + xport.send, + get_transport_endianness(mb_index), + fc_cache, + _1 + ), + fc_handle_window, + true/*init*/ + ); + + //Give the streamer a functor issue stream cmd + //bind requires a shared pointer to add a streamer->framer lifetime dependency + my_streamer->set_issue_stream_cmd( + stream_i, + boost::bind(&uhd::rfnoc::source_block_ctrl_base::issue_stream_cmd, blk_ctrl, _1, block_port) + ); + + // Tell the streamer which SID is valid for this channel + my_streamer->set_xport_chan_sid(stream_i, true, xport.send_sid); + } + + // Connect the terminator to the streamer + my_streamer->set_terminator(recv_terminator); + + // Notify all blocks in this chain that they are connected to an active streamer + recv_terminator->set_rx_streamer(true, 0); + + // Store a weak pointer to prevent a streamer->device3_impl->streamer circular dependency. + // Note that we store the streamer only once, and use its terminator's + // ID to do so. + _rx_streamers[recv_terminator->unique_id()] = boost::weak_ptr<sph::recv_packet_streamer>(my_streamer); + + // Sets tick rate, samp rate and scaling on this streamer. + // A registered terminator is required to do this. + update_rx_streamers(); + + post_streamer_hooks(RX_DIRECTION); + return my_streamer; +} + +/*********************************************************************** + * Transmit streamer + **********************************************************************/ +void device3_impl::update_tx_streamers(double /* rate */) +{ + BOOST_FOREACH(const std::string &block_id, _tx_streamers.keys()) { + UHD_STREAMER_LOG() << "[Device3] updating TX streamer: " << block_id << std::endl; + boost::shared_ptr<sph::send_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::send_packet_streamer>(_tx_streamers[block_id].lock()); + if (my_streamer) { + double tick_rate = my_streamer->get_terminator()->get_tick_rate(); + if (tick_rate == rfnoc::tick_node_ctrl::RATE_UNDEFINED) { + tick_rate = 1.0; + } + double samp_rate = my_streamer->get_terminator()->get_input_samp_rate(); + if (samp_rate == rfnoc::rate_node_ctrl::RATE_UNDEFINED) { + samp_rate = 1.0; + } + double scaling = my_streamer->get_terminator()->get_input_scale_factor(); + if (scaling == rfnoc::scalar_node_ctrl::SCALE_UNDEFINED) { + scaling = 32767.; + } + UHD_STREAMER_LOG() << " New tick_rate == " << tick_rate << " New samp_rate == " << samp_rate << " New scaling == " << scaling << std::endl; + my_streamer->set_tick_rate(tick_rate); + my_streamer->set_samp_rate(samp_rate); + my_streamer->set_scale_factor(scaling); + } + } +} + +tx_streamer::sptr device3_impl::get_tx_stream(const uhd::stream_args_t &args_) +{ + boost::mutex::scoped_lock lock(_transport_setup_mutex); + stream_args_t args = sanitize_stream_args(args_); + + // I. Generate the channel list + std::vector<uhd::rfnoc::block_id_t> chan_list; + std::vector<device_addr_t> chan_args; + generate_channel_list(args, chan_list, chan_args); + // Note: All 'args.args' are merged into chan_args now. + + //shared async queue for all channels in streamer + boost::shared_ptr<async_md_type> async_md(new async_md_type(1000/*messages deep*/)); + + // II. Iterate over all channels + boost::shared_ptr<sph::send_packet_streamer> my_streamer; + // The terminator's lifetime is coupled to the streamer. + // There is only one terminator. If the streamer has multiple channels, + // it will be connected to each downstream block. + rfnoc::tx_stream_terminator::sptr send_terminator = rfnoc::tx_stream_terminator::make(); + for (size_t stream_i = 0; stream_i < chan_list.size(); stream_i++) { + // Get block ID and mb index + uhd::rfnoc::block_id_t block_id = chan_list[stream_i]; + // Update args so args.args is always valid for this particular channel: + args.args = chan_args[stream_i]; + size_t mb_index = block_id.get_device_no(); + size_t suggested_block_port = args.args.cast<size_t>("block_port", rfnoc::ANY_PORT); + + // Access to this channel's block control + uhd::rfnoc::sink_block_ctrl_base::sptr blk_ctrl = + boost::dynamic_pointer_cast<uhd::rfnoc::sink_block_ctrl_base>(get_block_ctrl(block_id)); + + // Connect the terminator with this channel's block. + // This will throw if the connection is not possible. + size_t block_port = blk_ctrl->connect_upstream( + send_terminator, + suggested_block_port, + args.args + ); + const size_t terminator_port = send_terminator->connect_downstream(blk_ctrl); + blk_ctrl->set_upstream_port(block_port, terminator_port); + send_terminator->set_downstream_port(terminator_port, block_port); + + // Check if the block connection is compatible (spp and item type) + check_stream_sig_compatible(blk_ctrl->get_input_signature(block_port), args, "TX"); + + // Setup the dsp transport hints + device_addr_t tx_hints = get_tx_hints(mb_index); + + //allocate sid and create transport + uhd::sid_t stream_address = blk_ctrl->get_address(block_port); + UHD_STREAMER_LOG() << "[TX Streamer] creating tx stream " << tx_hints.to_string() << std::endl; + both_xports_t xport = make_transport(stream_address, TX_DATA, tx_hints); + UHD_STREAMER_LOG() << std::hex << "[TX Streamer] data_sid = " << xport.send_sid << std::dec << std::endl; + + // To calculate the max number of samples per packet, we assume the maximum header length + // to avoid fragmentation should the entire header be used. + const size_t bpp = tx_hints.cast<size_t>("bpp", xport.send->get_send_frame_size()) - stream_options.tx_max_len_hdr; + const size_t bpi = convert::get_bytes_per_item(args.otw_format); // bytes per item + const size_t spp = std::min(args.args.cast<size_t>("spp", bpp/bpi), bpp/bpi); // samples per packet + UHD_STREAMER_LOG() << "[TX Streamer] spp == " << spp << std::endl; + + //make the new streamer given the samples per packet + if (not my_streamer) + my_streamer = boost::make_shared<sph::send_packet_streamer>(spp); + my_streamer->resize(chan_list.size()); + + //init some streamer stuff + std::string conv_endianness; + if (get_transport_endianness(mb_index) == ENDIANNESS_BIG) { + my_streamer->set_vrt_packer(&vrt::chdr::if_hdr_pack_be); + conv_endianness = "be"; + } else { + my_streamer->set_vrt_packer(&vrt::chdr::if_hdr_pack_le); + conv_endianness = "le"; + } + + //set the converter + uhd::convert::id_type id; + id.input_format = args.cpu_format; + id.num_inputs = 1; + id.output_format = args.otw_format + "_item32_" + conv_endianness; + id.num_outputs = 1; + my_streamer->set_converter(id); + + //flow control setup + const size_t pkt_size = spp * bpi + stream_options.tx_max_len_hdr; + // For flow control, this value is used to determine the window size in *packets* + size_t fc_window = get_tx_flow_control_window( + pkt_size, // This is the maximum packet size + blk_ctrl->get_fifo_size(block_port), + tx_hints // This can override the value reported by the block! + ); + const size_t fc_handle_window = std::max<size_t>(1, fc_window / stream_options.tx_fc_response_freq); + UHD_STREAMER_LOG() << "[TX Streamer] Flow Control Window = " << fc_window << ", Flow Control Handler Window = " << fc_handle_window << std::endl; + blk_ctrl->configure_flow_control_in( + stream_options.tx_fc_response_cycles, + fc_handle_window, /*pkts*/ + block_port + ); + + boost::shared_ptr<tx_fc_cache_t> fc_cache(new tx_fc_cache_t()); + fc_cache->stream_channel = stream_i; + fc_cache->device_channel = mb_index; + fc_cache->async_queue = async_md; + fc_cache->old_async_queue = _async_md; + + boost::function<double(void)> tick_rate_retriever = boost::bind( + &rfnoc::tick_node_ctrl::get_tick_rate, + send_terminator, + std::set< rfnoc::node_ctrl_base::sptr >() // Need to specify default args with bind + ); + task::sptr task = task::make( + boost::bind( + &handle_tx_async_msgs, + fc_cache, + xport.recv, + get_transport_endianness(mb_index), + tick_rate_retriever + ) + ); + + blk_ctrl->sr_write(uhd::rfnoc::SR_RESP_IN_DST_SID, xport.recv_sid.get_dst(), block_port); + UHD_STREAMER_LOG() << "[TX Streamer] resp_in_dst_sid == " << boost::format("0x%04X") % xport.recv_sid.get_dst() << std::endl; + // Find all downstream radio nodes and set their response in SID to the host + std::vector<boost::shared_ptr<uhd::rfnoc::radio_ctrl> > downstream_radio_nodes = blk_ctrl->find_downstream_node<uhd::rfnoc::radio_ctrl>(); + UHD_STREAMER_LOG() << "[TX Streamer] Number of downstream radio nodes: " << downstream_radio_nodes.size() << std::endl; + BOOST_FOREACH(const boost::shared_ptr<uhd::rfnoc::radio_ctrl> &node, downstream_radio_nodes) { + node->sr_write(uhd::rfnoc::SR_RESP_IN_DST_SID, xport.send_sid.get_src(), block_port); + } + + //Give the streamer a functor to get the send buffer + //get_tx_buff_with_flowctrl is static so bind has no lifetime issues + //xport.send (sptr) is required to add streamer->data-transport lifetime dependency + //task (sptr) is required to add a streamer->async-handler lifetime dependency + my_streamer->set_xport_chan_get_buff( + stream_i, + boost::bind(&get_tx_buff_with_flowctrl, task, fc_cache, xport.send, fc_window, _1) + ); + //Give the streamer a functor handled received async messages + my_streamer->set_async_receiver( + boost::bind(&async_md_type::pop_with_timed_wait, async_md, _1, _2) + ); + my_streamer->set_xport_chan_sid(stream_i, true, xport.send_sid); + // CHDR does not support trailers + my_streamer->set_enable_trailer(false); + } + + // Connect the terminator to the streamer + my_streamer->set_terminator(send_terminator); + + // Notify all blocks in this chain that they are connected to an active streamer + send_terminator->set_tx_streamer(true, 0); + + // Store a weak pointer to prevent a streamer->device3_impl->streamer circular dependency. + // Note that we store the streamer only once, and use its terminator's + // ID to do so. + _tx_streamers[send_terminator->unique_id()] = boost::weak_ptr<sph::send_packet_streamer>(my_streamer); + + // Sets tick rate, samp rate and scaling on this streamer + // A registered terminator is required to do this. + update_tx_streamers(); + + post_streamer_hooks(TX_DIRECTION); + return my_streamer; +} + + diff --git a/host/lib/usrp/multi_usrp.cpp b/host/lib/usrp/multi_usrp.cpp index 0c3c78fc6..cb1ba8784 100644 --- a/host/lib/usrp/multi_usrp.cpp +++ b/host/lib/usrp/multi_usrp.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2013 Ettus Research LLC +// Copyright 2010-2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -27,6 +27,7 @@ #include <uhd/usrp/dboard_eeprom.hpp> #include <uhd/convert.hpp> #include <uhd/utils/soft_register.hpp> +#include "legacy_compat.hpp" #include <boost/assign/list_of.hpp> #include <boost/thread.hpp> #include <boost/foreach.hpp> @@ -390,12 +391,28 @@ public: multi_usrp_impl(const device_addr_t &addr){ _dev = device::make(addr, device::USRP); _tree = _dev->get_tree(); + _is_device3 = bool(boost::dynamic_pointer_cast<uhd::device3>(_dev)); + + if (is_device3()) { + _legacy_compat = rfnoc::legacy_compat::make(get_device3(), addr); + } } device::sptr get_device(void){ return _dev; } + bool is_device3(void) { + return _is_device3; + } + + device3::sptr get_device3(void) { + if (not is_device3()) { + throw uhd::type_error("Cannot call get_device3() on a non-generation 3 device."); + } + return boost::dynamic_pointer_cast<uhd::device3>(_dev); + } + dict<std::string, std::string> get_usrp_rx_info(size_t chan){ mboard_chan_pair mcp = rx_chan_to_mcp(chan); dict<std::string, std::string> usrp_info; @@ -608,7 +625,12 @@ public: void issue_stream_cmd(const stream_cmd_t &stream_cmd, size_t chan){ if (chan != ALL_CHANS){ - _tree->access<stream_cmd_t>(rx_dsp_root(chan) / "stream_cmd").set(stream_cmd); + if (is_device3()) { + mboard_chan_pair mcp = rx_chan_to_mcp(chan); + _legacy_compat->issue_stream_cmd(stream_cmd, mcp.mboard, mcp.chan); + } else { + _tree->access<stream_cmd_t>(rx_dsp_root(chan) / "stream_cmd").set(stream_cmd); + } return; } for (size_t c = 0; c < get_rx_num_channels(); c++){ @@ -743,6 +765,9 @@ public: ******************************************************************/ rx_streamer::sptr get_rx_stream(const stream_args_t &args) { _check_link_rate(args, false); + if (is_device3()) { + return _legacy_compat->get_rx_stream(args); + } return this->get_device()->get_rx_stream(args); } @@ -809,28 +834,10 @@ public: } tune_result_t set_rx_freq(const tune_request_t &tune_request, size_t chan){ - tune_request_t local_request = tune_request; - - // If any mixer is driven by an external LO the daughterboard assumes that no CORDIC correction is - // necessary. Since the LO might be sourced from another daughterboard which would normally apply a - // cordic correction we must disable all DSP tuning to ensure identical configurations across daughterboards. - if (tune_request.dsp_freq_policy == tune_request.POLICY_AUTO and - tune_request.rf_freq_policy == tune_request.POLICY_AUTO) - { - for (size_t c = 0; c < get_rx_num_channels(); c++) { - UHD_VAR(get_rx_lo_source(ALL_LOS, c)); - if (get_rx_lo_source(ALL_LOS, c) == "external") { - local_request.dsp_freq_policy = tune_request.POLICY_MANUAL; - local_request.dsp_freq = 0; - break; - } - } - } - tune_result_t result = tune_xx_subdev_and_dsp(RX_SIGN, _tree->subtree(rx_dsp_root(chan)), _tree->subtree(rx_rf_fe_root(chan)), - local_request); + tune_request); //do_tune_freq_results_message(tune_request, result, get_rx_freq(chan), "RX"); return result; } @@ -920,7 +927,7 @@ public: } } else { // If the daughterboard doesn't expose it's LO(s) then it can only be internal - return std::vector<std::string> {1, "internal"}; + return std::vector<std::string>(1, "internal"); } } @@ -1286,6 +1293,9 @@ public: ******************************************************************/ tx_streamer::sptr get_tx_stream(const stream_args_t &args) { _check_link_rate(args, true); + if (is_device3()) { + return _legacy_compat->get_tx_stream(args); + } return this->get_device()->get_tx_stream(args); } @@ -1680,6 +1690,8 @@ public: private: device::sptr _dev; property_tree::sptr _tree; + bool _is_device3; + uhd::rfnoc::legacy_compat::sptr _legacy_compat; struct mboard_chan_pair{ size_t mboard, chan; @@ -1732,6 +1744,10 @@ private: fs_path rx_dsp_root(const size_t chan) { mboard_chan_pair mcp = rx_chan_to_mcp(chan); + if (is_device3()) { + return _legacy_compat->rx_dsp_root(mcp.mboard, mcp.chan); + } + if (_tree->exists(mb_root(mcp.mboard) / "rx_chan_dsp_mapping")) { std::vector<size_t> map = _tree->access<std::vector<size_t> >(mb_root(mcp.mboard) / "rx_chan_dsp_mapping").get(); UHD_ASSERT_THROW(map.size() > mcp.chan); @@ -1752,6 +1768,10 @@ private: fs_path tx_dsp_root(const size_t chan) { mboard_chan_pair mcp = tx_chan_to_mcp(chan); + if (is_device3()) { + return _legacy_compat->tx_dsp_root(mcp.mboard, mcp.chan); + } + if (_tree->exists(mb_root(mcp.mboard) / "tx_chan_dsp_mapping")) { std::vector<size_t> map = _tree->access<std::vector<size_t> >(mb_root(mcp.mboard) / "tx_chan_dsp_mapping").get(); UHD_ASSERT_THROW(map.size() > mcp.chan); @@ -1771,6 +1791,9 @@ private: fs_path rx_fe_root(const size_t chan) { mboard_chan_pair mcp = rx_chan_to_mcp(chan); + if (is_device3()) { + return _legacy_compat->rx_fe_root(mcp.mboard, mcp.chan); + } try { const subdev_spec_pair_t spec = get_rx_subdev_spec(mcp.mboard).at(mcp.chan); @@ -1785,6 +1808,9 @@ private: fs_path tx_fe_root(const size_t chan) { mboard_chan_pair mcp = tx_chan_to_mcp(chan); + if (is_device3()) { + return _legacy_compat->tx_fe_root(mcp.mboard, mcp.chan); + } try { const subdev_spec_pair_t spec = get_tx_subdev_spec(mcp.mboard).at(mcp.chan); diff --git a/host/lib/usrp/x300/CMakeLists.txt b/host/lib/usrp/x300/CMakeLists.txt index f8b129f89..ea237b008 100644 --- a/host/lib/usrp/x300/CMakeLists.txt +++ b/host/lib/usrp/x300/CMakeLists.txt @@ -24,6 +24,7 @@ ######################################################################## IF(ENABLE_X300) LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/x300_radio_ctrl_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_fw_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_fw_uart.cpp @@ -33,7 +34,6 @@ IF(ENABLE_X300) ${CMAKE_CURRENT_SOURCE_DIR}/x300_dboard_iface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_clock_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_image_loader.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/x300_adc_dac_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cdecode.c ) ENDIF(ENABLE_X300) diff --git a/host/lib/usrp/x300/x300_dac_ctrl.cpp b/host/lib/usrp/x300/x300_dac_ctrl.cpp index d49fba383..6ffd1ede4 100644 --- a/host/lib/usrp/x300/x300_dac_ctrl.cpp +++ b/host/lib/usrp/x300/x300_dac_ctrl.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2014 Ettus Research LLC +// Copyright 2010-2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by diff --git a/host/lib/usrp/x300/x300_dac_ctrl.hpp b/host/lib/usrp/x300/x300_dac_ctrl.hpp index f2a407971..ca47a90e7 100644 --- a/host/lib/usrp/x300/x300_dac_ctrl.hpp +++ b/host/lib/usrp/x300/x300_dac_ctrl.hpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2014 Ettus Research LLC +// Copyright 2010-2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by diff --git a/host/lib/usrp/x300/x300_dboard_iface.cpp b/host/lib/usrp/x300/x300_dboard_iface.cpp index 87f537874..b28768f90 100644 --- a/host/lib/usrp/x300/x300_dboard_iface.cpp +++ b/host/lib/usrp/x300/x300_dboard_iface.cpp @@ -15,92 +15,16 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. // -#include "x300_impl.hpp" +#include "x300_dboard_iface.hpp" #include "x300_regs.hpp" -#include <uhd/usrp/dboard_iface.hpp> #include <uhd/utils/safe_call.hpp> #include <boost/assign/list_of.hpp> #include <boost/math/special_functions/round.hpp> -#include "ad7922_regs.hpp" //aux adc -#include "ad5623_regs.hpp" //aux dac using namespace uhd; using namespace uhd::usrp; using namespace boost::assign; -class x300_dboard_iface : public dboard_iface -{ -public: - x300_dboard_iface(const x300_dboard_iface_config_t &config); - ~x300_dboard_iface(void); - - special_props_t get_special_props(void) - { - special_props_t props; - props.soft_clock_divider = false; - props.mangle_i2c_addrs = (_config.dboard_slot == 1); - return props; - } - - void write_aux_dac(unit_t, aux_dac_t, double); - double read_aux_adc(unit_t, aux_adc_t); - - void set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); - boost::uint32_t get_pin_ctrl(unit_t unit); - void set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); - boost::uint32_t get_atr_reg(unit_t unit, atr_reg_t reg); - void set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); - boost::uint32_t get_gpio_ddr(unit_t unit); - void set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); - boost::uint32_t get_gpio_out(unit_t unit); - boost::uint32_t read_gpio(unit_t unit); - - void set_command_time(const uhd::time_spec_t& t); - uhd::time_spec_t get_command_time(void); - - void write_i2c(boost::uint16_t, const byte_vector_t &); - byte_vector_t read_i2c(boost::uint16_t, size_t); - - void set_clock_rate(unit_t, double); - double get_clock_rate(unit_t); - std::vector<double> get_clock_rates(unit_t); - void set_clock_enabled(unit_t, bool); - double get_codec_rate(unit_t); - - void write_spi( - unit_t unit, - const spi_config_t &config, - boost::uint32_t data, - size_t num_bits - ); - - boost::uint32_t read_write_spi( - unit_t unit, - const spi_config_t &config, - boost::uint32_t data, - size_t num_bits - ); - void set_fe_connection( - unit_t unit, - const std::string& fe_name, - const fe_connection_t& fe_conn - ); - - const x300_dboard_iface_config_t _config; - uhd::dict<unit_t, ad5623_regs_t> _dac_regs; - uhd::dict<unit_t, double> _clock_rates; - void _write_aux_dac(unit_t); - -}; - -/*********************************************************************** - * Make Function - **********************************************************************/ -dboard_iface::sptr x300_make_dboard_iface(const x300_dboard_iface_config_t &config) -{ - return dboard_iface::sptr(new x300_dboard_iface(config)); -} - /*********************************************************************** * Structors **********************************************************************/ @@ -375,12 +299,23 @@ void x300_dboard_iface::set_command_time(const uhd::time_spec_t& t) _config.cmd_time_ctrl->set_time(t); } +void x300_dboard_iface::add_rx_fe( + const std::string& fe_name, + rx_frontend_core_3000::sptr fe_core) +{ + _rx_fes[fe_name] = fe_core; +} + void x300_dboard_iface::set_fe_connection( - unit_t unit, const std::string& /*fe_name*/, + unit_t unit, const std::string& fe_name, const fe_connection_t& fe_conn) { if (unit == UNIT_RX) { - _config.rx_dsp->set_mux(fe_conn); + if (_rx_fes.has_key(fe_name)) { + _rx_fes[fe_name]->set_fe_connection(fe_conn); + } else { + throw uhd::assertion_error("front-end name was not registered: " + fe_name); + } } else { throw uhd::not_implemented_error("frontend connection not configurable for TX"); } diff --git a/host/lib/usrp/x300/x300_dboard_iface.hpp b/host/lib/usrp/x300/x300_dboard_iface.hpp new file mode 100644 index 000000000..124d768e8 --- /dev/null +++ b/host/lib/usrp/x300/x300_dboard_iface.hpp @@ -0,0 +1,115 @@ +// +// Copyright 2010-2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_X300_DBOARD_IFACE_HPP +#define INCLUDED_X300_DBOARD_IFACE_HPP + +#include "x300_clock_ctrl.hpp" +#include "spi_core_3000.hpp" +#include "i2c_core_100_wb32.hpp" +#include "gpio_atr_3000.hpp" +#include "rx_frontend_core_3000.hpp" +#include <uhd/usrp/dboard_iface.hpp> +#include "ad7922_regs.hpp" //aux adc +#include "ad5623_regs.hpp" //aux dac +#include <uhd/types/dict.hpp> + +struct x300_dboard_iface_config_t +{ + uhd::usrp::gpio_atr::db_gpio_atr_3000::sptr gpio; + spi_core_3000::sptr spi; + size_t rx_spi_slaveno; + size_t tx_spi_slaveno; + uhd::i2c_iface::sptr i2c; + x300_clock_ctrl::sptr clock; + x300_clock_which_t which_rx_clk; + x300_clock_which_t which_tx_clk; + boost::uint8_t dboard_slot; + uhd::timed_wb_iface::sptr cmd_time_ctrl; +}; + +class x300_dboard_iface : public uhd::usrp::dboard_iface +{ +public: + x300_dboard_iface(const x300_dboard_iface_config_t &config); + ~x300_dboard_iface(void); + + inline special_props_t get_special_props(void) + { + special_props_t props; + props.soft_clock_divider = false; + props.mangle_i2c_addrs = (_config.dboard_slot == 1); + return props; + } + + void write_aux_dac(unit_t, aux_dac_t, double); + double read_aux_adc(unit_t, aux_adc_t); + + void set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_pin_ctrl(unit_t unit); + void set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_atr_reg(unit_t unit, atr_reg_t reg); + void set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_ddr(unit_t unit); + void set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_out(unit_t unit); + boost::uint32_t read_gpio(unit_t unit); + + void set_command_time(const uhd::time_spec_t& t); + uhd::time_spec_t get_command_time(void); + + void write_i2c(boost::uint16_t, const uhd::byte_vector_t &); + uhd::byte_vector_t read_i2c(boost::uint16_t, size_t); + + void set_clock_rate(unit_t, double); + double get_clock_rate(unit_t); + std::vector<double> get_clock_rates(unit_t); + void set_clock_enabled(unit_t, bool); + double get_codec_rate(unit_t); + + void write_spi( + unit_t unit, + const uhd::spi_config_t &config, + boost::uint32_t data, + size_t num_bits + ); + + boost::uint32_t read_write_spi( + unit_t unit, + const uhd::spi_config_t &config, + boost::uint32_t data, + size_t num_bits + ); + void set_fe_connection( + unit_t unit, const std::string& name, + const uhd::usrp::fe_connection_t& fe_conn); + + void add_rx_fe( + const std::string& fe_name, + rx_frontend_core_3000::sptr fe_core); + +private: + const x300_dboard_iface_config_t _config; + uhd::dict<unit_t, ad5623_regs_t> _dac_regs; + uhd::dict<unit_t, double> _clock_rates; + uhd::dict<std::string, rx_frontend_core_3000::sptr> _rx_fes; + void _write_aux_dac(unit_t); +}; + + + +#endif /* INCLUDED_X300_DBOARD_IFACE_HPP */ diff --git a/host/lib/usrp/x300/x300_fw_common.h b/host/lib/usrp/x300/x300_fw_common.h index 6039ee376..e05cd9ec7 100644 --- a/host/lib/usrp/x300/x300_fw_common.h +++ b/host/lib/usrp/x300/x300_fw_common.h @@ -33,7 +33,7 @@ extern "C" { #define X300_REVISION_MIN 2 #define X300_FW_COMPAT_MAJOR 4 #define X300_FW_COMPAT_MINOR 0 -#define X300_FPGA_COMPAT_MAJOR 20 +#define X300_FPGA_COMPAT_MAJOR 0x20 //shared memory sections - in between the stack and the program space #define X300_FW_SHMEM_BASE 0x6000 diff --git a/host/lib/usrp/x300/x300_impl.cpp b/host/lib/usrp/x300/x300_impl.cpp index b59b920ab..ce257947e 100644 --- a/host/lib/usrp/x300/x300_impl.cpp +++ b/host/lib/usrp/x300/x300_impl.cpp @@ -1,5 +1,5 @@ // -// Copyright 2013-2015 Ettus Research LLC +// Copyright 2013-2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -47,24 +47,16 @@ using namespace uhd; using namespace uhd::usrp; +using namespace uhd::rfnoc; using namespace uhd::transport; using namespace uhd::niusrprio; using namespace uhd::usrp::gpio_atr; using namespace uhd::usrp::x300; namespace asio = boost::asio; -static bool has_dram_buff(wb_iface::sptr zpu_ctrl) { - bool dramR0 = dma_fifo_core_3000::check( - zpu_ctrl, SR_ADDR(SET0_BASE, ZPU_SR_DRAM_FIFO0), SR_ADDR(SET0_BASE, ZPU_RB_DRAM_FIFO0)); - bool dramR1 = dma_fifo_core_3000::check( - zpu_ctrl, SR_ADDR(SET0_BASE, ZPU_SR_DRAM_FIFO1), SR_ADDR(SET0_BASE, ZPU_RB_DRAM_FIFO1)); - return (dramR0 and dramR1); -} - static std::string get_fpga_option(wb_iface::sptr zpu_ctrl) { //Possible options: //1G = {0:1G, 1:1G} w/ DRAM, HG = {0:1G, 1:10G} w/ DRAM, XG = {0:10G, 1:10G} w/ DRAM - //1GS = {0:1G, 1:1G} w/ SRAM, HGS = {0:1G, 1:10G} w/ SRAM, XGS = {0:10G, 1:10G} w/ SRAM //HA = {0:1G, 1:Aurora} w/ DRAM, XA = {0:10G, 1:Aurora} w/ DRAM std::string option; @@ -84,9 +76,6 @@ static std::string get_fpga_option(wb_iface::sptr zpu_ctrl) { } else { option = "HG"; //Default } - if (not has_dram_buff(zpu_ctrl)) { - option += "S"; - } return option; } @@ -250,7 +239,7 @@ static device_addrs_t x300_find_pcie(const device_addr_t &hint, bool explicit_qu //set these values as empty string so the device may still be found //and the filter's below can still operate on the discovered device if (not hint.has_key("fpga")) { - new_addr["fpga"] = "HGS"; + new_addr["fpga"] = "HG"; } new_addr["name"] = ""; new_addr["serial"] = ""; @@ -388,15 +377,13 @@ static void x300_load_fw(wb_iface::sptr fw_reg_ctrl, const std::string &file_nam UHD_MSG(status) << " done!" << std::endl; } -x300_impl::x300_impl(const uhd::device_addr_t &dev_addr) +x300_impl::x300_impl(const uhd::device_addr_t &dev_addr) + : device3_impl() + , _sid_framer(0) { UHD_MSG(status) << "X300 initialization sequence..." << std::endl; - _type = device::USRP; _ignore_cal_file = dev_addr.has_key("ignore-cal-file"); - _async_md.reset(new async_md_type(1000/*messages deep*/)); - _tree = uhd::property_tree::make(); _tree->create<std::string>("/name").set("X-Series Device"); - _sid_framer = 0; const device_addrs_t device_args = separate_device_addr(dev_addr); _mb.resize(device_args.size()); @@ -519,6 +506,7 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) std::string eth0_addr = dev_addr.has_key("resource") ? dev_addr["resource"] : dev_addr["addr"]; eth_addrs.push_back(eth0_addr); + mb.next_src_addr = 0; //Host source address for blocks if (dev_addr.has_key("second_addr")) { std::string eth1_addr = dev_addr["second_addr"]; @@ -570,6 +558,8 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) const boost::uint32_t tx_data_fifos[2] = {X300_RADIO_DEST_PREFIX_TX, X300_RADIO_DEST_PREFIX_TX + 3}; mb.rio_fpga_interface->get_kernel_proxy()->get_rio_quirks().register_tx_streams(tx_data_fifos, 2); + _tree->create<size_t>(mb_path / "mtu/recv").set(X300_PCIE_RX_DATA_FRAME_SIZE); + _tree->create<size_t>(mb_path / "mtu/send").set(X300_PCIE_TX_DATA_FRAME_SIZE); _tree->create<double>(mb_path / "link_max_rate").set(X300_MAX_RATE_PCIE); } @@ -657,6 +647,8 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) << std::endl; } + _tree->create<size_t>(mb_path / "mtu/recv").set(_max_frame_sizes.recv_frame_size); + _tree->create<size_t>(mb_path / "mtu/send").set(_max_frame_sizes.send_frame_size); _tree->create<double>(mb_path / "link_max_rate").set(X300_MAX_RATE_10GIGE); } @@ -777,15 +769,6 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) } //////////////////////////////////////////////////////////////////// - // read dboard eeproms - //////////////////////////////////////////////////////////////////// - for (size_t i = 0; i < 8; i++) - { - if (i == 0 or i == 2) continue; //not used - mb.db_eeproms[i].load(*mb.zpu_i2c, 0x50 | i); - } - - //////////////////////////////////////////////////////////////////// // read hardware revision and compatibility number //////////////////////////////////////////////////////////////////// mb.hw_rev = 0; @@ -847,15 +830,14 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) //Initialize clock source to use internal reference and generate //a valid radio clock. This may change after configuration is done. //This will configure the LMK and wait for lock - update_clock_source(mb, "internal"); + update_clock_source(mb, X300_DEFAULT_CLOCK_SOURCE); //////////////////////////////////////////////////////////////////// // create clock properties //////////////////////////////////////////////////////////////////// - _tree->create<double>(mb_path / "tick_rate") - .set_publisher(boost::bind(&x300_clock_ctrl::get_master_clock_rate, mb.clock)); - - _tree->create<time_spec_t>(mb_path / "time" / "cmd"); + _tree->create<double>(mb_path / "master_clock_rate") + .set_publisher(boost::bind(&x300_clock_ctrl::get_master_clock_rate, mb.clock)) + ; UHD_MSG(status) << "Radio 1x clock:" << (mb.clock->get_master_clock_rate()/1e6) << std::endl; @@ -898,78 +880,6 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) mb.zpu_ctrl->poke32(SR_ADDR(SETXB_BASE, i), 0); } - //////////////////////////////////////////////////////////////////// - // DRAM FIFO initialization - //////////////////////////////////////////////////////////////////// - mb.has_dram_buff = has_dram_buff(mb.zpu_ctrl); - if (mb.has_dram_buff) { - for (size_t i = 0; i < mboard_members_t::NUM_RADIOS; i++) { - static const size_t NUM_REGS = 8; - mb.dram_buff_ctrl[i] = dma_fifo_core_3000::make( - mb.zpu_ctrl, - SR_ADDR(SET0_BASE, ZPU_SR_DRAM_FIFO0+(i*NUM_REGS)), - SR_ADDR(SET0_BASE, ZPU_RB_DRAM_FIFO0+i)); - mb.dram_buff_ctrl[i]->resize(X300_DRAM_FIFO_SIZE * i, X300_DRAM_FIFO_SIZE); - - if (mb.dram_buff_ctrl[i]->ext_bist_supported()) { - UHD_MSG(status) << boost::format("Running BIST for DRAM FIFO %d... ") % i; - boost::uint32_t bisterr = mb.dram_buff_ctrl[i]->run_bist(); - if (bisterr != 0) { - throw uhd::runtime_error(str(boost::format("DRAM FIFO BIST failed! (code: %d)\n") % bisterr)); - } else { - double throughput = mb.dram_buff_ctrl[i]->get_bist_throughput(X300_BUS_CLOCK_RATE); - UHD_MSG(status) << (boost::format("pass (Throughput: %.1fMB/s)") % (throughput/1e6)) << std::endl; - } - } else { - if (mb.dram_buff_ctrl[i]->run_bist() != 0) { - throw uhd::runtime_error(str(boost::format("DRAM FIFO %d BIST failed!\n") % i)); - } - } - } - } - - //////////////////////////////////////////////////////////////////// - // setup radios - //////////////////////////////////////////////////////////////////// - this->setup_radio(mb_i, "A", dev_addr); - this->setup_radio(mb_i, "B", dev_addr); - - //////////////////////////////////////////////////////////////////// - // ADC test and cal - //////////////////////////////////////////////////////////////////// - if (dev_addr.has_key("self_cal_adc_delay")) { - self_cal_adc_xfer_delay(mb, true /* Apply ADC delay */); - } - if (dev_addr.has_key("ext_adc_self_test")) { - extended_adc_test(mb, dev_addr.cast<double>("ext_adc_self_test", 30)); - } else if ( ! dev_addr.has_key("disable_adc_self_test") ) { - self_test_adcs(mb); - } - - //////////////////////////////////////////////////////////////////// - // front panel gpio - //////////////////////////////////////////////////////////////////// - mb.fp_gpio = gpio_atr_3000::make(mb.radio_perifs[0].ctrl, radio::sr_addr(radio::FP_GPIO), radio::RB32_FP_GPIO); - BOOST_FOREACH(const gpio_attr_map_t::value_type attr, gpio_attr_map) - { - _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / attr.second) - .set(0) - .add_coerced_subscriber(boost::bind(&gpio_atr_3000::set_gpio_attr, mb.fp_gpio, attr.first, _1)); - } - _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "READBACK") - .set_publisher(boost::bind(&gpio_atr_3000::read_gpio, mb.fp_gpio)); - - //////////////////////////////////////////////////////////////////// - // register the time keepers - only one can be the highlander - //////////////////////////////////////////////////////////////////// - _tree->create<time_spec_t>(mb_path / "time" / "now") - .set_publisher(boost::bind(&time_core_3000::get_time_now, mb.radio_perifs[0].time64)) - .add_coerced_subscriber(boost::bind(&x300_impl::sync_times, this, mb, _1)) - .set(0.0); - _tree->create<time_spec_t>(mb_path / "time" / "pps") - .set_publisher(boost::bind(&time_core_3000::get_time_last_pps, mb.radio_perifs[0].time64)) - .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_next_pps, mb.radio_perifs[0].time64, _1)) - .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_next_pps, mb.radio_perifs[1].time64, _1)); //////////////////////////////////////////////////////////////////// // setup time sources and properties @@ -989,7 +899,7 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) // setup clock sources and properties //////////////////////////////////////////////////////////////////// _tree->create<std::string>(mb_path / "clock_source" / "value") - .set("internal") + .set(X300_DEFAULT_CLOCK_SOURCE) .add_coerced_subscriber(boost::bind(&x300_impl::update_clock_source, this, boost::ref(mb), _1)); static const std::vector<std::string> clock_source_options = boost::assign::list_of("internal")("external")("gpsdo"); @@ -1009,21 +919,11 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) .add_coerced_subscriber(boost::bind(&x300_clock_ctrl::set_ref_out, mb.clock, _1)); //initialize tick rate (must be done before setting time) - _tree->access<double>(mb_path / "tick_rate") - .add_coerced_subscriber(boost::bind(&x300_impl::set_tick_rate, this, boost::ref(mb), _1)) - .add_coerced_subscriber(boost::bind(&x300_impl::update_tick_rate, this, boost::ref(mb), _1)) - .set(mb.clock->get_master_clock_rate()); - - //////////////////////////////////////////////////////////////////// - // create frontend mapping - //////////////////////////////////////////////////////////////////// - std::vector<size_t> default_map(2, 0); default_map[1] = 1; - _tree->create<std::vector<size_t> >(mb_path / "rx_chan_dsp_mapping").set(default_map); - _tree->create<std::vector<size_t> >(mb_path / "tx_chan_dsp_mapping").set(default_map); - _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") - .add_coerced_subscriber(boost::bind(&x300_impl::update_subdev_spec, this, "rx", mb_i, _1)); - _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") - .add_coerced_subscriber(boost::bind(&x300_impl::update_subdev_spec, this, "tx", mb_i, _1)); + _tree->create<double>(mb_path / "tick_rate") + .add_coerced_subscriber(boost::bind(&device3_impl::update_tx_streamers, this, _1)) + .add_coerced_subscriber(boost::bind(&device3_impl::update_rx_streamers, this, _1)) + .set(mb.clock->get_master_clock_rate()) + ; //////////////////////////////////////////////////////////////////// // and do the misc mboard sensors @@ -1031,29 +931,55 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) _tree->create<sensor_value_t>(mb_path / "sensors" / "ref_locked") .set_publisher(boost::bind(&x300_impl::get_ref_locked, this, mb)); - //////////////////////////////////////////////////////////////////// - // do some post-init tasks - //////////////////////////////////////////////////////////////////// - subdev_spec_t rx_fe_spec, tx_fe_spec; - rx_fe_spec.push_back(subdev_spec_pair_t("A", - _tree->list(mb_path / "dboards" / "A" / "rx_frontends").at(0))); - rx_fe_spec.push_back(subdev_spec_pair_t("B", - _tree->list(mb_path / "dboards" / "B" / "rx_frontends").at(0))); - tx_fe_spec.push_back(subdev_spec_pair_t("A", - _tree->list(mb_path / "dboards" / "A" / "tx_frontends").at(0))); - tx_fe_spec.push_back(subdev_spec_pair_t("B", - _tree->list(mb_path / "dboards" / "B" / "tx_frontends").at(0))); - - _tree->access<subdev_spec_t>(mb_path / "rx_subdev_spec").set(rx_fe_spec); - _tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(tx_fe_spec); - - mb.regmap_db = boost::make_shared<uhd::soft_regmap_db_t>(); - mb.regmap_db->add(*mb.fw_regmap); - mb.regmap_db->add(*mb.radio_perifs[0].regmap); - mb.regmap_db->add(*mb.radio_perifs[1].regmap); - - _tree->create<uhd::soft_regmap_accessor_t::sptr>(mb_path / "registers") - .set(mb.regmap_db); + //////////////// RFNOC ///////////////// + const size_t n_rfnoc_blocks = mb.zpu_ctrl->peek32(SR_ADDR(SET0_BASE, ZPU_RB_NUM_CE)); + enumerate_rfnoc_blocks( + mb_i, + n_rfnoc_blocks, + X300_XB_DST_PCI + 1, /* base port */ + uhd::sid_t(X300_SRC_ADDR0, 0, X300_DST_ADDR + mb_i, 0), + dev_addr, + mb.if_pkt_is_big_endian ? ENDIANNESS_BIG : ENDIANNESS_LITTLE + ); + //////////////// RFNOC ///////////////// + + // If we have a radio, we must configure its codec control: + const std::string radio_blockid_hint = str(boost::format("%d/Radio") % mb_i); + std::vector<rfnoc::block_id_t> radio_ids = + find_blocks<rfnoc::x300_radio_ctrl_impl>(radio_blockid_hint); + if (not radio_ids.empty()) { + if (radio_ids.size() > 2) { + UHD_MSG(warning) << "Too many Radio Blocks found. Using only the first two." << std::endl; + radio_ids.resize(2); + } + + BOOST_FOREACH(const rfnoc::block_id_t &id, radio_ids) { + rfnoc::x300_radio_ctrl_impl::sptr radio(get_block_ctrl<rfnoc::x300_radio_ctrl_impl>(id)); + mb.radios.push_back(radio); + radio->setup_radio(mb.zpu_i2c, mb.clock, dev_addr.has_key("self_cal_adc_delay")); + } + + //////////////////////////////////////////////////////////////////// + // ADC test and cal + //////////////////////////////////////////////////////////////////// + if (dev_addr.has_key("self_cal_adc_delay")) { + rfnoc::x300_radio_ctrl_impl::self_cal_adc_xfer_delay( + mb.radios, mb.clock, + boost::bind(&x300_impl::wait_for_clk_locked, this, mb, fw_regmap_t::clk_status_reg_t::LMK_LOCK, _1), + true /* Apply ADC delay */); + } + if (dev_addr.has_key("ext_adc_self_test")) { + rfnoc::x300_radio_ctrl_impl::extended_adc_test( + mb.radios, + dev_addr.cast<double>("ext_adc_self_test", 30)); + } else if (not dev_addr.has_key("recover_mb_eeprom")){ + for (size_t i = 0; i < mb.radios.size(); i++) { + mb.radios.at(i)->self_test_adc(); + } + } + } else { + UHD_MSG(status) << "No Radio Block found. Assuming radio-less operation." << std::endl; + } mb.initialization_done = true; } @@ -1064,14 +990,6 @@ x300_impl::~x300_impl(void) { BOOST_FOREACH(mboard_members_t &mb, _mb) { - //Disable/reset ADC/DAC - mb.radio_perifs[0].regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1); - mb.radio_perifs[0].regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0); - mb.radio_perifs[0].regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 0); - mb.radio_perifs[0].regmap->misc_outs_reg.flush(); - mb.radio_perifs[1].regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 0); - mb.radio_perifs[1].regmap->misc_outs_reg.flush(); - //kill the claimer task and unclaim the device mb.claimer_task.reset(); { //Critical section @@ -1090,268 +1008,117 @@ x300_impl::~x300_impl(void) } } -void x300_impl::setup_radio(const size_t mb_i, const std::string &slot_name, const uhd::device_addr_t &dev_addr) +uint32_t x300_impl::allocate_pcie_dma_chan(const uhd::sid_t &tx_sid, const xport_type_t xport_type) { - const fs_path mb_path = "/mboards/"+boost::lexical_cast<std::string>(mb_i); - UHD_ASSERT_THROW(mb_i < _mb.size()); - mboard_members_t &mb = _mb[mb_i]; - const size_t radio_index = mb.get_radio_index(slot_name); - radio_perifs_t &perif = mb.radio_perifs[radio_index]; - - UHD_MSG(status) << boost::format("Initialize Radio%d control...") % radio_index << std::endl; - - //////////////////////////////////////////////////////////////////// - // radio control - //////////////////////////////////////////////////////////////////// - boost::uint8_t dest = (radio_index == 0)? X300_XB_DST_R0 : X300_XB_DST_R1; - boost::uint32_t ctrl_sid; - both_xports_t xport = this->make_transport(mb_i, dest, X300_RADIO_DEST_PREFIX_CTRL, device_addr_t(), ctrl_sid); - perif.ctrl = radio_ctrl_core_3000::make(mb.if_pkt_is_big_endian, xport.recv, xport.send, ctrl_sid, slot_name); - - perif.regmap = boost::make_shared<radio_regmap_t>(radio_index); - perif.regmap->initialize(*perif.ctrl, true); - - //Only Radio0 has the ADC/DAC reset bits. Those bits are reserved for Radio1 - if (radio_index == 0) { - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1); - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0); - perif.regmap->misc_outs_reg.flush(); - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 0); - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 1); - perif.regmap->misc_outs_reg.flush(); - } - perif.regmap->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 1); - - this->register_loopback_self_test(perif.ctrl); - - //////////////////////////////////////////////////////////////// - // Setup peripherals - //////////////////////////////////////////////////////////////// - perif.spi = spi_core_3000::make(perif.ctrl, radio::sr_addr(radio::SPI), radio::RB32_SPI); - perif.adc = x300_adc_ctrl::make(perif.spi, DB_ADC_SEN); - perif.dac = x300_dac_ctrl::make(perif.spi, DB_DAC_SEN, mb.clock->get_master_clock_rate()); - perif.leds = gpio_atr_3000::make_write_only(perif.ctrl, radio::sr_addr(radio::LEDS)); - perif.leds->set_atr_mode(MODE_ATR, 0xFFFFFFFF); - perif.rx_fe = rx_frontend_core_200::make(perif.ctrl, radio::sr_addr(radio::RX_FRONT)); - perif.rx_fe->set_dc_offset(rx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); - perif.rx_fe->set_dc_offset_auto(rx_frontend_core_200::DEFAULT_DC_OFFSET_ENABLE); - perif.tx_fe = tx_frontend_core_200::make(perif.ctrl, radio::sr_addr(radio::TX_FRONT)); - perif.tx_fe->set_dc_offset(tx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); - perif.tx_fe->set_iq_balance(tx_frontend_core_200::DEFAULT_IQ_BALANCE_VALUE); - perif.framer = rx_vita_core_3000::make(perif.ctrl, radio::sr_addr(radio::RX_CTRL)); - perif.ddc = rx_dsp_core_3000::make(perif.ctrl, radio::sr_addr(radio::RX_DSP)); - perif.ddc->set_link_rate(10e9/8); //whatever - perif.ddc->set_tick_rate(mb.clock->get_master_clock_rate()); - //The DRAM FIFO is treated as in internal radio FIFO for flow control purposes - tx_vita_core_3000::fc_monitor_loc fc_loc = - mb.has_dram_buff ? tx_vita_core_3000::FC_PRE_FIFO : tx_vita_core_3000::FC_PRE_RADIO; - perif.deframer = tx_vita_core_3000::make(perif.ctrl, radio::sr_addr(radio::TX_CTRL), fc_loc); - perif.duc = tx_dsp_core_3000::make(perif.ctrl, radio::sr_addr(radio::TX_DSP)); - perif.duc->set_link_rate(10e9/8); //whatever - perif.duc->set_tick_rate(mb.clock->get_master_clock_rate()); - - //////////////////////////////////////////////////////////////////// - // create time control objects - //////////////////////////////////////////////////////////////////// - time_core_3000::readback_bases_type time64_rb_bases; - time64_rb_bases.rb_now = radio::RB64_TIME_NOW; - time64_rb_bases.rb_pps = radio::RB64_TIME_PPS; - perif.time64 = time_core_3000::make(perif.ctrl, radio::sr_addr(radio::TIME), time64_rb_bases); - - //Capture delays are calibrated every time. The status is only printed is the user - //asks to run the xfer self cal using "self_cal_adc_delay" - self_cal_adc_capture_delay(mb, radio_index, dev_addr.has_key("self_cal_adc_delay")); - - _tree->access<time_spec_t>(mb_path / "time" / "cmd") - .add_coerced_subscriber(boost::bind(&radio_ctrl_core_3000::set_time, perif.ctrl, _1)); - - //////////////////////////////////////////////////////////////// - // create codec control objects - //////////////////////////////////////////////////////////////// - _tree->create<int>(mb_path / "rx_codecs" / slot_name / "gains"); //phony property so this dir exists - _tree->create<int>(mb_path / "tx_codecs" / slot_name / "gains"); //phony property so this dir exists - _tree->create<std::string>(mb_path / "rx_codecs" / slot_name / "name").set("ads62p48"); - _tree->create<std::string>(mb_path / "tx_codecs" / slot_name / "name").set("ad9146"); - - _tree->create<meta_range_t>(mb_path / "rx_codecs" / slot_name / "gains" / "digital" / "range").set(meta_range_t(0, 6.0, 0.5)); - _tree->create<double>(mb_path / "rx_codecs" / slot_name / "gains" / "digital" / "value") - .add_coerced_subscriber(boost::bind(&x300_adc_ctrl::set_gain, perif.adc, _1)).set(0); - - //////////////////////////////////////////////////////////////////// - // front end corrections - //////////////////////////////////////////////////////////////////// - perif.rx_fe->populate_subtree(_tree->subtree(mb_path / "rx_frontends" / slot_name)); - perif.tx_fe->populate_subtree(_tree->subtree(mb_path / "tx_frontends" / slot_name)); - - //////////////////////////////////////////////////////////////////// - // connect rx dsp control objects - //////////////////////////////////////////////////////////////////// - const fs_path rx_dsp_path = mb_path / "rx_dsps" / str(boost::format("%u") % radio_index); - perif.ddc->populate_subtree(_tree->subtree(rx_dsp_path)); - _tree->access<double>(rx_dsp_path / "rate" / "value") - .add_coerced_subscriber(boost::bind(&x300_impl::update_rx_samp_rate, this, boost::ref(mb), radio_index, _1)) - ; - _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") - .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); - - //////////////////////////////////////////////////////////////////// - // connect tx dsp control objects - //////////////////////////////////////////////////////////////////// - const fs_path tx_dsp_path = mb_path / "tx_dsps" / str(boost::format("%u") % radio_index); - perif.duc->populate_subtree(_tree->subtree(tx_dsp_path)); - _tree->access<double>(tx_dsp_path / "rate" / "value") - .add_coerced_subscriber(boost::bind(&x300_impl::update_tx_samp_rate, this, boost::ref(mb), radio_index, _1)) - ; - - //////////////////////////////////////////////////////////////////// - // create RF frontend interfacing - //////////////////////////////////////////////////////////////////// - const fs_path db_path = (mb_path / "dboards" / slot_name); - const size_t j = (slot_name == "B")? 0x2 : 0x0; - _tree->create<dboard_eeprom_t>(db_path / "rx_eeprom") - .set(mb.db_eeproms[X300_DB0_RX_EEPROM | j]) - .add_coerced_subscriber(boost::bind(&x300_impl::set_db_eeprom, this, mb.zpu_i2c, (0x50 | X300_DB0_RX_EEPROM | j), _1)); - _tree->create<dboard_eeprom_t>(db_path / "tx_eeprom") - .set(mb.db_eeproms[X300_DB0_TX_EEPROM | j]) - .add_coerced_subscriber(boost::bind(&x300_impl::set_db_eeprom, this, mb.zpu_i2c, (0x50 | X300_DB0_TX_EEPROM | j), _1)); - _tree->create<dboard_eeprom_t>(db_path / "gdb_eeprom") - .set(mb.db_eeproms[X300_DB0_GDB_EEPROM | j]) - .add_coerced_subscriber(boost::bind(&x300_impl::set_db_eeprom, this, mb.zpu_i2c, (0x50 | X300_DB0_GDB_EEPROM | j), _1)); - - //create a new dboard interface - x300_dboard_iface_config_t db_config; - db_config.gpio = db_gpio_atr_3000::make(perif.ctrl, radio::sr_addr(radio::GPIO), radio::RB32_GPIO); - db_config.spi = perif.spi; - db_config.rx_spi_slaveno = DB_RX_SEN; - db_config.tx_spi_slaveno = DB_TX_SEN; - db_config.i2c = mb.zpu_i2c; - db_config.clock = mb.clock; - db_config.rx_dsp = mb.radio_perifs[radio_index].ddc; - db_config.which_rx_clk = (slot_name == "A")? X300_CLOCK_WHICH_DB0_RX : X300_CLOCK_WHICH_DB1_RX; - db_config.which_tx_clk = (slot_name == "A")? X300_CLOCK_WHICH_DB0_TX : X300_CLOCK_WHICH_DB1_TX; - db_config.dboard_slot = (slot_name == "A")? 0 : 1; - db_config.cmd_time_ctrl = perif.ctrl; - - //create a new dboard manager - _dboard_managers[db_path] = dboard_manager::make( - mb.db_eeproms[X300_DB0_RX_EEPROM | j].id, - mb.db_eeproms[X300_DB0_TX_EEPROM | j].id, - mb.db_eeproms[X300_DB0_GDB_EEPROM | j].id, - x300_make_dboard_iface(db_config), - _tree->subtree(db_path) - ); - - //now that dboard is created -- register into rx antenna event - const std::string fe_name = _tree->list(db_path / "rx_frontends").front(); - _tree->access<std::string>(db_path / "rx_frontends" / fe_name / "antenna" / "value") - .add_coerced_subscriber(boost::bind(&x300_impl::update_atr_leds, this, mb.radio_perifs[radio_index].leds, _1)); - this->update_atr_leds(mb.radio_perifs[radio_index].leds, ""); //init anyway, even if never called - - //bind frontend corrections to the dboard freq props - const fs_path db_tx_fe_path = db_path / "tx_frontends"; - BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)) { - _tree->access<double>(db_tx_fe_path / name / "freq" / "value") - .add_coerced_subscriber(boost::bind(&x300_impl::set_tx_fe_corrections, this, mb_path, slot_name, _1)); - } - const fs_path db_rx_fe_path = db_path / "rx_frontends"; - BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)) { - _tree->access<double>(db_rx_fe_path / name / "freq" / "value") - .add_coerced_subscriber(boost::bind(&x300_impl::set_rx_fe_corrections, this, mb_path, slot_name, _1)); - } -} + static const uint32_t CTRL_CHANNEL = 0; + static const uint32_t FIRST_DATA_CHANNEL = 1; + if (xport_type == CTRL) { + return CTRL_CHANNEL; + } else { + // sid_t has no comparison defined + uint32_t raw_sid = tx_sid.get(); -void x300_impl::set_rx_fe_corrections(const uhd::fs_path &mb_path, const std::string &fe_name, const double lo_freq) -{ - if(not _ignore_cal_file){ - apply_rx_fe_corrections(this->get_tree()->subtree(mb_path), fe_name, lo_freq); - } -} + if (_dma_chan_pool.count(raw_sid) == 0) { + _dma_chan_pool[raw_sid] = _dma_chan_pool.size() + FIRST_DATA_CHANNEL; + UHD_MSG(status) << "[X300] Assigning PCIe DMA channel " << _dma_chan_pool[raw_sid] + << " to SID " << tx_sid.to_pp_string_hex() << std::endl; + } -void x300_impl::set_tx_fe_corrections(const uhd::fs_path &mb_path, const std::string &fe_name, const double lo_freq) -{ - if(not _ignore_cal_file){ - apply_tx_fe_corrections(this->get_tree()->subtree(mb_path), fe_name, lo_freq); + if (_dma_chan_pool.size() + FIRST_DATA_CHANNEL > X300_PCIE_MAX_CHANNELS) { + throw uhd::runtime_error("Trying to allocate more DMA channels than are available"); + } + return _dma_chan_pool[raw_sid]; } } -boost::uint32_t get_pcie_dma_channel(boost::uint8_t destination, boost::uint8_t prefix) -{ - static const boost::uint32_t RADIO_GRP_SIZE = 3; - static const boost::uint32_t RADIO0_GRP = 0; - static const boost::uint32_t RADIO1_GRP = 1; - - boost::uint32_t radio_grp = (destination == X300_XB_DST_R0) ? RADIO0_GRP : RADIO1_GRP; - return ((radio_grp * RADIO_GRP_SIZE) + prefix); +static boost::uint32_t extract_sid_from_pkt(void* pkt, size_t) { + return uhd::sid_t(uhd::wtohx(static_cast<const boost::uint32_t*>(pkt)[1])).get_dst(); } -x300_impl::both_xports_t x300_impl::make_transport( - const size_t mb_index, - const boost::uint8_t& destination, - const boost::uint8_t& prefix, - const uhd::device_addr_t& args, - boost::uint32_t& sid) -{ +uhd::both_xports_t x300_impl::make_transport( + const uhd::sid_t &address, + const xport_type_t xport_type, + const uhd::device_addr_t& args +) { + const size_t mb_index = address.get_dst_addr() - X300_DST_ADDR; mboard_members_t &mb = _mb[mb_index]; - both_xports_t xports; - - sid_config_t config; - config.router_addr_there = X300_DEVICE_THERE; - config.dst_prefix = prefix; - config.router_dst_there = destination; - - // Choose the endpoint based on the destination - size_t endpoint = 0; - if (destination == X300_XB_DST_R1) { - if (mb.eth_conns.size() > 1) - endpoint = 1; - } - - // Decide on the IP/Interface pair based on the endpoint index - std::string interface_addr = mb.eth_conns[endpoint].addr; - config.iface_index = mb.eth_conns[endpoint].type; - - sid = this->allocate_sid(mb, config); - static const uhd::device_addr_t DEFAULT_XPORT_ARGS; - - const uhd::device_addr_t& xport_args = - (prefix != X300_RADIO_DEST_PREFIX_CTRL) ? args : DEFAULT_XPORT_ARGS; - + const uhd::device_addr_t& xport_args = (xport_type == CTRL) ? uhd::device_addr_t() : args; zero_copy_xport_params default_buff_args; + both_xports_t xports; if (mb.xport_path == "nirio") { - default_buff_args.send_frame_size = - (prefix == X300_RADIO_DEST_PREFIX_TX) - ? X300_PCIE_TX_DATA_FRAME_SIZE - : X300_PCIE_MSG_FRAME_SIZE; - - default_buff_args.recv_frame_size = - (prefix == X300_RADIO_DEST_PREFIX_RX) - ? X300_PCIE_RX_DATA_FRAME_SIZE - : X300_PCIE_MSG_FRAME_SIZE; - - default_buff_args.num_send_frames = - (prefix == X300_RADIO_DEST_PREFIX_TX) - ? X300_PCIE_DATA_NUM_FRAMES - : X300_PCIE_MSG_NUM_FRAMES; - - default_buff_args.num_recv_frames = - (prefix == X300_RADIO_DEST_PREFIX_RX) - ? X300_PCIE_DATA_NUM_FRAMES - : X300_PCIE_MSG_NUM_FRAMES; - - xports.recv = nirio_zero_copy::make( - mb.rio_fpga_interface, - get_pcie_dma_channel(destination, prefix), - default_buff_args, - xport_args); + xports.send_sid = this->allocate_sid(mb, address, X300_SRC_ADDR0, X300_XB_DST_PCI); + xports.recv_sid = xports.send_sid.reversed(); + + uint32_t dma_channel_num = allocate_pcie_dma_chan(xports.send_sid, xport_type); + if (xport_type == CTRL) { + //Transport for control stream + if (_ctrl_dma_xport.get() == NULL) { + //One underlying DMA channel will handle + //all control traffic + zero_copy_xport_params ctrl_buff_args; + ctrl_buff_args.send_frame_size = X300_PCIE_MSG_FRAME_SIZE; + ctrl_buff_args.recv_frame_size = X300_PCIE_MSG_FRAME_SIZE; + ctrl_buff_args.num_send_frames = X300_PCIE_MSG_NUM_FRAMES * X300_PCIE_MAX_MUXED_XPORTS; + ctrl_buff_args.num_recv_frames = X300_PCIE_MSG_NUM_FRAMES * X300_PCIE_MAX_MUXED_XPORTS; + + zero_copy_if::sptr base_xport = nirio_zero_copy::make( + mb.rio_fpga_interface, dma_channel_num, + ctrl_buff_args, uhd::device_addr_t()); + _ctrl_dma_xport = muxed_zero_copy_if::make(base_xport, extract_sid_from_pkt, X300_PCIE_MAX_MUXED_XPORTS); + } + //Create a virtual control transport + xports.recv = _ctrl_dma_xport->make_stream(xports.recv_sid.get_dst()); + } else { + //Transport for data stream + default_buff_args.send_frame_size = + (xport_type == TX_DATA) + ? X300_PCIE_TX_DATA_FRAME_SIZE + : X300_PCIE_MSG_FRAME_SIZE; + + default_buff_args.recv_frame_size = + (xport_type == RX_DATA) + ? X300_PCIE_RX_DATA_FRAME_SIZE + : X300_PCIE_MSG_FRAME_SIZE; + + default_buff_args.num_send_frames = + (xport_type == TX_DATA) + ? X300_PCIE_DATA_NUM_FRAMES + : X300_PCIE_MSG_NUM_FRAMES; + + default_buff_args.num_recv_frames = + (xport_type == RX_DATA) + ? X300_PCIE_DATA_NUM_FRAMES + : X300_PCIE_MSG_NUM_FRAMES; + + xports.recv = nirio_zero_copy::make( + mb.rio_fpga_interface, dma_channel_num, + default_buff_args, xport_args); + } xports.send = xports.recv; + // Router config word is: + // - Upper 16 bits: Destination address (e.g. 0.0) + // - Lower 16 bits: DMA channel + uint32_t router_config_word = (xports.recv_sid.get_dst() << 16) | dma_channel_num; + mb.rio_fpga_interface->get_kernel_proxy()->poke(PCIE_ROUTER_REG(0), router_config_word); + //For the nirio transport, buffer size is depends on the frame size and num frames xports.recv_buff_size = xports.recv->get_num_recv_frames() * xports.recv->get_recv_frame_size(); xports.send_buff_size = xports.send->get_num_send_frames() * xports.send->get_send_frame_size(); } else if (mb.xport_path == "eth") { + // Decide on the IP/Interface pair based on the endpoint index + std::string interface_addr = mb.eth_conns[mb.next_src_addr].addr; + const uint32_t xbar_src_addr = + mb.next_src_addr==0 ? X300_SRC_ADDR0 : X300_SRC_ADDR1; + const uint32_t xbar_src_dst = + mb.eth_conns[mb.next_src_addr].type==X300_IFACE_ETH0 ? X300_XB_DST_E0 : X300_XB_DST_E1; + mb.next_src_addr = (mb.next_src_addr + 1) % mb.eth_conns.size(); + + xports.send_sid = this->allocate_sid(mb, address, xbar_src_addr, xbar_src_dst); + xports.recv_sid = xports.send_sid.reversed(); /* Determine what the recommended frame size is for this * connection type.*/ @@ -1359,24 +1126,22 @@ x300_impl::both_xports_t x300_impl::make_transport( fs_path mboard_path = fs_path("/mboards/"+boost::lexical_cast<std::string>(mb_index) / "link_max_rate"); - UHD_ASSERT_THROW(mb.loaded_fpga_image.size() >= 2); - - if (mb.loaded_fpga_image.substr(0,2) == "HG") { + if (mb.loaded_fpga_image == "HG") { size_t max_link_rate = 0; - if (config.iface_index == X300_IFACE_ETH0) { + if (xbar_src_dst == X300_XB_DST_E0) { eth_data_rec_frame_size = X300_1GE_DATA_FRAME_MAX_SIZE; max_link_rate += X300_MAX_RATE_1GIGE; - } else if (config.iface_index == X300_IFACE_ETH1) { + } else if (xbar_src_dst == X300_XB_DST_E1) { eth_data_rec_frame_size = X300_10GE_DATA_FRAME_MAX_SIZE; max_link_rate += X300_MAX_RATE_10GIGE; } _tree->access<double>(mboard_path).set(max_link_rate); - } else if (mb.loaded_fpga_image.substr(0,2) == "XG" or mb.loaded_fpga_image.substr(0,2) == "XA") { + } else if (mb.loaded_fpga_image == "XG" or mb.loaded_fpga_image == "XA") { eth_data_rec_frame_size = X300_10GE_DATA_FRAME_MAX_SIZE; size_t max_link_rate = X300_MAX_RATE_10GIGE; max_link_rate *= mb.eth_conns.size(); _tree->access<double>(mboard_path).set(max_link_rate); - } else if (mb.loaded_fpga_image.substr(0,2) == "HA") { + } else if (mb.loaded_fpga_image == "HA") { eth_data_rec_frame_size = X300_1GE_DATA_FRAME_MAX_SIZE; size_t max_link_rate = X300_MAX_RATE_1GIGE; max_link_rate *= mb.eth_conns.size(); @@ -1414,22 +1179,22 @@ x300_impl::both_xports_t x300_impl::make_transport( // Make sure frame sizes do not exceed the max available value supported by UHD default_buff_args.send_frame_size = - (prefix == X300_RADIO_DEST_PREFIX_TX) + (xport_type == TX_DATA) ? std::min(system_max_send_frame_size, X300_10GE_DATA_FRAME_MAX_SIZE) : std::min(system_max_send_frame_size, X300_ETH_MSG_FRAME_SIZE); default_buff_args.recv_frame_size = - (prefix == X300_RADIO_DEST_PREFIX_RX) + (xport_type == RX_DATA) ? std::min(system_max_recv_frame_size, X300_10GE_DATA_FRAME_MAX_SIZE) : std::min(system_max_recv_frame_size, X300_ETH_MSG_FRAME_SIZE); default_buff_args.num_send_frames = - (prefix == X300_RADIO_DEST_PREFIX_TX) + (xport_type == TX_DATA) ? X300_ETH_DATA_NUM_FRAMES : X300_ETH_MSG_NUM_FRAMES; default_buff_args.num_recv_frames = - (prefix == X300_RADIO_DEST_PREFIX_RX) + (xport_type == RX_DATA) ? X300_ETH_DATA_NUM_FRAMES : X300_ETH_MSG_NUM_FRAMES; @@ -1445,7 +1210,7 @@ x300_impl::both_xports_t x300_impl::make_transport( // Create a threaded transport for the receive chain only // Note that this shouldn't affect PCIe - if (prefix == X300_RADIO_DEST_PREFIX_RX) { + if (xport_type == RX_DATA) { xports.recv = zero_copy_recv_offload::make( xports.recv, X300_THREAD_BUFFER_TIMEOUT @@ -1465,12 +1230,12 @@ x300_impl::both_xports_t x300_impl::make_transport( //send a mini packet with SID into the ZPU //ZPU will reprogram the ethernet framer UHD_LOG << "programming packet for new xport on " - << mb.get_pri_eth().addr << std::hex << "sid 0x" << sid << std::dec << std::endl; + << interface_addr << " sid " << xports.send_sid << std::endl; //YES, get a __send__ buffer from the __recv__ socket //-- this is the only way to program the framer for recv: managed_send_buffer::sptr buff = xports.recv->get_send_buff(); buff->cast<boost::uint32_t *>()[0] = 0; //eth dispatch looks for != 0 - buff->cast<boost::uint32_t *>()[1] = uhd::htonx(sid); + buff->cast<boost::uint32_t *>()[1] = uhd::htonx(xports.send_sid.get()); buff->commit(8); buff.reset(); @@ -1487,60 +1252,27 @@ x300_impl::both_xports_t x300_impl::make_transport( } -boost::uint32_t x300_impl::allocate_sid(mboard_members_t &mb, const sid_config_t &config) -{ - const std::string &xport_path = mb.xport_path; - const boost::uint32_t stream = (config.dst_prefix | (config.router_dst_there << 2)) & 0xff; - boost::uint8_t sid_ret_addr = X300_SRC_ADDR_ETH0; - boost::uint32_t xb_port = X300_XB_DST_E0; - - // Use the interface index to decide on the ethernet port - if (config.iface_index == X300_IFACE_ETH1) { - sid_ret_addr = X300_SRC_ADDR_ETH1; - xb_port = X300_XB_DST_E1; - } - - // Ensure we choose the right port in the case of NI-RIO - if (xport_path == "nirio") { - xb_port = X300_XB_DST_PCI; - } - - int int_sid_ret_addr = int(sid_ret_addr); - - const boost::uint32_t sid = 0 - | (sid_ret_addr << 24) - | (_sid_framer << 16) - | (config.router_addr_there << 8) - | (stream << 0) - ; - - UHD_LOG << std::hex - << " sid 0x" << sid - << " return address 0x" << int_sid_ret_addr - << " framer 0x" << _sid_framer - << " stream 0x" << stream - << " router_dst_there 0x" << int(config.router_dst_there) - << " router_addr_there 0x" << int(config.router_addr_there) - << std::dec << std::endl; +uhd::sid_t x300_impl::allocate_sid( + mboard_members_t &mb, + const uhd::sid_t &address, + const uint32_t src_addr, + const uint32_t src_dst +) { + uhd::sid_t sid = address; + sid.set_src_addr(src_addr); + sid.set_src_endpoint(_sid_framer); + // TODO Move all of this setup_mb() // Program the X300 to recognise it's own local address. - mb.zpu_ctrl->poke32(SR_ADDR(SET0_BASE, ZPU_SR_XB_LOCAL), config.router_addr_there); + mb.zpu_ctrl->poke32(SR_ADDR(SET0_BASE, ZPU_SR_XB_LOCAL), address.get_dst_addr()); // Program CAM entry for outgoing packets matching a X300 resource (for example a Radio) - // This type of packet does matches the XB_LOCAL address and is looked up in the upper half of the CAM - mb.zpu_ctrl->poke32(SR_ADDR(SETXB_BASE, 256 + (stream)), config.router_dst_there); + // This type of packet matches the XB_LOCAL address and is looked up in the upper half of the CAM + mb.zpu_ctrl->poke32(SR_ADDR(SETXB_BASE, 256 + address.get_dst_endpoint()), address.get_dst_xbarport()); // Program CAM entry for returning packets to us (for example GR host via Eth0) // This type of packet does not match the XB_LOCAL address and is looked up in the lower half of the CAM - mb.zpu_ctrl->poke32(SR_ADDR(SETXB_BASE, 0 + (sid_ret_addr)), xb_port); - - if (xport_path == "nirio") { - boost::uint32_t router_config_word = ((_sid_framer & 0xff) << 16) | //Return SID - get_pcie_dma_channel(config.router_dst_there, config.dst_prefix); //Dest - mb.rio_fpga_interface->get_kernel_proxy()->poke(PCIE_ROUTER_REG(0), router_config_word); - } + mb.zpu_ctrl->poke32(SR_ADDR(SETXB_BASE, 0 + src_addr), src_dst); - UHD_LOG << std::hex - << "done router config for sid 0x" << sid - << std::dec << std::endl; + UHD_LOG << "done router config for sid " << sid << std::endl; //increment for next setup _sid_framer++; @@ -1548,56 +1280,9 @@ boost::uint32_t x300_impl::allocate_sid(mboard_members_t &mb, const sid_config_t return sid; } -void x300_impl::update_atr_leds(gpio_atr_3000::sptr leds, const std::string &rx_ant) -{ - const bool is_txrx = (rx_ant == "TX/RX"); - const int rx_led = (1 << 2); - const int tx_led = (1 << 1); - const int txrx_led = (1 << 0); - leds->set_atr_reg(ATR_REG_IDLE, 0); - leds->set_atr_reg(ATR_REG_RX_ONLY, is_txrx? txrx_led : rx_led); - leds->set_atr_reg(ATR_REG_TX_ONLY, tx_led); - leds->set_atr_reg(ATR_REG_FULL_DUPLEX, rx_led | tx_led); -} - -void x300_impl::set_tick_rate(mboard_members_t &mb, const double rate) -{ - BOOST_FOREACH(radio_perifs_t &perif, mb.radio_perifs) { - perif.ctrl->set_tick_rate(rate); - perif.time64->set_tick_rate(rate); - perif.framer->set_tick_rate(rate); - perif.ddc->set_tick_rate(rate); - perif.duc->set_tick_rate(rate); - } -} - -void x300_impl::register_loopback_self_test(wb_iface::sptr iface) -{ - bool test_fail = false; - UHD_MSG(status) << "Performing register loopback test... " << std::flush; - size_t hash = size_t(time(NULL)); - for (size_t i = 0; i < 100; i++) - { - boost::hash_combine(hash, i); - iface->poke32(radio::sr_addr(radio::TEST), boost::uint32_t(hash)); - test_fail = iface->peek32(radio::RB32_TEST) != boost::uint32_t(hash); - if (test_fail) break; //exit loop on any failure - } - UHD_MSG(status) << ((test_fail)? " fail" : "pass") << std::endl; -} - -void x300_impl::radio_loopback(wb_iface::sptr iface, const bool on) -{ - iface->poke32(radio::sr_addr(radio::LOOPBACK), (on ? 0x1 : 0x0)); - UHD_MSG(status) << ((on)? "Radio Loopback On" : "Radio Loopback Off") << std::endl; -} - - - /*********************************************************************** * clock and time control logic **********************************************************************/ - void x300_impl::set_time_source_out(mboard_members_t &mb, const bool enb) { mb.fw_regmap->clock_ctrl_reg.write(fw_regmap_t::clk_ctrl_reg_t::PPS_OUT_EN, enb?1:0); @@ -1670,18 +1355,8 @@ void x300_impl::update_clock_source(mboard_members_t &mb, const std::string &sou } // Reset ADCs and DACs - for (size_t r = 0; r < mboard_members_t::NUM_RADIOS; r++) { - radio_perifs_t &perif = mb.radio_perifs[r]; - if (perif.regmap && r==0) { //ADC/DAC reset lines only exist in Radio0 - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1); - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0); - perif.regmap->misc_outs_reg.flush(); - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 0); - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 1); - perif.regmap->misc_outs_reg.flush(); - } - if (perif.adc) perif.adc->reset(); - if (perif.dac) perif.dac->reset(); + BOOST_FOREACH(rfnoc::x300_radio_ctrl_impl::sptr r, mb.radios) { + r->reset_codec(); } } @@ -1711,8 +1386,12 @@ void x300_impl::update_time_source(mboard_members_t &mb, const std::string &sour void x300_impl::sync_times(mboard_members_t &mb, const uhd::time_spec_t& t) { - BOOST_FOREACH(radio_perifs_t &perif, mb.radio_perifs) - perif.time64->set_time_sync(t); + std::vector<rfnoc::block_id_t> radio_ids = find_blocks<rfnoc::x300_radio_ctrl_impl>("Radio"); + BOOST_FOREACH(const rfnoc::block_id_t &id, radio_ids) { + get_block_ctrl<rfnoc::x300_radio_ctrl_impl>(id)->set_time_sync(t); + } + + mb.fw_regmap->clock_ctrl_reg.write(fw_regmap_t::clk_ctrl_reg_t::TIME_SYNC, 0); mb.fw_regmap->clock_ctrl_reg.write(fw_regmap_t::clk_ctrl_reg_t::TIME_SYNC, 1); mb.fw_regmap->clock_ctrl_reg.write(fw_regmap_t::clk_ctrl_reg_t::TIME_SYNC, 0); } @@ -1911,7 +1590,10 @@ void x300_impl::check_fpga_compat(const fs_path &mb_path, const mboard_members_t : "resource") % members.get_pri_eth().addr); - throw uhd::runtime_error(str(boost::format( + std::cout << "=========================================================" << std::endl; + std::cout << "Warning:" << std::endl; + //throw uhd::runtime_error(str(boost::format( + std::cout << (str(boost::format( "Expected FPGA compatibility number %d, but got %d:\n" "The FPGA image on your device is not compatible with this host code build.\n" "Download the appropriate FPGA images for this version of UHD.\n" @@ -1923,6 +1605,7 @@ void x300_impl::check_fpga_compat(const fs_path &mb_path, const mboard_members_t ) % int(X300_FPGA_COMPAT_MAJOR) % compat_major % print_utility_error("uhd_images_downloader.py") % image_loader_cmd)); + std::cout << "=========================================================" << std::endl; } _tree->create<std::string>(mb_path / "fpga_version").set(str(boost::format("%u.%u") % compat_major % compat_minor)); diff --git a/host/lib/usrp/x300/x300_impl.hpp b/host/lib/usrp/x300/x300_impl.hpp index b5bbbe136..d491e2bc0 100644 --- a/host/lib/usrp/x300/x300_impl.hpp +++ b/host/lib/usrp/x300/x300_impl.hpp @@ -19,55 +19,36 @@ #define INCLUDED_X300_IMPL_HPP #include <uhd/property_tree.hpp> -#include <uhd/device.hpp> +#include "../device3/device3_impl.hpp" #include <uhd/usrp/mboard_eeprom.hpp> -#include <uhd/usrp/dboard_manager.hpp> -#include <uhd/usrp/dboard_eeprom.hpp> #include <uhd/usrp/subdev_spec.hpp> #include <uhd/types/sensors.hpp> +#include "x300_radio_ctrl_impl.hpp" #include "x300_clock_ctrl.hpp" #include "x300_fw_common.h" #include <uhd/transport/udp_simple.hpp> //mtu -#include <uhd/utils/tasks.hpp> -#include "spi_core_3000.hpp" -#include "x300_adc_ctrl.hpp" -#include "x300_dac_ctrl.hpp" -#include "rx_vita_core_3000.hpp" -#include "tx_vita_core_3000.hpp" -#include "time_core_3000.hpp" -#include "rx_dsp_core_3000.hpp" -#include "tx_dsp_core_3000.hpp" #include "i2c_core_100_wb32.hpp" -#include "radio_ctrl_core_3000.hpp" -#include "rx_frontend_core_200.hpp" -#include "tx_frontend_core_200.hpp" -#include "gpio_atr_3000.hpp" -#include "dma_fifo_core_3000.hpp" #include <boost/weak_ptr.hpp> #include <uhd/usrp/gps_ctrl.hpp> -#include <uhd/usrp/mboard_eeprom.hpp> -#include <uhd/transport/bounded_buffer.hpp> #include <uhd/transport/nirio/niusrprio_session.h> #include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/transport/muxed_zero_copy_if.hpp> #include "recv_packet_demuxer_3000.hpp" #include "x300_regs.hpp" +///////////// RFNOC ///////////////////// +#include <uhd/rfnoc/block_ctrl.hpp> +///////////// RFNOC ///////////////////// +#include <boost/dynamic_bitset.hpp> static const std::string X300_FW_FILE_NAME = "usrp_x300_fw.bin"; +static const std::string X300_DEFAULT_CLOCK_SOURCE = "internal"; static const double X300_DEFAULT_TICK_RATE = 200e6; //Hz static const double X300_DEFAULT_DBOARD_CLK_RATE = 50e6; //Hz static const double X300_BUS_CLOCK_RATE = 166.666667e6; //Hz -static const size_t X300_TX_HW_BUFF_SIZE_SRAM = 520*1024; //512K SRAM buffer + 8K 2Clk FIFO -static const size_t X300_TX_FC_RESPONSE_FREQ_SRAM = 8; //per flow-control window -static const size_t X300_TX_HW_BUFF_SIZE_DRAM = 128*1024; -static const size_t X300_TX_FC_RESPONSE_FREQ_DRAM = 32; -static const boost::uint32_t X300_DRAM_FIFO_SIZE = 32*1024*1024; - static const size_t X300_RX_SW_BUFF_SIZE_ETH = 0x2000000;//32MiB For an ~8k frame size any size >32MiB is just wasted buffer space static const size_t X300_RX_SW_BUFF_SIZE_ETH_MACOS = 0x100000; //1Mib -static const double X300_RX_SW_BUFF_FULL_FACTOR = 0.90; //Buffer should ideally be 90% full. -static const size_t X300_RX_FC_REQUEST_FREQ = 32; //per flow-control window //The FIFO closest to the DMA controller is 1023 elements deep for RX and 1029 elements deep for TX //where an element is 8 bytes. For best throughput ensure that the data frame fits in these buffers. @@ -77,6 +58,8 @@ static const size_t X300_PCIE_TX_DATA_FRAME_SIZE = 8192; //bytes static const size_t X300_PCIE_DATA_NUM_FRAMES = 2048; static const size_t X300_PCIE_MSG_FRAME_SIZE = 256; //bytes static const size_t X300_PCIE_MSG_NUM_FRAMES = 64; +static const size_t X300_PCIE_MAX_CHANNELS = 6; +static const size_t X300_PCIE_MAX_MUXED_XPORTS = 32; static const size_t X300_10GE_DATA_FRAME_MAX_SIZE = 8000; //bytes static const size_t X300_1GE_DATA_FRAME_MAX_SIZE = 1472; //bytes @@ -88,46 +71,22 @@ static const size_t X300_ETH_MSG_NUM_FRAMES = 64; static const size_t X300_ETH_DATA_NUM_FRAMES = 32; static const double X300_DEFAULT_SYSREF_RATE = 10e6; -static const size_t X300_TX_MAX_HDR_LEN = // bytes - sizeof(boost::uint32_t) // Header - + sizeof(uhd::transport::vrt::if_packet_info_t().sid) // SID - + sizeof(uhd::transport::vrt::if_packet_info_t().tsf); // Timestamp -static const size_t X300_RX_MAX_HDR_LEN = // bytes - sizeof(boost::uint32_t) // Header - + sizeof(uhd::transport::vrt::if_packet_info_t().sid) // SID - + sizeof(uhd::transport::vrt::if_packet_info_t().tsf); // Timestamp - static const size_t X300_MAX_RATE_PCIE = 800000000; // bytes/s static const size_t X300_MAX_RATE_10GIGE = 800000000; // bytes/s static const size_t X300_MAX_RATE_1GIGE = 100000000; // bytes/s #define X300_RADIO_DEST_PREFIX_TX 0 -#define X300_RADIO_DEST_PREFIX_CTRL 1 -#define X300_RADIO_DEST_PREFIX_RX 2 - -#define X300_XB_DST_E0 0 -#define X300_XB_DST_E1 1 -#define X300_XB_DST_R0 2 // Radio 0 -> Slot A -#define X300_XB_DST_R1 3 // Radio 1 -> Slot B -#define X300_XB_DST_CE0 4 -#define X300_XB_DST_CE1 5 -#define X300_XB_DST_CE2 5 -#define X300_XB_DST_PCI 7 - -#define X300_DEVICE_THERE 2 -#define X300_SRC_ADDR_ETH0 0 -#define X300_SRC_ADDR_ETH1 1 - -//eeprom addrs for various boards -enum -{ - X300_DB0_RX_EEPROM = 0x5, - X300_DB0_TX_EEPROM = 0x4, - X300_DB0_GDB_EEPROM = 0x1, - X300_DB1_RX_EEPROM = 0x7, - X300_DB1_TX_EEPROM = 0x6, - X300_DB1_GDB_EEPROM = 0x3, -}; + +#define X300_XB_DST_E0 0 +#define X300_XB_DST_E1 1 +#define X300_XB_DST_PCI 2 +#define X300_XB_DST_R0 3 // Radio 0 -> Slot A +#define X300_XB_DST_R1 4 // Radio 1 -> Slot B +#define X300_XB_DST_CE0 5 + +#define X300_SRC_ADDR0 0 +#define X300_SRC_ADDR1 1 +#define X300_DST_ADDR 2 // Ethernet ports enum x300_eth_iface_t @@ -143,22 +102,7 @@ struct x300_eth_conn_t x300_eth_iface_t type; }; -struct x300_dboard_iface_config_t -{ - uhd::usrp::gpio_atr::db_gpio_atr_3000::sptr gpio; - spi_core_3000::sptr spi; - size_t rx_spi_slaveno; - size_t tx_spi_slaveno; - i2c_core_100_wb32::sptr i2c; - x300_clock_ctrl::sptr clock; - rx_dsp_core_3000::sptr rx_dsp; - x300_clock_which_t which_rx_clk; - x300_clock_which_t which_tx_clk; - boost::uint8_t dboard_slot; - uhd::timed_wb_iface::sptr cmd_time_ctrl; -}; -uhd::usrp::dboard_iface::sptr x300_make_dboard_iface(const x300_dboard_iface_config_t &); uhd::uart_iface::sptr x300_make_uart_iface(uhd::wb_iface::sptr iface); uhd::wb_iface::sptr x300_make_ctrl_iface_enet(uhd::transport::udp_simple::sptr udp, bool enable_errors = true); @@ -166,22 +110,14 @@ uhd::wb_iface::sptr x300_make_ctrl_iface_pcie(uhd::niusrprio::niriok_proxy::sptr uhd::device_addrs_t x300_find(const uhd::device_addr_t &hint_); -class x300_impl : public uhd::device +class x300_impl : public uhd::usrp::device3_impl { public: - typedef uhd::transport::bounded_buffer<uhd::async_metadata_t> async_md_type; x300_impl(const uhd::device_addr_t &); void setup_mb(const size_t which, const uhd::device_addr_t &); ~x300_impl(void); - //the io interface - uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &); - uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &); - - //support old async call - bool recv_async_msg(uhd::async_metadata_t &, double); - // used by x300_find_with_addr to find X300 devices. static boost::mutex claimer_mutex; //All claims and checks in this process are serialized static bool is_claimed(uhd::wb_iface::sptr); @@ -192,43 +128,26 @@ public: static x300_mboard_t get_mb_type_from_pcie(const std::string& resource, const std::string& rpc_port); static x300_mboard_t get_mb_type_from_eeprom(const uhd::usrp::mboard_eeprom_t& mb_eeprom); -private: - boost::shared_ptr<async_md_type> _async_md; +protected: + void subdev_to_blockid( + const uhd::usrp::subdev_spec_pair_t &spec, const size_t mb_i, + uhd::rfnoc::block_id_t &block_id, uhd::device_addr_t &block_args + ); + uhd::usrp::subdev_spec_pair_t blockid_to_subdev( + const uhd::rfnoc::block_id_t &blockid, const uhd::device_addr_t &block_args + ); - //perifs in the radio core - struct radio_perifs_t - { - //Interfaces - radio_ctrl_core_3000::sptr ctrl; - spi_core_3000::sptr spi; - x300_adc_ctrl::sptr adc; - x300_dac_ctrl::sptr dac; - time_core_3000::sptr time64; - rx_vita_core_3000::sptr framer; - rx_dsp_core_3000::sptr ddc; - tx_vita_core_3000::sptr deframer; - tx_dsp_core_3000::sptr duc; - uhd::usrp::gpio_atr::gpio_atr_3000::sptr leds; - rx_frontend_core_200::sptr rx_fe; - tx_frontend_core_200::sptr tx_fe; - //Registers - uhd::usrp::x300::radio_regmap_t::sptr regmap; - }; - - //overflow recovery impl - void handle_overflow(radio_perifs_t &perif, boost::weak_ptr<uhd::rx_streamer> streamer); +private: //vector of member objects per motherboard struct mboard_members_t { - uhd::dict<size_t, boost::weak_ptr<uhd::rx_streamer> > rx_streamers; - uhd::dict<size_t, boost::weak_ptr<uhd::tx_streamer> > tx_streamers; - bool initialization_done; uhd::task::sptr claimer_task; std::string xport_path; std::vector<x300_eth_conn_t> eth_conns; + size_t next_src_addr; // Discover the ethernet connections per motherboard void discover_eth(const uhd::usrp::mboard_eeprom_t mb_eeprom, @@ -250,24 +169,9 @@ private: spi_core_3000::sptr zpu_spi; i2c_core_100_wb32::sptr zpu_i2c; - //perifs in each radio - static const size_t NUM_RADIOS = 2; - radio_perifs_t radio_perifs[NUM_RADIOS]; //!< This is hardcoded s.t. radio_perifs[0] points to slot A and [1] to B - uhd::usrp::dboard_eeprom_t db_eeproms[8]; - //! Return the index of a radio component, given a slot name. This means DSPs, radio_perifs - size_t get_radio_index(const std::string &slot_name) { - UHD_ASSERT_THROW(slot_name == "A" or slot_name == "B"); - return slot_name == "A" ? 0 : 1; - } - - bool has_dram_buff; - dma_fifo_core_3000::sptr dram_buff_ctrl[NUM_RADIOS]; - - //other perifs on mboard x300_clock_ctrl::sptr clock; uhd::gps_ctrl::sptr gps; - uhd::usrp::gpio_atr::gpio_atr_3000::sptr fp_gpio; uhd::usrp::x300::fw_regmap_t::sptr fw_regmap; @@ -277,57 +181,25 @@ private: size_t hw_rev; std::string current_refclk_src; - uhd::soft_regmap_db_t::sptr regmap_db; + std::vector<uhd::rfnoc::x300_radio_ctrl_impl::sptr> radios; }; std::vector<mboard_members_t> _mb; //task for periodically reclaiming the device from others void claimer_loop(uhd::wb_iface::sptr); - boost::mutex _transport_setup_mutex; - - void register_loopback_self_test(uhd::wb_iface::sptr iface); - - void radio_loopback(uhd::wb_iface::sptr iface, const bool on); - - /*! \brief Initialize the radio component on a given slot. - * - * Call this function once per slot (A and B) and motherboard to initialize all the radio components. - * This will: - * - Reset and init DACs and ADCs - * - Setup controls for DAC, ADC, SPI and LEDs - * - Self test ADC - * - Sync DACs (for MIMO) - * - Initialize the property tree for control objects etc. (gain, rate...) - * - * \param mb_i Motherboard index - * \param slot_name Slot name (A or B). - */ - void setup_radio(const size_t, const std::string &slot_name, const uhd::device_addr_t &dev_addr); - size_t _sid_framer; - struct sid_config_t - { - boost::uint8_t router_addr_there; - boost::uint8_t dst_prefix; //2bits - boost::uint8_t router_dst_there; - x300_eth_iface_t iface_index; - }; - boost::uint32_t allocate_sid(mboard_members_t &mb, const sid_config_t &config); - struct both_xports_t - { - uhd::transport::zero_copy_if::sptr recv; - uhd::transport::zero_copy_if::sptr send; - size_t recv_buff_size; - size_t send_buff_size; - }; - both_xports_t make_transport( - const size_t mb_index, - const boost::uint8_t& destination, - const boost::uint8_t& prefix, - const uhd::device_addr_t& args, - boost::uint32_t& sid); + uhd::sid_t allocate_sid( + mboard_members_t &mb, + const uhd::sid_t &address, + const uint32_t src_addr, + const uint32_t src_dst); + uhd::both_xports_t make_transport( + const uhd::sid_t &address, + const xport_type_t xport_type, + const uhd::device_addr_t& args + ); struct frame_size_t { @@ -343,6 +215,15 @@ private: */ frame_size_t determine_max_frame_size(const std::string &addr, const frame_size_t &user_mtu); + std::map<uint32_t, uint32_t> _dma_chan_pool; + uhd::transport::muxed_zero_copy_if::sptr _ctrl_dma_xport; + + /*! Allocate or return a previously allocated PCIe channel pair + * + * Note the SID is always the transmit SID (i.e. from host to device). + */ + uint32_t allocate_pcie_dma_chan(const uhd::sid_t &tx_sid, const xport_type_t xport_type); + //////////////////////////////////////////////////////////////////// // //Caching for transport interface re-use -- like sharing a DMA. @@ -366,26 +247,8 @@ private: uhd::dict<std::string, uhd::usrp::dboard_manager::sptr> _dboard_managers; - void set_rx_fe_corrections(const uhd::fs_path &mb_path, const std::string &fe_name, const double lo_freq); - void set_tx_fe_corrections(const uhd::fs_path &mb_path, const std::string &fe_name, const double lo_freq); bool _ignore_cal_file; - - /*! Update the IQ MUX settings for the radio peripheral according to given subdev spec. - * - * Also checks if the given subdev is valid for this device and updates the channel to DSP mapping. - * - * \param tx_rx "tx" or "rx", depending where you're setting the subdev spec - * \param mb_i Mainboard index number. - * \param spec Subdev spec - */ - void update_subdev_spec(const std::string &tx_rx, const size_t mb_i, const uhd::usrp::subdev_spec_t &spec); - - void set_tick_rate(mboard_members_t &, const double); - void update_tick_rate(mboard_members_t &, const double); - void update_rx_samp_rate(mboard_members_t&, const size_t, const double); - void update_tx_samp_rate(mboard_members_t&, const size_t, const double); - void update_clock_control(mboard_members_t&); void initialize_clock_control(mboard_members_t &mb); void set_time_source_out(mboard_members_t&, const bool); @@ -403,19 +266,15 @@ private: void check_fw_compat(const uhd::fs_path &mb_path, uhd::wb_iface::sptr iface); void check_fpga_compat(const uhd::fs_path &mb_path, const mboard_members_t &members); - void update_atr_leds(uhd::usrp::gpio_atr::gpio_atr_3000::sptr, const std::string &ant); - - void self_cal_adc_capture_delay(mboard_members_t& mb, const size_t radio_i, bool print_status = false); - double self_cal_adc_xfer_delay(mboard_members_t& mb, bool apply_delay = false); - void self_test_adcs(mboard_members_t& mb, boost::uint32_t ramp_time_ms = 100); - - void extended_adc_test(mboard_members_t& mb, double duration_s); + /// More IO stuff + uhd::device_addr_t get_tx_hints(size_t mb_index); + uhd::device_addr_t get_rx_hints(size_t mb_index); + uhd::endianness_t get_transport_endianness(size_t mb_index) { + return _mb[mb_index].if_pkt_is_big_endian ? uhd::ENDIANNESS_BIG : uhd::ENDIANNESS_LITTLE; + }; - //**PRECONDITION** - //This function assumes that all the VITA times in "radios" are synchronized - //to a common reference. Currently, this function is called in get_tx_stream - //which also has the same precondition. - static void synchronize_dacs(const std::vector<radio_perifs_t*>& mboards); + void post_streamer_hooks(uhd::direction_t dir); }; #endif /* INCLUDED_X300_IMPL_HPP */ +// vim: sw=4 expandtab: diff --git a/host/lib/usrp/x300/x300_io_impl.cpp b/host/lib/usrp/x300/x300_io_impl.cpp index 329405261..5314d1b9a 100644 --- a/host/lib/usrp/x300/x300_io_impl.cpp +++ b/host/lib/usrp/x300/x300_io_impl.cpp @@ -15,18 +15,19 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. // +#define DEVICE3_STREAMER + #include "x300_regs.hpp" #include "x300_impl.hpp" -#include "validate_subdev_spec.hpp" #include "../../transport/super_recv_packet_handler.hpp" #include "../../transport/super_send_packet_handler.hpp" #include <uhd/transport/nirio_zero_copy.hpp> #include "async_packet_handler.hpp" #include <uhd/transport/bounded_buffer.hpp> -#include <uhd/transport/chdr.hpp> #include <boost/bind.hpp> #include <uhd/utils/tasks.hpp> #include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> #include <boost/foreach.hpp> #include <boost/make_shared.hpp> @@ -35,589 +36,65 @@ using namespace uhd::usrp; using namespace uhd::transport; /*********************************************************************** - * update streamer rates + * Hooks for get_tx_stream() and get_rx_stream() **********************************************************************/ -void x300_impl::update_tick_rate(mboard_members_t &mb, const double rate) +device_addr_t x300_impl::get_rx_hints(size_t mb_index) { - BOOST_FOREACH(const size_t &dspno, mb.rx_streamers.keys()) - { - boost::shared_ptr<sph::recv_packet_streamer> my_streamer = - boost::dynamic_pointer_cast<sph::recv_packet_streamer>(mb.rx_streamers[dspno].lock()); - if (my_streamer) my_streamer->set_tick_rate(rate); - } - BOOST_FOREACH(const size_t &dspno, mb.tx_streamers.keys()) + device_addr_t rx_hints = _mb[mb_index].recv_args; + // (default to a large recv buff) + if (not rx_hints.has_key("recv_buff_size")) { - boost::shared_ptr<sph::send_packet_streamer> my_streamer = - boost::dynamic_pointer_cast<sph::send_packet_streamer>(mb.tx_streamers[dspno].lock()); - if (my_streamer) my_streamer->set_tick_rate(rate); - } -} - -void x300_impl::update_rx_samp_rate(mboard_members_t &mb, const size_t dspno, const double rate) -{ - if (not mb.rx_streamers.has_key(dspno)) return; - boost::shared_ptr<sph::recv_packet_streamer> my_streamer = - boost::dynamic_pointer_cast<sph::recv_packet_streamer>(mb.rx_streamers[dspno].lock()); - if (not my_streamer) return; - my_streamer->set_samp_rate(rate); - const double adj = mb.radio_perifs[dspno].ddc->get_scaling_adjustment(); - my_streamer->set_scale_factor(adj); -} - -void x300_impl::update_tx_samp_rate(mboard_members_t &mb, const size_t dspno, const double rate) -{ - if (not mb.tx_streamers.has_key(dspno)) return; - boost::shared_ptr<sph::send_packet_streamer> my_streamer = - boost::dynamic_pointer_cast<sph::send_packet_streamer>(mb.tx_streamers[dspno].lock()); - if (not my_streamer) return; - my_streamer->set_samp_rate(rate); - const double adj = mb.radio_perifs[dspno].duc->get_scaling_adjustment(); - my_streamer->set_scale_factor(adj); -} - -/*********************************************************************** - * Setup dboard muxing for IQ - **********************************************************************/ -void x300_impl::update_subdev_spec(const std::string &tx_rx, const size_t mb_i, const subdev_spec_t &spec) -{ - UHD_ASSERT_THROW(tx_rx == "tx" or tx_rx == "rx"); - UHD_ASSERT_THROW(mb_i < _mb.size()); - const std::string mb_name = boost::lexical_cast<std::string>(mb_i); - fs_path mb_root = "/mboards/" + mb_name; - - //sanity checking - validate_subdev_spec(_tree, spec, tx_rx, mb_name); - UHD_ASSERT_THROW(spec.size() <= 2); - if (spec.size() == 1) { - UHD_ASSERT_THROW(spec[0].db_name == "A" || spec[0].db_name == "B"); - } - else if (spec.size() == 2) { - UHD_ASSERT_THROW( - (spec[0].db_name == "A" && spec[1].db_name == "B") || - (spec[0].db_name == "B" && spec[1].db_name == "A") - ); - } - - std::vector<size_t> chan_to_dsp_map(spec.size(), 0); - // setup mux for this spec - for (size_t i = 0; i < spec.size(); i++) - { - const int radio_idx = _mb[mb_i].get_radio_index(spec[i].db_name); - chan_to_dsp_map[i] = radio_idx; - - //extract connection - const fs_path fe_path(mb_root / "dboards" / spec[i].db_name / (tx_rx + "_frontends") / spec[i].sd_name); - const std::string conn = _tree->access<std::string>(fe_path / "connection").get(); - if (tx_rx == "tx") { - //swap condition - _mb[mb_i].radio_perifs[radio_idx].tx_fe->set_mux(conn); - } else { - double if_freq = (_tree->exists(fe_path / "if_freq/value")) ? - _tree->access<double>(fe_path / "if_freq/value").get() : 0.0; - _mb[mb_i].radio_perifs[radio_idx].ddc->set_mux(usrp::fe_connection_t(conn, if_freq)); - _mb[mb_i].radio_perifs[radio_idx].rx_fe->set_mux(false); + if (_mb[mb_index].xport_path != "nirio") { + //For the ethernet transport, the buffer has to be set before creating + //the transport because it is independent of the frame size and # frames + //For nirio, the buffer size is not configurable by the user + #if defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD) + //limit buffer resize on macos or it will error + rx_hints["recv_buff_size"] = boost::lexical_cast<std::string>(X300_RX_SW_BUFF_SIZE_ETH_MACOS); + #elif defined(UHD_PLATFORM_LINUX) || defined(UHD_PLATFORM_WIN32) + //set to half-a-second of buffering at max rate + rx_hints["recv_buff_size"] = boost::lexical_cast<std::string>(X300_RX_SW_BUFF_SIZE_ETH); + #endif } } - - _tree->access<std::vector<size_t> >(mb_root / (tx_rx + "_chan_dsp_mapping")).set(chan_to_dsp_map); + return rx_hints; } -/*********************************************************************** - * RX flow control handler - **********************************************************************/ -static size_t get_rx_flow_control_window(size_t frame_size, size_t sw_buff_size, const device_addr_t& rx_args) +device_addr_t x300_impl::get_tx_hints(size_t mb_index) { - double fullness_factor = rx_args.cast<double>("recv_buff_fullness", X300_RX_SW_BUFF_FULL_FACTOR); - - if (fullness_factor < 0.01 || fullness_factor > 1) { - throw uhd::value_error("recv_buff_fullness must be between 0.01 and 1 inclusive (1% to 100%)"); + device_addr_t tx_hints = _mb[mb_index].send_args; + if (_mb[mb_index].xport_path != "nirio") { + tx_hints["bpp"] = boost::lexical_cast<std::string>(X300_1GE_DATA_FRAME_MAX_SIZE); } - - size_t window_in_pkts = (static_cast<size_t>(sw_buff_size * fullness_factor) / frame_size); - if (window_in_pkts == 0) { - throw uhd::value_error("recv_buff_size must be larger than the recv_frame_size."); - } - return window_in_pkts; + return tx_hints; } -static void handle_rx_flowctrl(const boost::uint32_t sid, zero_copy_if::sptr xport, bool big_endian, boost::shared_ptr<boost::uint32_t> seq32_state, const size_t last_seq) +void x300_impl::post_streamer_hooks(direction_t dir) { - managed_send_buffer::sptr buff = xport->get_send_buff(0.0); - if (not buff) - { - throw uhd::runtime_error("handle_rx_flowctrl timed out getting a send buffer"); - } - boost::uint32_t *pkt = buff->cast<boost::uint32_t *>(); - - //recover seq32 - boost::uint32_t &seq32 = *seq32_state; - const size_t seq12 = seq32 & 0xfff; - if (last_seq < seq12) seq32 += (1 << 12); - seq32 &= ~0xfff; - seq32 |= last_seq; - - //load packet info - vrt::if_packet_info_t packet_info; - packet_info.packet_type = vrt::if_packet_info_t::PACKET_TYPE_CONTEXT; - packet_info.num_payload_words32 = 2; - packet_info.num_payload_bytes = packet_info.num_payload_words32*sizeof(boost::uint32_t); - packet_info.packet_count = seq32; - packet_info.sob = false; - packet_info.eob = false; - packet_info.sid = sid; - packet_info.has_sid = true; - packet_info.has_cid = false; - packet_info.has_tsi = false; - packet_info.has_tsf = false; - packet_info.has_tlr = false; - - //load header - if (big_endian) - vrt::chdr::if_hdr_pack_be(pkt, packet_info); - else - vrt::chdr::if_hdr_pack_le(pkt, packet_info); - - //load payload - pkt[packet_info.num_header_words32+0] = uhd::htonx<boost::uint32_t>(0); - pkt[packet_info.num_header_words32+1] = uhd::htonx<boost::uint32_t>(seq32); - - //send the buffer over the interface - buff->commit(sizeof(boost::uint32_t)*(packet_info.num_packet_words32)); -} - - -/*********************************************************************** - * TX flow control handler - **********************************************************************/ -struct x300_tx_fc_guts_t -{ - x300_tx_fc_guts_t(void): - stream_channel(0), - device_channel(0), - last_seq_out(0), - last_seq_ack(0), - seq_queue(1){} - size_t stream_channel; - size_t device_channel; - size_t last_seq_out; - size_t last_seq_ack; - bounded_buffer<size_t> seq_queue; - boost::shared_ptr<x300_impl::async_md_type> async_queue; - boost::shared_ptr<x300_impl::async_md_type> old_async_queue; -}; - -#define X300_ASYNC_EVENT_CODE_FLOW_CTRL 0 - -/*! - * If the return value of this function is F, the last tx'd packet - * has index N and the last ack'd packet has index M, the amount of - * FC credit we have is C = F + M - N (i.e. we can send C more packets - * before getting another ack). - */ -static size_t get_tx_flow_control_window(size_t frame_size, const bool dram_buff, const device_addr_t& tx_args) -{ - double default_buff_size = dram_buff ? X300_TX_HW_BUFF_SIZE_DRAM : X300_TX_HW_BUFF_SIZE_SRAM; - double hw_buff_size = tx_args.cast<double>("send_buff_size", default_buff_size); - size_t window_in_pkts = (static_cast<size_t>(hw_buff_size) / frame_size); - if (window_in_pkts == 0) { - throw uhd::value_error("send_buff_size must be larger than the send_frame_size."); - } - return window_in_pkts; -} - -static void handle_tx_async_msgs(boost::shared_ptr<x300_tx_fc_guts_t> guts, zero_copy_if::sptr xport, bool big_endian, x300_clock_ctrl::sptr clock) -{ - managed_recv_buffer::sptr buff = xport->get_recv_buff(); - if (not buff) return; - - //extract packet info - vrt::if_packet_info_t if_packet_info; - if_packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); - const boost::uint32_t *packet_buff = buff->cast<const boost::uint32_t *>(); - - //unpacking can fail - boost::uint32_t (*endian_conv)(boost::uint32_t) = uhd::ntohx; - try - { - if (big_endian) - { - vrt::chdr::if_hdr_unpack_be(packet_buff, if_packet_info); - endian_conv = uhd::ntohx; - } - else - { - vrt::chdr::if_hdr_unpack_le(packet_buff, if_packet_info); - endian_conv = uhd::wtohx; - } - } - catch(const std::exception &ex) - { - UHD_MSG(error) << "Error parsing async message packet: " << ex.what() << std::endl; + if (dir != TX_DIRECTION) { return; } - //fill in the async metadata - async_metadata_t metadata; - load_metadata_from_buff( - endian_conv, metadata, if_packet_info, packet_buff, - clock->get_master_clock_rate(), guts->stream_channel); - - //The FC response and the burst ack are two indicators that the radio - //consumed packets. Use them to update the FC metadata - if (metadata.event_code == X300_ASYNC_EVENT_CODE_FLOW_CTRL or - metadata.event_code == async_metadata_t::EVENT_CODE_BURST_ACK - ) { - const size_t seq = metadata.user_payload[0]; - guts->seq_queue.push_with_pop_on_full(seq); - } - - //FC responses don't propagate up to the user so filter them here - if (metadata.event_code != X300_ASYNC_EVENT_CODE_FLOW_CTRL) { - guts->async_queue->push_with_pop_on_full(metadata); - metadata.channel = guts->device_channel; - guts->old_async_queue->push_with_pop_on_full(metadata); - standard_async_msg_prints(metadata); - } -} - -static managed_send_buffer::sptr get_tx_buff_with_flowctrl( - task::sptr /*holds ref*/, - boost::shared_ptr<x300_tx_fc_guts_t> guts, - zero_copy_if::sptr xport, - size_t fc_pkt_window, - const double timeout -){ - while (true) - { - // delta is the amount of FC credit we've used up - const size_t delta = (guts->last_seq_out & 0xfff) - (guts->last_seq_ack & 0xfff); - // If we want to send another packet, we must have FC credit left - if ((delta & 0xfff) < fc_pkt_window) break; - - // If credit is all used up, we check seq_queue for more. - const bool ok = guts->seq_queue.pop_with_timed_wait(guts->last_seq_ack, timeout); - if (not ok) return managed_send_buffer::sptr(); //timeout waiting for flow control - } - - managed_send_buffer::sptr buff = xport->get_send_buff(timeout); - if (buff) { - guts->last_seq_out++; //update seq, this will actually be a send - } - return buff; -} - -/*********************************************************************** - * Async Data - **********************************************************************/ -bool x300_impl::recv_async_msg( - async_metadata_t &async_metadata, double timeout -){ - return _async_md->pop_with_timed_wait(async_metadata, timeout); -} - -/*********************************************************************** - * Receive streamer - **********************************************************************/ -rx_streamer::sptr x300_impl::get_rx_stream(const uhd::stream_args_t &args_) -{ - boost::mutex::scoped_lock lock(_transport_setup_mutex); - stream_args_t args = args_; - - //setup defaults for unspecified values - if (not args.otw_format.empty() and args.otw_format != "sc16") - { - throw uhd::value_error("x300_impl::get_rx_stream only supports otw_format sc16"); - } - args.otw_format = "sc16"; - args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; - - boost::shared_ptr<sph::recv_packet_streamer> my_streamer; - for (size_t stream_i = 0; stream_i < args.channels.size(); stream_i++) - { - // Find the mainboard and subdev that corresponds to channel args.channels[stream_i] - const size_t chan = args.channels[stream_i]; - size_t mb_chan = chan, mb_index; - for (mb_index = 0; mb_index < _mb.size(); mb_index++) { - const subdev_spec_t &curr_subdev_spec = - _tree->access<subdev_spec_t>("/mboards/" + boost::lexical_cast<std::string>(mb_index) / "rx_subdev_spec").get(); - if (mb_chan < curr_subdev_spec.size()) { - break; - } else { - mb_chan -= curr_subdev_spec.size(); - } + // Loop through all tx streamers. Find all radios connected to one + // streamer. Sync those. + BOOST_FOREACH(const boost::weak_ptr<uhd::tx_streamer> &streamer_w, _tx_streamers.vals()) { + const boost::shared_ptr<sph::send_packet_streamer> streamer = + boost::dynamic_pointer_cast<sph::send_packet_streamer>(streamer_w.lock()); + if (not streamer) { + continue; } - // Find the DSP that corresponds to this mainboard and subdev - UHD_ASSERT_THROW(mb_index < _mb.size()); - mboard_members_t &mb = _mb[mb_index]; - const std::vector<size_t> dsp_map = _tree->access<std::vector<size_t> >("/mboards/" + boost::lexical_cast<std::string>(mb_index) / "rx_chan_dsp_mapping") - .get(); //.at(mb_chan); - UHD_ASSERT_THROW(mb_chan < dsp_map.size()); - const size_t radio_index = dsp_map[mb_chan]; - UHD_ASSERT_THROW(radio_index < 2); - radio_perifs_t &perif = mb.radio_perifs[radio_index]; - - //setup the dsp transport hints (default to a large recv buff) - device_addr_t device_addr = mb.recv_args; - if (not device_addr.has_key("recv_buff_size")) - { - if (mb.xport_path != "nirio") { - //For the ethernet transport, the buffer has to be set before creating - //the transport because it is independent of the frame size and # frames - //For nirio, the buffer size is not configurable by the user - #if defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD) - //limit buffer resize on macos or it will error - device_addr["recv_buff_size"] = boost::lexical_cast<std::string>(X300_RX_SW_BUFF_SIZE_ETH_MACOS); - #elif defined(UHD_PLATFORM_LINUX) || defined(UHD_PLATFORM_WIN32) - //set to half-a-second of buffering at max rate - device_addr["recv_buff_size"] = boost::lexical_cast<std::string>(X300_RX_SW_BUFF_SIZE_ETH); - #endif - } + std::vector<rfnoc::x300_radio_ctrl_impl::sptr> radio_ctrl_blks = + streamer->get_terminator()->find_downstream_node<rfnoc::x300_radio_ctrl_impl>(); + try { + //UHD_MSG(status) << "[X300] syncing " << radio_ctrl_blks.size() << " radios " << std::endl; + rfnoc::x300_radio_ctrl_impl::synchronize_dacs(radio_ctrl_blks); } - - //allocate sid and create transport - boost::uint8_t dest = (radio_index == 0)? X300_XB_DST_R0 : X300_XB_DST_R1; - boost::uint32_t data_sid; - UHD_LOG << "creating rx stream " << device_addr.to_string() << std::endl; - both_xports_t xport = this->make_transport(mb_index, dest, X300_RADIO_DEST_PREFIX_RX, device_addr, data_sid); - UHD_LOG << boost::format("data_sid = 0x%08x, actual recv_buff_size = %d\n") % data_sid % xport.recv_buff_size << std::endl; - - // To calculate the max number of samples per packet, we assume the maximum header length - // to avoid fragmentation should the entire header be used. - const size_t bpp = xport.recv->get_recv_frame_size() - X300_RX_MAX_HDR_LEN; // bytes per packet - const size_t bpi = convert::get_bytes_per_item(args.otw_format); // bytes per item - const size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi)); // samples per packet - - //make the new streamer given the samples per packet - if (not my_streamer) my_streamer = boost::make_shared<sph::recv_packet_streamer>(spp); - my_streamer->resize(args.channels.size()); - - //init some streamer stuff - std::string conv_endianness; - if (mb.if_pkt_is_big_endian) { - my_streamer->set_vrt_unpacker(&vrt::chdr::if_hdr_unpack_be); - conv_endianness = "be"; - } else { - my_streamer->set_vrt_unpacker(&vrt::chdr::if_hdr_unpack_le); - conv_endianness = "le"; + catch(const uhd::io_error &ex) { + throw uhd::io_error(str(boost::format("Failed to sync DACs! %s ") % ex.what())); } - - //set the converter - uhd::convert::id_type id; - id.input_format = args.otw_format + "_item32_" + conv_endianness; - id.num_inputs = 1; - id.output_format = args.cpu_format; - id.num_outputs = 1; - my_streamer->set_converter(id); - - perif.framer->clear(); - perif.framer->set_nsamps_per_packet(spp); //seems to be a good place to set this - perif.framer->set_sid((data_sid << 16) | (data_sid >> 16)); - perif.framer->setup(args); - perif.ddc->setup(args); - - //flow control setup - const size_t fc_window = get_rx_flow_control_window(xport.recv->get_recv_frame_size(), xport.recv_buff_size, device_addr); - const size_t fc_handle_window = std::max<size_t>(1, fc_window / X300_RX_FC_REQUEST_FREQ); - - UHD_LOG << "RX Flow Control Window = " << fc_window << ", RX Flow Control Handler Window = " << fc_handle_window << std::endl; - - perif.framer->configure_flow_control(fc_window); - - boost::shared_ptr<boost::uint32_t> seq32(new boost::uint32_t(0)); - //Give the streamer a functor to get the recv_buffer - //bind requires a zero_copy_if::sptr to add a streamer->xport lifetime dependency - my_streamer->set_xport_chan_get_buff( - stream_i, - boost::bind(&zero_copy_if::get_recv_buff, xport.recv, _1), - true /*flush*/ - ); - //Give the streamer a functor to handle overflows - //bind requires a weak_ptr to break the a streamer->streamer circular dependency - //Using "this" is OK because we know that x300_impl will outlive the streamer - my_streamer->set_overflow_handler( - stream_i, - boost::bind(&x300_impl::handle_overflow, this, boost::ref(perif), boost::weak_ptr<uhd::rx_streamer>(my_streamer)) - ); - //Give the streamer a functor to send flow control messages - //handle_rx_flowctrl is static and has no lifetime issues - my_streamer->set_xport_handle_flowctrl( - stream_i, boost::bind(&handle_rx_flowctrl, data_sid, xport.send, mb.if_pkt_is_big_endian, seq32, _1), - fc_handle_window, - true/*init*/ - ); - //Give the streamer a functor issue stream cmd - //bind requires a rx_vita_core_3000::sptr to add a streamer->framer lifetime dependency - my_streamer->set_issue_stream_cmd( - stream_i, boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1) - ); - - //Store a weak pointer to prevent a streamer->x300_impl->streamer circular dependency - mb.rx_streamers[radio_index] = boost::weak_ptr<sph::recv_packet_streamer>(my_streamer); - - //sets all tick and samp rates on this streamer - const fs_path mb_path = "/mboards/"+boost::lexical_cast<std::string>(mb_index); - _tree->access<double>(mb_path / "tick_rate").update(); - _tree->access<double>(mb_path / "rx_dsps" / boost::lexical_cast<std::string>(radio_index) / "rate" / "value").update(); } - - return my_streamer; } -void x300_impl::handle_overflow(x300_impl::radio_perifs_t &perif, boost::weak_ptr<uhd::rx_streamer> streamer) -{ - boost::shared_ptr<sph::recv_packet_streamer> my_streamer = - boost::dynamic_pointer_cast<sph::recv_packet_streamer>(streamer.lock()); - if (not my_streamer) return; //If the rx_streamer has expired then overflow handling makes no sense. - - if (my_streamer->get_num_channels() == 1) - { - perif.framer->handle_overflow(); - return; - } - - ///////////////////////////////////////////////////////////// - // MIMO overflow recovery time - ///////////////////////////////////////////////////////////// - //find out if we were in continuous mode before stopping - const bool in_continuous_streaming_mode = perif.framer->in_continuous_streaming_mode(); - //stop streaming - my_streamer->issue_stream_cmd(stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); - //flush transports - my_streamer->flush_all(0.001); - //restart streaming - if (in_continuous_streaming_mode) - { - stream_cmd_t stream_cmd(stream_cmd_t::STREAM_MODE_START_CONTINUOUS); - stream_cmd.stream_now = false; - stream_cmd.time_spec = perif.time64->get_time_now() + time_spec_t(0.01); - my_streamer->issue_stream_cmd(stream_cmd); - } -} - -/*********************************************************************** - * Transmit streamer - **********************************************************************/ -tx_streamer::sptr x300_impl::get_tx_stream(const uhd::stream_args_t &args_) -{ - boost::mutex::scoped_lock lock(_transport_setup_mutex); - stream_args_t args = args_; - - //setup defaults for unspecified values - if (not args.otw_format.empty() and args.otw_format != "sc16") - { - throw uhd::value_error("x300_impl::get_tx_stream only supports otw_format sc16"); - } - args.otw_format = "sc16"; - args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; - - //shared async queue for all channels in streamer - boost::shared_ptr<async_md_type> async_md(new async_md_type(1000/*messages deep*/)); - - std::vector<radio_perifs_t*> radios_list; - boost::shared_ptr<sph::send_packet_streamer> my_streamer; - for (size_t stream_i = 0; stream_i < args.channels.size(); stream_i++) - { - // Find the mainboard and subdev that corresponds to channel args.channels[stream_i] - const size_t chan = args.channels[stream_i]; - size_t mb_chan = chan, mb_index; - for (mb_index = 0; mb_index < _mb.size(); mb_index++) { - const subdev_spec_t &curr_subdev_spec = - _tree->access<subdev_spec_t>("/mboards/" + boost::lexical_cast<std::string>(mb_index) / "tx_subdev_spec").get(); - if (mb_chan < curr_subdev_spec.size()) { - break; - } else { - mb_chan -= curr_subdev_spec.size(); - } - } - // Find the DSP that corresponds to this mainboard and subdev - mboard_members_t &mb = _mb[mb_index]; - const size_t radio_index = _tree->access<std::vector<size_t> >("/mboards/" + boost::lexical_cast<std::string>(mb_index) / "tx_chan_dsp_mapping") - .get().at(mb_chan); - radio_perifs_t &perif = mb.radio_perifs[radio_index]; - radios_list.push_back(&perif); - - //setup the dsp transport hints (TODO) - device_addr_t device_addr = mb.send_args; - - //allocate sid and create transport - boost::uint8_t dest = (radio_index == 0)? X300_XB_DST_R0 : X300_XB_DST_R1; - boost::uint32_t data_sid; - UHD_LOG << "creating tx stream " << device_addr.to_string() << std::endl; - both_xports_t xport = this->make_transport(mb_index, dest, X300_RADIO_DEST_PREFIX_TX, device_addr, data_sid); - UHD_LOG << boost::format("data_sid = 0x%08x\n") % data_sid << std::endl; - - // To calculate the max number of samples per packet, we assume the maximum header length - // to avoid fragmentation should the entire header be used. - const size_t bpp = xport.send->get_send_frame_size() - X300_TX_MAX_HDR_LEN; - const size_t bpi = convert::get_bytes_per_item(args.otw_format); - const size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi)); - - //make the new streamer given the samples per packet - if (not my_streamer) my_streamer = boost::make_shared<sph::send_packet_streamer>(spp); - my_streamer->resize(args.channels.size()); - - std::string conv_endianness; - if (mb.if_pkt_is_big_endian) { - my_streamer->set_vrt_packer(&vrt::chdr::if_hdr_pack_be); - conv_endianness = "be"; - } else { - my_streamer->set_vrt_packer(&vrt::chdr::if_hdr_pack_le); - conv_endianness = "le"; - } - - //set the converter - uhd::convert::id_type id; - id.input_format = args.cpu_format; - id.num_inputs = 1; - id.output_format = args.otw_format + "_item32_" + conv_endianness; - id.num_outputs = 1; - my_streamer->set_converter(id); - - perif.deframer->clear(); - perif.deframer->setup(args); - perif.duc->setup(args); - - //flow control setup - size_t fc_window = get_tx_flow_control_window(xport.send->get_send_frame_size(), mb.has_dram_buff, device_addr); //In packets - const size_t fc_handle_window = std::max<size_t>(1, - fc_window/ (mb.has_dram_buff ? X300_TX_FC_RESPONSE_FREQ_DRAM : X300_TX_FC_RESPONSE_FREQ_SRAM)); - - UHD_LOG << "TX Flow Control Window = " << fc_window << ", TX Flow Control Handler Window = " << fc_handle_window << std::endl; - - perif.deframer->configure_flow_control(0/*cycs off*/, fc_handle_window); - boost::shared_ptr<x300_tx_fc_guts_t> guts(new x300_tx_fc_guts_t()); - guts->stream_channel = stream_i; - guts->device_channel = chan; - guts->async_queue = async_md; - guts->old_async_queue = _async_md; - task::sptr task = task::make(boost::bind(&handle_tx_async_msgs, guts, xport.recv, mb.if_pkt_is_big_endian, mb.clock)); - - //Give the streamer a functor to get the send buffer - //get_tx_buff_with_flowctrl is static so bind has no lifetime issues - //xport.send (sptr) is required to add streamer->data-transport lifetime dependency - //task (sptr) is required to add a streamer->async-handler lifetime dependency - my_streamer->set_xport_chan_get_buff( - stream_i, - boost::bind(&get_tx_buff_with_flowctrl, task, guts, xport.send, fc_window, _1) - ); - //Give the streamer a functor handled received async messages - my_streamer->set_async_receiver( - boost::bind(&async_md_type::pop_with_timed_wait, async_md, _1, _2) - ); - my_streamer->set_xport_chan_sid(stream_i, true, data_sid); - my_streamer->set_enable_trailer(false); //TODO not implemented trailer support yet - - //Store a weak pointer to prevent a streamer->x300_impl->streamer circular dependency - mb.tx_streamers[radio_index] = boost::weak_ptr<sph::send_packet_streamer>(my_streamer); - - //sets all tick and samp rates on this streamer - const fs_path mb_path = "/mboards/"+boost::lexical_cast<std::string>(mb_index); - _tree->access<double>(mb_path / "tick_rate").update(); - _tree->access<double>(mb_path / "tx_dsps" / boost::lexical_cast<std::string>(radio_index) / "rate" / "value").update(); - } - - synchronize_dacs(radios_list); - return my_streamer; -} +// vim: sw=4 expandtab: diff --git a/host/lib/usrp/x300/x300_radio_ctrl_impl.cpp b/host/lib/usrp/x300/x300_radio_ctrl_impl.cpp new file mode 100644 index 000000000..3a129b334 --- /dev/null +++ b/host/lib/usrp/x300/x300_radio_ctrl_impl.cpp @@ -0,0 +1,888 @@ +// +// Copyright 2015-2016 Ettus Research +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "x300_radio_ctrl_impl.hpp" + +#include "x300_dboard_iface.hpp" +#include "wb_iface_adapter.hpp" +#include "gpio_atr_3000.hpp" +#include "apply_corrections.hpp" +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/usrp/dboard_iface.hpp> +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/transport/chdr.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/make_shared.hpp> +#include <boost/date_time/posix_time/posix_time_io.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; +using namespace uhd::usrp::x300; + +static const size_t IO_MASTER_RADIO = 0; + +/**************************************************************************** + * Structors + ***************************************************************************/ +UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR(x300_radio_ctrl) + , _ignore_cal_file(false) +{ + UHD_RFNOC_BLOCK_TRACE() << "x300_radio_ctrl_impl::ctor() " << std::endl; + + //////////////////////////////////////////////////////////////////// + // Set up basic info + //////////////////////////////////////////////////////////////////// + _radio_type = (get_block_id().get_block_count() == 0) ? PRIMARY : SECONDARY; + _radio_slot = (get_block_id().get_block_count() == 0) ? "A" : "B"; + _radio_clk_rate = _tree->access<double>("master_clock_rate").get(); + + //////////////////////////////////////////////////////////////////// + // Set up peripherals + //////////////////////////////////////////////////////////////////// + wb_iface::sptr ctrl = _get_ctrl(IO_MASTER_RADIO); + _regs = boost::make_shared<radio_regmap_t>(_radio_type==PRIMARY?0:1); + _regs->initialize(*ctrl, true); + + //Only Radio0 has the ADC/DAC reset bits. Those bits are reserved for Radio1 + if (_radio_type==PRIMARY) { + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0); + _regs->misc_outs_reg.flush(); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 0); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 1); + _regs->misc_outs_reg.flush(); + } + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 1); + + //////////////////////////////////////////////////////////////// + // Setup peripherals + //////////////////////////////////////////////////////////////// + _spi = spi_core_3000::make(ctrl, + radio_ctrl_impl::regs::sr_addr(radio_ctrl_impl::regs::SPI), + radio_ctrl_impl::regs::RB_SPI); + _leds = gpio_atr::gpio_atr_3000::make_write_only(ctrl, regs::sr_addr(regs::LEDS)); + _leds->set_atr_mode(usrp::gpio_atr::MODE_ATR, usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL); + _adc = x300_adc_ctrl::make(_spi, DB_ADC_SEN); + _dac = x300_dac_ctrl::make(_spi, DB_DAC_SEN, _radio_clk_rate); + + if (_radio_type==PRIMARY) { + _fp_gpio = gpio_atr::gpio_atr_3000::make(ctrl, regs::sr_addr(regs::FP_GPIO), regs::RB_FP_GPIO); + BOOST_FOREACH(const gpio_atr::gpio_attr_map_t::value_type attr, gpio_atr::gpio_attr_map) { + _tree->create<boost::uint32_t>(fs_path("gpio") / "FP0" / attr.second) + .set(0) + .add_coerced_subscriber(boost::bind(&gpio_atr::gpio_atr_3000::set_gpio_attr, _fp_gpio, attr.first, _1)); + } + _tree->create<boost::uint32_t>(fs_path("gpio") / "FP0" / "READBACK") + .set_publisher(boost::bind(&gpio_atr::gpio_atr_3000::read_gpio, _fp_gpio)); + } + + //////////////////////////////////////////////////////////////// + // create legacy codec control objects + //////////////////////////////////////////////////////////////// + _tree->create<int>("rx_codecs" / _radio_slot / "gains"); //phony property so this dir exists + _tree->create<int>("tx_codecs" / _radio_slot / "gains"); //phony property so this dir exists + _tree->create<std::string>("rx_codecs" / _radio_slot / "name").set("ads62p48"); + _tree->create<std::string>("tx_codecs" / _radio_slot / "name").set("ad9146"); + + _tree->create<meta_range_t>("rx_codecs" / _radio_slot / "gains" / "digital" / "range").set(meta_range_t(0, 6.0, 0.5)); + _tree->create<double>("rx_codecs" / _radio_slot / "gains" / "digital" / "value") + .add_coerced_subscriber(boost::bind(&x300_adc_ctrl::set_gain, _adc, _1)).set(0) + ; + + //////////////////////////////////////////////////////////////// + // create front-end objects + //////////////////////////////////////////////////////////////// + for (size_t i = 0; i < _get_num_radios(); i++) { + _rx_fe_map[i].core = rx_frontend_core_3000::make(_get_ctrl(i), regs::sr_addr(x300_regs::RX_RE_BASE)); + _rx_fe_map[i].core->set_adc_rate(_radio_clk_rate); + _rx_fe_map[i].core->set_dc_offset(rx_frontend_core_3000::DEFAULT_DC_OFFSET_VALUE); + _rx_fe_map[i].core->set_dc_offset_auto(rx_frontend_core_3000::DEFAULT_DC_OFFSET_ENABLE); + _rx_fe_map[i].core->populate_subtree(_tree->subtree(_root_path / "rx_fe_corrections" / i)); + + _tx_fe_map[i].core = tx_frontend_core_200::make(_get_ctrl(i), regs::sr_addr(x300_regs::TX_FE_BASE)); + _tx_fe_map[i].core->set_dc_offset(tx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); + _tx_fe_map[i].core->set_iq_balance(tx_frontend_core_200::DEFAULT_IQ_BALANCE_VALUE); + _tx_fe_map[i].core->populate_subtree(_tree->subtree(_root_path / "tx_fe_corrections" / i)); + } + + //////////////////////////////////////////////////////////////// + // Update default SPP (overwrites the default value from the XML file) + //////////////////////////////////////////////////////////////// + const size_t max_bytes_header = uhd::transport::vrt::chdr::max_if_hdr_words64 * sizeof(uint64_t); + const size_t default_spp = (_tree->access<size_t>("mtu/recv").get() - max_bytes_header) + / (2 * sizeof(int16_t)); + _tree->access<int>(get_arg_path("spp") / "value").set(default_spp); +} + +x300_radio_ctrl_impl::~x300_radio_ctrl_impl() +{ + // Tear down our part of the tree: + _tree->remove(fs_path("rx_codecs" / _radio_slot)); + _tree->remove(fs_path("tx_codecs" / _radio_slot)); + _tree->remove(_root_path / "rx_fe_corrections"); + _tree->remove(_root_path / "tx_fe_corrections"); + if (_radio_type==PRIMARY) { + BOOST_FOREACH(const gpio_atr::gpio_attr_map_t::value_type attr, gpio_atr::gpio_attr_map) { + _tree->remove(fs_path("gpio") / "FP0" / attr.second); + } + _tree->remove(fs_path("gpio") / "FP0" / "READBACK"); + } + + // Reset peripherals + if (_radio_type==PRIMARY) { + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0); + } + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 0); + _regs->misc_outs_reg.flush(); +} + +/**************************************************************************** + * API calls + ***************************************************************************/ +double x300_radio_ctrl_impl::set_rate(double /* rate */) +{ + // On X3x0, tick rate can't actually be changed at runtime + return get_rate(); +} + +void x300_radio_ctrl_impl::set_tx_antenna(const std::string &ant, const size_t chan) +{ + _tree->access<std::string>( + fs_path("dboards" / _radio_slot / "tx_frontends" / _tx_fe_map.at(chan).db_fe_name / "antenna" / "value") + ).set(ant); +} + +void x300_radio_ctrl_impl::set_rx_antenna(const std::string &ant, const size_t chan) +{ + _tree->access<std::string>( + fs_path("dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name / "antenna" / "value") + ).set(ant); +} + +double x300_radio_ctrl_impl::set_tx_frequency(const double freq, const size_t chan) +{ + return _tree->access<double>( + fs_path("dboards" / _radio_slot / "tx_frontends" / _tx_fe_map.at(chan).db_fe_name / "freq" / "value") + ).set(freq).get(); +} + +double x300_radio_ctrl_impl::get_tx_frequency(const size_t chan) +{ + return _tree->access<double>( + fs_path("dboards" / _radio_slot / "tx_frontends" / _tx_fe_map.at(chan).db_fe_name / "freq" / "value") + ).get(); +} + +double x300_radio_ctrl_impl::set_rx_frequency(const double freq, const size_t chan) +{ + return _tree->access<double>( + fs_path("dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name / "freq" / "value") + ).set(freq).get(); +} + +double x300_radio_ctrl_impl::get_rx_frequency(const size_t chan) +{ + return _tree->access<double>( + fs_path("dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name / "freq" / "value") + ).get(); +} + +double x300_radio_ctrl_impl::set_tx_gain(const double gain, const size_t chan) +{ + //TODO: This is extremely hacky! + fs_path path("dboards" / _radio_slot / "tx_frontends" / _tx_fe_map.at(chan).db_fe_name / "gains"); + std::vector<std::string> gain_stages = _tree->list(path); + if (gain_stages.size() == 1) { + const double actual_gain = _tree->access<double>(path / gain_stages[0] / "value").set(gain).get(); + radio_ctrl_impl::set_tx_gain(actual_gain, chan); + return gain; + } else { + UHD_MSG(warning) << "set_tx_gain: could not apply gain for this daughterboard."; + radio_ctrl_impl::set_tx_gain(0.0, chan); + return 0.0; + } +} + +double x300_radio_ctrl_impl::set_rx_gain(const double gain, const size_t chan) +{ + //TODO: This is extremely hacky! + fs_path path("dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name / "gains"); + std::vector<std::string> gain_stages = _tree->list(path); + if (gain_stages.size() == 1) { + const double actual_gain = _tree->access<double>(path / gain_stages[0] / "value").set(gain).get(); + radio_ctrl_impl::set_rx_gain(actual_gain, chan); + return gain; + } else { + UHD_MSG(warning) << "set_rx_gain: could not apply gain for this daughterboard."; + radio_ctrl_impl::set_tx_gain(0.0, chan); + return 0.0; + } +} + + +template <typename map_type> +static size_t _get_chan_from_map(std::map<size_t, map_type> map, const std::string &fe) +{ + // TODO replace with 'auto' when possible + typedef typename std::map<size_t, map_type>::iterator chan_iterator; + for (chan_iterator it = map.begin(); it != map.end(); ++it) { + if (it->second.db_fe_name == fe) { + return it->first; + } + + } + throw uhd::runtime_error(str( + boost::format("Invalid daughterboard frontend name: %s") + % fe + )); +} + +size_t x300_radio_ctrl_impl::get_chan_from_dboard_fe(const std::string &fe, const uhd::direction_t direction) +{ + switch (direction) { + case uhd::TX_DIRECTION: + return _get_chan_from_map(_tx_fe_map, fe); + case uhd::RX_DIRECTION: + return _get_chan_from_map(_rx_fe_map, fe); + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +std::string x300_radio_ctrl_impl::get_dboard_fe_from_chan(const size_t chan, const uhd::direction_t direction) +{ + switch (direction) { + case uhd::TX_DIRECTION: + return _tx_fe_map.at(chan).db_fe_name; + case uhd::RX_DIRECTION: + return _rx_fe_map.at(chan).db_fe_name; + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +double x300_radio_ctrl_impl::get_output_samp_rate(size_t chan) +{ + // TODO: chan should never be ANY_PORT, but due to our current graph search + // method, this can actually happen: + if (chan == ANY_PORT) { + chan = 0; + for (size_t i = 0; i < _get_num_radios(); i++) { + if (_is_streamer_active(uhd::RX_DIRECTION, chan)) { + chan = i; + break; + } + } + } + return _rx_fe_map.at(chan).core->get_output_rate(); +} + +/**************************************************************************** + * Radio control and setup + ***************************************************************************/ +void x300_radio_ctrl_impl::setup_radio(uhd::i2c_iface::sptr zpu_i2c, x300_clock_ctrl::sptr clock, bool verbose) +{ + _self_cal_adc_capture_delay(verbose); + + //////////////////////////////////////////////////////////////////// + // create RF frontend interfacing + //////////////////////////////////////////////////////////////////// + static const size_t BASE_ADDR = 0x50; + static const size_t RX_EEPROM_ADDR = 0x5; + static const size_t TX_EEPROM_ADDR = 0x4; + static const size_t GDB_EEPROM_ADDR = 0x1; + const static std::vector<size_t> EEPROM_ADDRS = + boost::assign::list_of(RX_EEPROM_ADDR)(TX_EEPROM_ADDR)(GDB_EEPROM_ADDR); + const static std::vector<std::string> EEPROM_PATHS = + boost::assign::list_of("rx_eeprom")("tx_eeprom")("gdb_eeprom"); + + const size_t DB_OFFSET = (_radio_slot == "A") ? 0x0 : 0x2; + const fs_path db_path = ("dboards" / _radio_slot); + for (size_t i = 0; i < EEPROM_ADDRS.size(); i++) { + const size_t addr = EEPROM_ADDRS[i] + DB_OFFSET; + //Load EEPROM + _db_eeproms[addr].load(*zpu_i2c, BASE_ADDR | addr); + //Add to tree + _tree->create<dboard_eeprom_t>(db_path / EEPROM_PATHS[i]) + .set(_db_eeproms[addr]) + .add_coerced_subscriber(boost::bind(&dboard_eeprom_t::store, + _db_eeproms[addr], boost::ref(*zpu_i2c), (BASE_ADDR | addr))); + } + + //create a new dboard interface + x300_dboard_iface_config_t db_config; + db_config.gpio = gpio_atr::db_gpio_atr_3000::make(_get_ctrl(IO_MASTER_RADIO), + radio_ctrl_impl::regs::sr_addr(radio_ctrl_impl::regs::GPIO), radio_ctrl_impl::regs::RB_DB_GPIO); + db_config.spi = _spi; + db_config.rx_spi_slaveno = DB_RX_SEN; + db_config.tx_spi_slaveno = DB_TX_SEN; + db_config.i2c = zpu_i2c; + db_config.clock = clock; + db_config.which_rx_clk = (_radio_slot == "A") ? X300_CLOCK_WHICH_DB0_RX : X300_CLOCK_WHICH_DB1_RX; + db_config.which_tx_clk = (_radio_slot == "A") ? X300_CLOCK_WHICH_DB0_TX : X300_CLOCK_WHICH_DB1_TX; + db_config.dboard_slot = (_radio_slot == "A")? 0 : 1; + db_config.cmd_time_ctrl = _get_ctrl(IO_MASTER_RADIO); + + //create a new dboard manager + boost::shared_ptr<x300_dboard_iface> db_iface = boost::make_shared<x300_dboard_iface>(db_config); + _db_manager = dboard_manager::make( + _db_eeproms[RX_EEPROM_ADDR + DB_OFFSET].id, + _db_eeproms[TX_EEPROM_ADDR + DB_OFFSET].id, + _db_eeproms[GDB_EEPROM_ADDR + DB_OFFSET].id, + db_iface, _tree->subtree(db_path), + true // defer daughterboard intitialization + ); + + size_t rx_chan = 0, tx_chan = 0; + BOOST_FOREACH(const std::string& fe, _db_manager->get_rx_frontends()) { + if (rx_chan >= _get_num_radios()) { + break; + } + _rx_fe_map[rx_chan].db_fe_name = fe; + db_iface->add_rx_fe(fe, _rx_fe_map[rx_chan].core); + const fs_path fe_path(db_path / "rx_frontends" / fe); + const std::string conn = _tree->access<std::string>(fe_path / "connection").get(); + const double if_freq = (_tree->exists(fe_path / "if_freq/value")) ? + _tree->access<double>(fe_path / "if_freq/value").get() : 0.0; + _rx_fe_map[rx_chan].core->set_fe_connection(usrp::fe_connection_t(conn, if_freq)); + rx_chan++; + } + BOOST_FOREACH(const std::string& fe, _db_manager->get_tx_frontends()) { + if (tx_chan >= _get_num_radios()) { + break; + } + _tx_fe_map[tx_chan].db_fe_name = fe; + const fs_path fe_path(db_path / "tx_frontends" / fe); + const std::string conn = _tree->access<std::string>(fe_path / "connection").get(); + _tx_fe_map[tx_chan].core->set_mux(conn); + tx_chan++; + } + UHD_ASSERT_THROW(rx_chan or tx_chan); + + // Initialize the daughterboards now that frontend cores and connections exist + _db_manager->initialize_dboards(); + + //now that dboard is created -- register into rx antenna event + if (not _rx_fe_map.empty() + and _tree->exists(db_path / "rx_frontends" / _rx_fe_map[0].db_fe_name / "antenna" / "value")) { + _tree->access<std::string>(db_path / "rx_frontends" / _rx_fe_map[0].db_fe_name / "antenna" / "value") + .add_coerced_subscriber(boost::bind(&x300_radio_ctrl_impl::_update_atr_leds, this, _1)); + } + _update_atr_leds(""); //init anyway, even if never called + + //bind frontend corrections to the dboard freq props + const fs_path db_tx_fe_path = db_path / "tx_frontends"; + BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)) { + _tree->access<double>(db_tx_fe_path / name / "freq" / "value") + .add_coerced_subscriber(boost::bind(&x300_radio_ctrl_impl::set_tx_fe_corrections, this, _radio_slot, _1)); + } + const fs_path db_rx_fe_path = db_path / "rx_frontends"; + BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)) { + _tree->access<double>(db_rx_fe_path / name / "freq" / "value") + .add_coerced_subscriber(boost::bind(&x300_radio_ctrl_impl::set_rx_fe_corrections, this, _radio_slot, _1)); + } + + //////////////////////////////////////////////////////////////// + // Set tick rate + //////////////////////////////////////////////////////////////// + const double tick_rate = get_output_samp_rate(0); + if (_radio_type==PRIMARY) { + // Slot A is the highlander timekeeper + _tree->access<double>("tick_rate").set(tick_rate); + } + radio_ctrl_impl::set_rate(tick_rate); +} + +void x300_radio_ctrl_impl::set_rx_fe_corrections( + const std::string &slot_name, + const double lo_freq +) { + if (not _ignore_cal_file) { + apply_rx_fe_corrections(_tree, slot_name, lo_freq); + } +} + +void x300_radio_ctrl_impl::set_tx_fe_corrections( + const std::string &slot_name, + const double lo_freq +) { + if (not _ignore_cal_file) { + apply_tx_fe_corrections(_tree, slot_name, lo_freq); + } +} + +void x300_radio_ctrl_impl::reset_codec() +{ + if (_radio_type==PRIMARY) { //ADC/DAC reset lines only exist in Radio0 + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0); + _regs->misc_outs_reg.flush(); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 0); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 1); + _regs->misc_outs_reg.flush(); + } + UHD_ASSERT_THROW(bool(_adc)); + UHD_ASSERT_THROW(bool(_dac)); + _adc->reset(); + _dac->reset(); +} + +void x300_radio_ctrl_impl::self_test_adc(boost::uint32_t ramp_time_ms) +{ + //Bypass all front-end corrections + for (size_t i = 0; i < _get_num_radios(); i++) { + _rx_fe_map[i].core->bypass_all(true); + } + + //Test basic patterns + _adc->set_test_word("ones", "ones"); _check_adc(0xfffcfffc); + _adc->set_test_word("zeros", "zeros"); _check_adc(0x00000000); + _adc->set_test_word("ones", "zeros"); _check_adc(0xfffc0000); + _adc->set_test_word("zeros", "ones"); _check_adc(0x0000fffc); + for (size_t k = 0; k < 14; k++) { + _adc->set_test_word("zeros", "custom", 1 << k); + _check_adc(1 << (k+2)); + } + for (size_t k = 0; k < 14; k++) { + _adc->set_test_word("custom", "zeros", 1 << k); + _check_adc(1 << (k+18)); + } + + //Turn on ramp pattern test + _adc->set_test_word("ramp", "ramp"); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + //Sleep added for SPI transactions to finish and ramp to start before checker is enabled. + boost::this_thread::sleep(boost::posix_time::microsec(1000)); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1); + + boost::this_thread::sleep(boost::posix_time::milliseconds(ramp_time_ms)); + _regs->misc_ins_reg.refresh(); + + std::string i_status, q_status; + if (_regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_LOCKED)) + if (_regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_ERROR)) + i_status = "Bit Errors!"; + else + i_status = "Good"; + else + i_status = "Not Locked!"; + + if (_regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_LOCKED)) + if (_regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_ERROR)) + q_status = "Bit Errors!"; + else + q_status = "Good"; + else + q_status = "Not Locked!"; + + //Return to normal mode + _adc->set_test_word("normal", "normal"); + + if ((i_status != "Good") or (q_status != "Good")) { + throw uhd::runtime_error( + (boost::format("ADC self-test failed for %s. Ramp checker status: {ADC_A=%s, ADC_B=%s}")%unique_id()%i_status%q_status).str()); + } + + //Restore front-end corrections + for (size_t i = 0; i < _get_num_radios(); i++) { + _rx_fe_map[i].core->bypass_all(false); + } +} + +void x300_radio_ctrl_impl::extended_adc_test(const std::vector<x300_radio_ctrl_impl::sptr>& radios, double duration_s) +{ + static const size_t SECS_PER_ITER = 5; + UHD_MSG(status) << boost::format("Running Extended ADC Self-Test (Duration=%.0fs, %ds/iteration)...\n") + % duration_s % SECS_PER_ITER; + + size_t num_iters = static_cast<size_t>(ceil(duration_s/SECS_PER_ITER)); + size_t num_failures = 0; + for (size_t iter = 0; iter < num_iters; iter++) { + //Print date and time + boost::posix_time::time_facet *facet = new boost::posix_time::time_facet("%d-%b-%Y %H:%M:%S"); + std::ostringstream time_strm; + time_strm.imbue(std::locale(std::locale::classic(), facet)); + time_strm << boost::posix_time::second_clock::local_time(); + //Run self-test + UHD_MSG(status) << boost::format("-- [%s] Iteration %06d... ") % time_strm.str() % (iter+1); + try { + for (size_t i = 0; i < radios.size(); i++) { + radios[i]->self_test_adc((SECS_PER_ITER*1000)/radios.size()); + } + UHD_MSG(status) << "passed" << std::endl; + } catch(std::exception &e) { + num_failures++; + UHD_MSG(status) << e.what() << std::endl; + } + } + if (num_failures == 0) { + UHD_MSG(status) << "Extended ADC Self-Test PASSED\n"; + } else { + throw uhd::runtime_error( + (boost::format("Extended ADC Self-Test FAILED!!! (%d/%d failures)\n") % num_failures % num_iters).str()); + } +} + +void x300_radio_ctrl_impl::synchronize_dacs(const std::vector<x300_radio_ctrl_impl::sptr>& radios) +{ + if (radios.size() < 2) return; //Nothing to synchronize + + //**PRECONDITION** + //This function assumes that all the VITA times in "radios" are synchronized + //to a common reference. Currently, this function is called in get_tx_stream + //which also has the same precondition. + + //Reinitialize and resync all DACs + for (size_t i = 0; i < radios.size(); i++) { + radios[i]->_dac->reset(); + } + + //Get a rough estimate of the cumulative command latency + boost::posix_time::ptime t_start = boost::posix_time::microsec_clock::local_time(); + for (size_t i = 0; i < radios.size(); i++) { + radios[i]->user_reg_read64(regs::RB_TIME_NOW); //Discard value. We are just timing the call + } + boost::posix_time::time_duration t_elapsed = + boost::posix_time::microsec_clock::local_time() - t_start; + + //Add 100% of headroom + uncertaintly to the command time + boost::uint64_t t_sync_us = (t_elapsed.total_microseconds() * 2) + 13000 /*Scheduler latency*/; + + //Pick radios[0] as the time reference. + uhd::time_spec_t sync_time = + radios[0]->_time64->get_time_now() + uhd::time_spec_t(((double)t_sync_us)/1e6); + + //Send the sync command + for (size_t i = 0; i < radios.size(); i++) { + radios[i]->set_command_tick_rate(radios[i]->_radio_clk_rate, IO_MASTER_RADIO); + radios[i]->_regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_SYNC, 0); + radios[i]->set_command_time(sync_time, IO_MASTER_RADIO); + //Arm FRAMEP/N sync pulse by asserting a rising edge + radios[i]->_regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_SYNC, 1); + radios[i]->_regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_SYNC, 0); + radios[i]->set_command_time(uhd::time_spec_t(0.0), IO_MASTER_RADIO); + } + + //Wait and check status + boost::this_thread::sleep(boost::posix_time::microseconds(t_sync_us)); + for (size_t i = 0; i < radios.size(); i++) { + radios[i]->_dac->verify_sync(); + } +} + +double x300_radio_ctrl_impl::self_cal_adc_xfer_delay( + const std::vector<x300_radio_ctrl_impl::sptr>& radios, + x300_clock_ctrl::sptr clock, + boost::function<void(double)> wait_for_clk_locked, + bool apply_delay) +{ + UHD_MSG(status) << "Running ADC transfer delay self-cal: " << std::flush; + + //Effective resolution of the self-cal. + static const size_t NUM_DELAY_STEPS = 100; + + double master_clk_period = (1.0e9 / clock->get_master_clock_rate()); //in ns + double delay_start = 0.0; + double delay_range = 2 * master_clk_period; + double delay_incr = delay_range / NUM_DELAY_STEPS; + + UHD_MSG(status) << "Measuring..." << std::flush; + double cached_clk_delay = clock->get_clock_delay(X300_CLOCK_WHICH_ADC0); + double fpga_clk_delay = clock->get_clock_delay(X300_CLOCK_WHICH_FPGA); + + //Iterate through several values of delays and measure ADC data integrity + std::vector< std::pair<double,bool> > results; + for (size_t i = 0; i < NUM_DELAY_STEPS; i++) { + //Delay the ADC clock (will set both Ch0 and Ch1 delays) + double delay = clock->set_clock_delay(X300_CLOCK_WHICH_ADC0, delay_incr*i + delay_start); + wait_for_clk_locked(0.1); + + boost::uint32_t err_code = 0; + for (size_t r = 0; r < radios.size(); r++) { + //Test each channel (I and Q) individually so as to not accidentally trigger + //on the data from the other channel if there is a swap + + // -- Test I Channel -- + //Put ADC in ramp test mode. Tie the other channel to all ones. + radios[r]->_adc->set_test_word("ramp", "ones"); + //Turn on the pattern checker in the FPGA. It will lock when it sees a zero + //and count deviations from the expected value + radios[r]->_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + radios[r]->_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1); + //50ms @ 200MHz = 10 million samples + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + if (radios[r]->_regs->misc_ins_reg.read(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_LOCKED)) { + err_code += radios[r]->_regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_ERROR); + } else { + err_code += 100; //Increment error code by 100 to indicate no lock + } + + // -- Test Q Channel -- + //Put ADC in ramp test mode. Tie the other channel to all ones. + radios[r]->_adc->set_test_word("ones", "ramp"); + //Turn on the pattern checker in the FPGA. It will lock when it sees a zero + //and count deviations from the expected value + radios[r]->_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + radios[r]->_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1); + //50ms @ 200MHz = 10 million samples + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + if (radios[r]->_regs->misc_ins_reg.read(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_LOCKED)) { + err_code += radios[r]->_regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_ERROR); + } else { + err_code += 100; //Increment error code by 100 to indicate no lock + } + } + //UHD_MSG(status) << (boost::format("XferDelay=%fns, Error=%d\n") % delay % err_code); + results.push_back(std::pair<double,bool>(delay, err_code==0)); + } + + //Calculate the valid window + int win_start_idx = -1, win_stop_idx = -1, cur_start_idx = -1, cur_stop_idx = -1; + for (size_t i = 0; i < results.size(); i++) { + std::pair<double,bool>& item = results[i]; + if (item.second) { //If data is stable + if (cur_start_idx == -1) { //This is the first window + cur_start_idx = i; + cur_stop_idx = i; + } else { //We are extending the window + cur_stop_idx = i; + } + } else { + if (cur_start_idx == -1) { //We haven't yet seen valid data + //Do nothing + } else if (win_start_idx == -1) { //We passed the first valid window + win_start_idx = cur_start_idx; + win_stop_idx = cur_stop_idx; + } else { //Update cached window if current window is larger + double cur_win_len = results[cur_stop_idx].first - results[cur_start_idx].first; + double cached_win_len = results[win_stop_idx].first - results[win_start_idx].first; + if (cur_win_len > cached_win_len) { + win_start_idx = cur_start_idx; + win_stop_idx = cur_stop_idx; + } + } + //Reset current window + cur_start_idx = -1; + cur_stop_idx = -1; + } + } + if (win_start_idx == -1) { + throw uhd::runtime_error("self_cal_adc_xfer_delay: Self calibration failed. Convergence error."); + } + + double win_center = (results[win_stop_idx].first + results[win_start_idx].first) / 2.0; + double win_length = results[win_stop_idx].first - results[win_start_idx].first; + if (win_length < master_clk_period/4) { + throw uhd::runtime_error("self_cal_adc_xfer_delay: Self calibration failed. Valid window too narrow."); + } + + //Cycle slip the relative delay by a clock cycle to prevent sample misalignment + //fpga_clk_delay > 0 and 0 < win_center < 2*(1/MCR) so one cycle slip is all we need + bool cycle_slip = (win_center-fpga_clk_delay >= master_clk_period); + if (cycle_slip) { + win_center -= master_clk_period; + } + + if (apply_delay) { + UHD_MSG(status) << "Validating..." << std::flush; + //Apply delay + win_center = clock->set_clock_delay(X300_CLOCK_WHICH_ADC0, win_center); //Sets ADC0 and ADC1 + wait_for_clk_locked(0.1); + //Validate + for (size_t r = 0; r < radios.size(); r++) { + radios[r]->self_test_adc(2000); + } + } else { + //Restore delay + clock->set_clock_delay(X300_CLOCK_WHICH_ADC0, cached_clk_delay); //Sets ADC0 and ADC1 + } + + //Teardown + for (size_t r = 0; r < radios.size(); r++) { + radios[r]->_adc->set_test_word("normal", "normal"); + radios[r]->_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + } + UHD_MSG(status) << (boost::format(" done (FPGA->ADC=%.3fns%s, Window=%.3fns)\n") % + (win_center-fpga_clk_delay) % (cycle_slip?" +cyc":"") % win_length); + + return win_center; +} +/**************************************************************************** + * Helpers + ***************************************************************************/ +void x300_radio_ctrl_impl::_update_atr_leds(const std::string &rx_ant) +{ + const bool is_txrx = (rx_ant == "TX/RX"); + const int rx_led = (1 << 2); + const int tx_led = (1 << 1); + const int txrx_led = (1 << 0); + _leds->set_atr_reg(gpio_atr::ATR_REG_IDLE, 0); + _leds->set_atr_reg(gpio_atr::ATR_REG_RX_ONLY, is_txrx? txrx_led : rx_led); + _leds->set_atr_reg(gpio_atr::ATR_REG_TX_ONLY, tx_led); + _leds->set_atr_reg(gpio_atr::ATR_REG_FULL_DUPLEX, rx_led | tx_led); +} + +void x300_radio_ctrl_impl::_self_cal_adc_capture_delay(bool print_status) +{ + if (print_status) UHD_MSG(status) << "Running ADC capture delay self-cal..." << std::flush; + + static const boost::uint32_t NUM_DELAY_STEPS = 32; //The IDELAYE2 element has 32 steps + static const boost::uint32_t NUM_RETRIES = 2; //Retry self-cal if it fails in warmup situations + static const boost::int32_t MIN_WINDOW_LEN = 4; + + boost::int32_t win_start = -1, win_stop = -1; + boost::uint32_t iter = 0; + while (iter++ < NUM_RETRIES) { + for (boost::uint32_t dly_tap = 0; dly_tap < NUM_DELAY_STEPS; dly_tap++) { + //Apply delay + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_VAL, dly_tap); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_STB, 1); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_STB, 0); + + boost::uint32_t err_code = 0; + + // -- Test I Channel -- + //Put ADC in ramp test mode. Tie the other channel to all ones. + _adc->set_test_word("ramp", "ones"); + //Turn on the pattern checker in the FPGA. It will lock when it sees a zero + //and count deviations from the expected value + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1); + //10ms @ 200MHz = 2 million samples + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + if (_regs->misc_ins_reg.read(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER0_I_LOCKED)) { + err_code += _regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER0_I_ERROR); + } else { + err_code += 100; //Increment error code by 100 to indicate no lock + } + + // -- Test Q Channel -- + //Put ADC in ramp test mode. Tie the other channel to all ones. + _adc->set_test_word("ones", "ramp"); + //Turn on the pattern checker in the FPGA. It will lock when it sees a zero + //and count deviations from the expected value + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1); + //10ms @ 200MHz = 2 million samples + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + if (_regs->misc_ins_reg.read(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER0_Q_LOCKED)) { + err_code += _regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER0_Q_ERROR); + } else { + err_code += 100; //Increment error code by 100 to indicate no lock + } + + if (err_code == 0) { + if (win_start == -1) { //This is the first window + win_start = dly_tap; + win_stop = dly_tap; + } else { //We are extending the window + win_stop = dly_tap; + } + } else { + if (win_start != -1) { //A valid window turned invalid + if (win_stop - win_start >= MIN_WINDOW_LEN) { + break; //Valid window found + } else { + win_start = -1; //Reset window + } + } + } + //UHD_MSG(status) << (boost::format("CapTap=%d, Error=%d\n") % dly_tap % err_code); + } + + //Retry the self-cal if it fails + if ((win_start == -1 || (win_stop - win_start) < MIN_WINDOW_LEN) && iter < NUM_RETRIES /*not last iteration*/) { + win_start = -1; + win_stop = -1; + boost::this_thread::sleep(boost::posix_time::milliseconds(2000)); + } else { + break; + } + } + _adc->set_test_word("normal", "normal"); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + + if (win_start == -1) { + throw uhd::runtime_error("self_cal_adc_capture_delay: Self calibration failed. Convergence error."); + } + + if (win_stop-win_start < MIN_WINDOW_LEN) { + throw uhd::runtime_error("self_cal_adc_capture_delay: Self calibration failed. Valid window too narrow."); + } + + boost::uint32_t ideal_tap = (win_stop + win_start) / 2; + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_VAL, ideal_tap); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_STB, 1); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_STB, 0); + + if (print_status) { + double tap_delay = (1.0e12 / _radio_clk_rate) / (2*32); //in ps + UHD_MSG(status) << boost::format(" done (Tap=%d, Window=%d, TapDelay=%.3fps, Iter=%d)\n") % ideal_tap % (win_stop-win_start) % tap_delay % iter; + } +} + +void x300_radio_ctrl_impl::_check_adc(const boost::uint32_t val) +{ + //Wait for previous control transaction to flush + user_reg_read64(regs::RB_TEST); + //Wait for ADC test pattern to propagate + boost::this_thread::sleep(boost::posix_time::microsec(5)); + //Read value of RX readback register and verify + boost::uint32_t adc_rb = static_cast<boost::uint32_t>(user_reg_read64(regs::RB_TEST)>>32); + adc_rb ^= 0xfffc0000; //adapt for I inversion in FPGA + if (val != adc_rb) { + throw uhd::runtime_error( + (boost::format("ADC self-test failed for %s. (Exp=0x%x, Got=0x%x)")%unique_id()%val%adc_rb).str()); + } +} + +/**************************************************************************** + * Helpers + ***************************************************************************/ +bool x300_radio_ctrl_impl::check_radio_config() +{ + UHD_RFNOC_BLOCK_TRACE() << "x300_radio_ctrl_impl::check_radio_config() " << std::endl; + const fs_path rx_fe_path = fs_path("dboards" / _radio_slot / "rx_frontends"); + for (size_t chan = 0; chan < _get_num_radios(); chan++) { + if (_tree->exists(rx_fe_path / _rx_fe_map.at(chan).db_fe_name / "enabled")) { + const bool chan_active = _is_streamer_active(RX_DIRECTION, chan); + _tree->access<bool>(rx_fe_path / _rx_fe_map.at(chan).db_fe_name / "enabled") + .set(chan_active) + ; + } + } + + const fs_path tx_fe_path = fs_path("dboards" / _radio_slot / "tx_frontends"); + for (size_t chan = 0; chan < _get_num_radios(); chan++) { + if (_tree->exists(tx_fe_path / _rx_fe_map.at(chan).db_fe_name / "enabled")) { + const bool chan_active = _is_streamer_active(TX_DIRECTION, chan); + _tree->access<bool>(tx_fe_path / _rx_fe_map.at(chan).db_fe_name / "enabled") + .set(chan_active) + ; + } + } + + return true; +} + +/**************************************************************************** + * Register block + ***************************************************************************/ +UHD_RFNOC_BLOCK_REGISTER(x300_radio_ctrl, "X300Radio"); diff --git a/host/lib/usrp/x300/x300_radio_ctrl_impl.hpp b/host/lib/usrp/x300/x300_radio_ctrl_impl.hpp new file mode 100644 index 000000000..f2150a982 --- /dev/null +++ b/host/lib/usrp/x300/x300_radio_ctrl_impl.hpp @@ -0,0 +1,198 @@ +// +// Copyright 2015-2016 Ettus Research +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_X300_RADIO_CTRL_IMPL_HPP +#define INCLUDED_LIBUHD_RFNOC_X300_RADIO_CTRL_IMPL_HPP + +#include "radio_ctrl_impl.hpp" +#include "x300_clock_ctrl.hpp" +#include "spi_core_3000.hpp" +#include "x300_adc_ctrl.hpp" +#include "x300_dac_ctrl.hpp" +#include "x300_regs.hpp" +#include "rx_frontend_core_3000.hpp" +#include "tx_frontend_core_200.hpp" +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/usrp/gpio_defs.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Provide access to an X300 radio. + */ +class x300_radio_ctrl_impl : public radio_ctrl_impl +{ +public: + typedef boost::shared_ptr<x300_radio_ctrl_impl> sptr; + + /************************************************************************ + * Structors + ***********************************************************************/ + UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR_DECL(x300_radio_ctrl) + virtual ~x300_radio_ctrl_impl(); + + /************************************************************************ + * API calls + ***********************************************************************/ + double set_rate(double rate); + + void set_tx_antenna(const std::string &ant, const size_t chan); + void set_rx_antenna(const std::string &ant, const size_t chan); + + double set_tx_frequency(const double freq, const size_t chan); + double set_rx_frequency(const double freq, const size_t chan); + double get_tx_frequency(const size_t chan); + double get_rx_frequency(const size_t chan); + + double set_tx_gain(const double gain, const size_t chan); + double set_rx_gain(const double gain, const size_t chan); + + size_t get_chan_from_dboard_fe(const std::string &fe, const direction_t dir); + std::string get_dboard_fe_from_chan(const size_t chan, const direction_t dir); + + double get_output_samp_rate(size_t port); + + /************************************************************************ + * Hardware setup and control + ***********************************************************************/ + /*! Set up the radio. No API calls may be made before this one. + */ + void setup_radio( + uhd::i2c_iface::sptr zpu_i2c, x300_clock_ctrl::sptr clock, bool verbose); + + void reset_codec(); + + void self_test_adc( + boost::uint32_t ramp_time_ms = 100); + + static void extended_adc_test( + const std::vector<x300_radio_ctrl_impl::sptr>&, double duration_s); + + static void synchronize_dacs( + const std::vector<x300_radio_ctrl_impl::sptr>& radios); + + static double self_cal_adc_xfer_delay( + const std::vector<x300_radio_ctrl_impl::sptr>& radios, + x300_clock_ctrl::sptr clock, + boost::function<void(double)> wait_for_clk_locked, + bool apply_delay); + +protected: + virtual bool check_radio_config(); + +private: + class radio_regmap_t : public uhd::soft_regmap_t { + public: + typedef boost::shared_ptr<radio_regmap_t> sptr; + class misc_outs_reg_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(DAC_ENABLED, /*width*/ 1, /*shift*/ 0); //[0] + UHD_DEFINE_SOFT_REG_FIELD(DAC_RESET_N, /*width*/ 1, /*shift*/ 1); //[1] + UHD_DEFINE_SOFT_REG_FIELD(ADC_RESET, /*width*/ 1, /*shift*/ 2); //[2] + UHD_DEFINE_SOFT_REG_FIELD(ADC_DATA_DLY_STB, /*width*/ 1, /*shift*/ 3); //[3] + UHD_DEFINE_SOFT_REG_FIELD(ADC_DATA_DLY_VAL, /*width*/ 5, /*shift*/ 4); //[8:4] + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER_ENABLED, /*width*/ 1, /*shift*/ 9); //[9] + UHD_DEFINE_SOFT_REG_FIELD(DAC_SYNC, /*width*/ 1, /*shift*/ 10); //[10] + + misc_outs_reg_t(): uhd::soft_reg32_wo_t(regs::sr_addr(regs::MISC_OUTS)) { + //Initial values + set(DAC_ENABLED, 0); + set(DAC_RESET_N, 0); + set(ADC_RESET, 0); + set(ADC_DATA_DLY_STB, 0); + set(ADC_DATA_DLY_VAL, 16); + set(ADC_CHECKER_ENABLED, 0); + set(DAC_SYNC, 0); + } + } misc_outs_reg; + + class misc_ins_reg_t : public uhd::soft_reg64_ro_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_Q_LOCKED, /*width*/ 1, /*shift*/ 32); //[0] + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_I_LOCKED, /*width*/ 1, /*shift*/ 33); //[1] + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_Q_LOCKED, /*width*/ 1, /*shift*/ 34); //[2] + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_I_LOCKED, /*width*/ 1, /*shift*/ 35); //[3] + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_Q_ERROR, /*width*/ 1, /*shift*/ 36); //[4] + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_I_ERROR, /*width*/ 1, /*shift*/ 37); //[5] + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_Q_ERROR, /*width*/ 1, /*shift*/ 38); //[6] + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_I_ERROR, /*width*/ 1, /*shift*/ 39); //[7] + + misc_ins_reg_t(): uhd::soft_reg64_ro_t(regs::RB_MISC_IO) { } + } misc_ins_reg; + + radio_regmap_t(int radio_num) : soft_regmap_t("radio" + boost::lexical_cast<std::string>(radio_num) + "_regmap") { + add_to_map(misc_outs_reg, "misc_outs_reg", PRIVATE); + add_to_map(misc_ins_reg, "misc_ins_reg", PRIVATE); + } + }; + + struct x300_regs { + static const uint32_t TX_FE_BASE = 224; + static const uint32_t RX_RE_BASE = 232; + }; + + void _update_atr_leds(const std::string &rx_ant); + + void _self_cal_adc_capture_delay(bool print_status); + + void _check_adc(const boost::uint32_t val); + + void set_rx_fe_corrections(const std::string &fe_name, const double lo_freq); + void set_tx_fe_corrections(const std::string &fe_name, const double lo_freq); + +private: // members + enum radio_connection_t { PRIMARY, SECONDARY }; + + radio_connection_t _radio_type; + std::string _radio_slot; + //! Radio clock rate is the rate at which the ADC and DAC are running at. + // Not necessarily this block's sampling rate (tick rate). + double _radio_clk_rate; + + radio_regmap_t::sptr _regs; + usrp::gpio_atr::gpio_atr_3000::sptr _leds; + spi_core_3000::sptr _spi; + x300_adc_ctrl::sptr _adc; + x300_dac_ctrl::sptr _dac; + usrp::gpio_atr::gpio_atr_3000::sptr _fp_gpio; + + std::map<size_t, usrp::dboard_eeprom_t> _db_eeproms; + usrp::dboard_manager::sptr _db_manager; + + struct rx_fe_perif { + std::string name; + std::string db_fe_name; + rx_frontend_core_3000::sptr core; + }; + struct tx_fe_perif { + std::string name; + std::string db_fe_name; + tx_frontend_core_200::sptr core; + }; + + std::map<size_t, rx_fe_perif> _rx_fe_map; + std::map<size_t, tx_fe_perif> _tx_fe_map; + + bool _ignore_cal_file; + +}; /* class radio_ctrl_impl */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_X300_RADIO_CTRL_IMPL_HPP */ +// vim: sw=4 et: diff --git a/host/lib/usrp/x300/x300_regs.hpp b/host/lib/usrp/x300/x300_regs.hpp index 69a8d5d9f..458d6ba0f 100644 --- a/host/lib/usrp/x300/x300_regs.hpp +++ b/host/lib/usrp/x300/x300_regs.hpp @@ -22,41 +22,6 @@ #include <stdint.h> #include <uhd/utils/soft_register.hpp> -namespace uhd { namespace usrp { namespace radio { - -static UHD_INLINE uint32_t sr_addr(const uint32_t offset) -{ - return offset * 4; -} - -static const uint32_t DACSYNC = 5; -static const uint32_t LOOPBACK = 6; -static const uint32_t TEST = 7; -static const uint32_t SPI = 8; -static const uint32_t GPIO = 16; -static const uint32_t MISC_OUTS = 24; -static const uint32_t READBACK = 32; -static const uint32_t TX_CTRL = 64; -static const uint32_t RX_CTRL = 96; -static const uint32_t TIME = 128; -static const uint32_t RX_DSP = 144; -static const uint32_t TX_DSP = 184; -static const uint32_t LEDS = 195; -static const uint32_t FP_GPIO = 201; -static const uint32_t RX_FRONT = 208; -static const uint32_t TX_FRONT = 216; - -static const uint32_t RB32_GPIO = 0; -static const uint32_t RB32_SPI = 4; -static const uint32_t RB64_TIME_NOW = 8; -static const uint32_t RB64_TIME_PPS = 16; -static const uint32_t RB32_TEST = 24; -static const uint32_t RB32_RX = 28; -static const uint32_t RB32_FP_GPIO = 32; -static const uint32_t RB32_MISC_INS = 36; - -}}} // namespace - static const int BL_ADDRESS = 0; static const int BL_DATA = 1; @@ -87,6 +52,7 @@ static const int ZPU_SR_DRAM_FIFO1 = 80; static const int ZPU_RB_SPI = 2; static const int ZPU_RB_CLK_STATUS = 3; static const int ZPU_RB_COMPAT_NUM = 6; +static const int ZPU_RB_NUM_CE = 7; static const int ZPU_RB_SFP0_TYPE = 4; static const int ZPU_RB_SFP1_TYPE = 5; static const int ZPU_RB_DRAM_FIFO0 = 10; @@ -250,49 +216,6 @@ namespace uhd { namespace usrp { namespace x300 { } }; - class radio_regmap_t : public uhd::soft_regmap_t { - public: - typedef boost::shared_ptr<radio_regmap_t> sptr; - class misc_outs_reg_t : public uhd::soft_reg32_wo_t { - public: - UHD_DEFINE_SOFT_REG_FIELD(DAC_ENABLED, /*width*/ 1, /*shift*/ 0); //[0] - UHD_DEFINE_SOFT_REG_FIELD(DAC_RESET_N, /*width*/ 1, /*shift*/ 1); //[1] - UHD_DEFINE_SOFT_REG_FIELD(ADC_RESET, /*width*/ 1, /*shift*/ 2); //[2] - UHD_DEFINE_SOFT_REG_FIELD(ADC_DATA_DLY_STB, /*width*/ 1, /*shift*/ 3); //[3] - UHD_DEFINE_SOFT_REG_FIELD(ADC_DATA_DLY_VAL, /*width*/ 5, /*shift*/ 4); //[8:4] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER_ENABLED, /*width*/ 1, /*shift*/ 9); //[9] - - misc_outs_reg_t(): uhd::soft_reg32_wo_t(uhd::usrp::radio::sr_addr(uhd::usrp::radio::MISC_OUTS)) { - //Initial values - set(DAC_ENABLED, 0); - set(DAC_RESET_N, 0); - set(ADC_RESET, 0); - set(ADC_DATA_DLY_STB, 0); - set(ADC_DATA_DLY_VAL, 16); - set(ADC_CHECKER_ENABLED, 0); - } - } misc_outs_reg; - - class misc_ins_reg_t : public uhd::soft_reg32_ro_t { - public: - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_Q_LOCKED, /*width*/ 1, /*shift*/ 0); //[0] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_I_LOCKED, /*width*/ 1, /*shift*/ 1); //[1] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_Q_LOCKED, /*width*/ 1, /*shift*/ 2); //[2] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_I_LOCKED, /*width*/ 1, /*shift*/ 3); //[3] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_Q_ERROR, /*width*/ 1, /*shift*/ 4); //[4] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_I_ERROR, /*width*/ 1, /*shift*/ 5); //[5] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_Q_ERROR, /*width*/ 1, /*shift*/ 6); //[6] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_I_ERROR, /*width*/ 1, /*shift*/ 7); //[7] - - misc_ins_reg_t(): uhd::soft_reg32_ro_t(uhd::usrp::radio::RB32_MISC_INS) { } - } misc_ins_reg; - - radio_regmap_t(int radio_num) : soft_regmap_t("radio" + boost::lexical_cast<std::string>(radio_num) + "_regmap") { - add_to_map(misc_outs_reg, "misc_outs_reg", PUBLIC); - add_to_map(misc_ins_reg, "misc_ins_reg", PUBLIC); - } - }; - }}} #endif /* INCLUDED_X300_REGS_HPP */ diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt index c5f25913e..8f7fdcd7c 100644 --- a/host/tests/CMakeLists.txt +++ b/host/tests/CMakeLists.txt @@ -52,6 +52,19 @@ SET(test_sources #turn each test cpp file into an executable with an int main() function ADD_DEFINITIONS(-DBOOST_TEST_DYN_LINK -DBOOST_TEST_MAIN) +IF(ENABLE_RFNOC) + LIST(APPEND test_sources + block_id_test.cpp + blockdef_test.cpp + device3_test.cpp + graph_search_test.cpp + node_connect_test.cpp + rate_node_test.cpp + stream_sig_test.cpp + tick_node_test.cpp + ) +ENDIF(ENABLE_RFNOC) + IF(ENABLE_C_API) LIST(APPEND test_sources eeprom_c_test.c @@ -72,6 +85,36 @@ FOREACH(test_source ${test_sources}) UHD_INSTALL(TARGETS ${test_name} RUNTIME DESTINATION ${PKG_LIB_DIR}/tests COMPONENT tests) ENDFOREACH(test_source) +# Other tests that don't directly link with libuhd: (TODO find a nicer way to do this) +INCLUDE_DIRECTORIES(${CMAKE_BINARY_DIR}/lib/rfnoc/nocscript/) +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/lib/rfnoc/nocscript/) +ADD_EXECUTABLE(nocscript_expr_test + nocscript_expr_test.cpp + ${CMAKE_SOURCE_DIR}/lib/rfnoc/nocscript/expression.cpp +) +TARGET_LINK_LIBRARIES(nocscript_expr_test uhd ${Boost_LIBRARIES}) +UHD_ADD_TEST(nocscript_expr_test nocscript_expr_test) +UHD_INSTALL(TARGETS nocscript_expr_test RUNTIME DESTINATION ${PKG_LIB_DIR}/tests COMPONENT tests) + +ADD_EXECUTABLE(nocscript_ftable_test + nocscript_ftable_test.cpp + ${CMAKE_SOURCE_DIR}/lib/rfnoc/nocscript/function_table.cpp + ${CMAKE_SOURCE_DIR}/lib/rfnoc/nocscript/expression.cpp +) +TARGET_LINK_LIBRARIES(nocscript_ftable_test uhd ${Boost_LIBRARIES}) +UHD_ADD_TEST(nocscript_ftable_test nocscript_ftable_test) +UHD_INSTALL(TARGETS nocscript_ftable_test RUNTIME DESTINATION ${PKG_LIB_DIR}/tests COMPONENT tests) + +ADD_EXECUTABLE(nocscript_parser_test + nocscript_parser_test.cpp + ${CMAKE_SOURCE_DIR}/lib/rfnoc/nocscript/parser.cpp + ${CMAKE_SOURCE_DIR}/lib/rfnoc/nocscript/function_table.cpp + ${CMAKE_SOURCE_DIR}/lib/rfnoc/nocscript/expression.cpp +) +TARGET_LINK_LIBRARIES(nocscript_parser_test uhd ${Boost_LIBRARIES}) +UHD_ADD_TEST(nocscript_parser_test nocscript_parser_test) +UHD_INSTALL(TARGETS nocscript_parser_test RUNTIME DESTINATION ${PKG_LIB_DIR}/tests COMPONENT tests) + ######################################################################## # demo of a loadable module ######################################################################## diff --git a/host/tests/block_id_test.cpp b/host/tests/block_id_test.cpp new file mode 100644 index 000000000..cab8a8cca --- /dev/null +++ b/host/tests/block_id_test.cpp @@ -0,0 +1,117 @@ +// +// 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 <iostream> +#include <boost/test/unit_test.hpp> +#include <uhd/exception.hpp> +#include <uhd/rfnoc/block_id.hpp> + +using namespace uhd::rfnoc; + +BOOST_AUTO_TEST_CASE(test_block_id) { + BOOST_CHECK(block_id_t::is_valid_block_id("00/Filter_1")); + BOOST_CHECK(not block_id_t::is_valid_block_id("0/MAG_SQUARE")); + BOOST_CHECK(block_id_t::is_valid_blockname("FilterFoo")); + BOOST_CHECK(not block_id_t::is_valid_blockname("Filter_Foo")); + BOOST_CHECK(not block_id_t::is_valid_blockname("Filter/Foo")); + BOOST_CHECK(not block_id_t::is_valid_blockname("0Filter/Foo")); + BOOST_CHECK(not block_id_t::is_valid_blockname("0/Filter/Foo")); + + BOOST_REQUIRE_THROW(block_id_t invalid_block_id("0Filter/1"), uhd::value_error); + + block_id_t block_id("0/FFT_1"); + BOOST_CHECK_EQUAL(block_id.get_device_no(), 0); + BOOST_CHECK_EQUAL(block_id.get_block_name(), "FFT"); + BOOST_CHECK_EQUAL(block_id.get_block_count(), 1); + + block_id.set_device_no(17); + BOOST_CHECK_EQUAL(block_id.get_device_no(), 17); + BOOST_CHECK_EQUAL(block_id.get_block_name(), "FFT"); + BOOST_CHECK_EQUAL(block_id.get_block_count(), 1); + + block_id.set_block_count(11); + BOOST_CHECK_EQUAL(block_id.get_device_no(), 17); + BOOST_CHECK_EQUAL(block_id.get_block_name(), "FFT"); + BOOST_CHECK_EQUAL(block_id.get_block_count(), 11); + + block_id.set_block_name("FooBar"); + BOOST_CHECK_EQUAL(block_id.get_device_no(), 17); + BOOST_CHECK_EQUAL(block_id.get_block_name(), "FooBar"); + BOOST_CHECK_EQUAL(block_id.get_block_count(), 11); + + BOOST_CHECK(not block_id.set_block_name("Foo_Bar")); + BOOST_CHECK_EQUAL(block_id.get_device_no(), 17); + BOOST_CHECK_EQUAL(block_id.get_block_name(), "FooBar"); // Is unchanged because invalid + BOOST_CHECK_EQUAL(block_id.get_block_count(), 11); + + block_id++; + BOOST_CHECK_EQUAL(block_id.get_device_no(), 17); + BOOST_CHECK_EQUAL(block_id.get_block_name(), "FooBar"); + BOOST_CHECK_EQUAL(block_id.get_block_count(), 12); + + block_id_t other_block_id(7, "BlockName", 3); + BOOST_CHECK_EQUAL(other_block_id.get_device_no(), 7); + BOOST_CHECK_EQUAL(other_block_id.get_block_name(), "BlockName"); + BOOST_CHECK_EQUAL(other_block_id.get_block_count(), 3); + BOOST_CHECK_EQUAL(other_block_id.to_string(), "7/BlockName_3"); + + // Cast + std::string block_id_str = std::string(other_block_id); + std::cout << "Should print '7/BlockName_3': " << block_id_str << std::endl; + BOOST_CHECK_EQUAL(block_id_str, "7/BlockName_3"); + + // Operators + std::cout << "Testing ostream printing (<<): " << other_block_id << std::endl; + BOOST_CHECK_EQUAL(other_block_id, block_id_str); + BOOST_CHECK_EQUAL(other_block_id, "7/BlockName_3"); + + // match() + BOOST_CHECK(other_block_id.match("BlockName")); + BOOST_CHECK(other_block_id.match("7/BlockName")); + BOOST_CHECK(other_block_id.match("BlockName_3")); + BOOST_CHECK(other_block_id.match("7/BlockName_3")); + BOOST_CHECK(not other_block_id.match("8/BlockName")); + BOOST_CHECK(not other_block_id.match("8/BlockName_3")); + BOOST_CHECK(not other_block_id.match("Block_Name_3")); + BOOST_CHECK(not other_block_id.match("BlockName_4")); + BOOST_CHECK(not other_block_id.match("BlockName_X")); + BOOST_CHECK(not other_block_id.match("2093ksdjfflsdkjf")); +} + +BOOST_AUTO_TEST_CASE(test_block_id_set) { + // test set() + block_id_t block_id_for_set(5, "Blockname", 9); + block_id_for_set.set("FirFilter"); + BOOST_CHECK_EQUAL(block_id_for_set.get_device_no(), 5); + BOOST_CHECK_EQUAL(block_id_for_set.get_block_name(), "FirFilter"); + BOOST_CHECK_EQUAL(block_id_for_set.get_block_count(), 9); + block_id_for_set.set("1/FirFilter2"); + BOOST_CHECK_EQUAL(block_id_for_set.get_device_no(), 1); + BOOST_CHECK_EQUAL(block_id_for_set.get_block_name(), "FirFilter2"); + BOOST_CHECK_EQUAL(block_id_for_set.get_block_count(), 9); + block_id_for_set.set("Sync_3"); + BOOST_CHECK_EQUAL(block_id_for_set.get_device_no(), 1); + BOOST_CHECK_EQUAL(block_id_for_set.get_block_name(), "Sync"); + BOOST_CHECK_EQUAL(block_id_for_set.get_block_count(), 3); +} + +BOOST_AUTO_TEST_CASE(test_block_id_cmp) { + BOOST_CHECK(block_id_t("0/FFT_1") == block_id_t("0/FFT_1")); + BOOST_CHECK(block_id_t("0/FFT_1") != block_id_t("1/FFT_1")); + BOOST_CHECK(block_id_t("0/FFT_1") < block_id_t("1/aaaaaaaaa_0")); + BOOST_CHECK(not (block_id_t("0/FFT_1") > block_id_t("1/aaaaaaaaa_0"))); +} diff --git a/host/tests/blockdef_test.cpp b/host/tests/blockdef_test.cpp new file mode 100644 index 000000000..2cd06a43a --- /dev/null +++ b/host/tests/blockdef_test.cpp @@ -0,0 +1,94 @@ +// +// 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 <iostream> +#include <map> +#include <boost/cstdint.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/test/unit_test.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <uhd/rfnoc/blockdef.hpp> + +using namespace uhd::rfnoc; + +BOOST_AUTO_TEST_CASE(test_lookup) { + std::map<boost::uint64_t, std::string> blocknames = boost::assign::list_of< std::pair<boost::uint64_t, std::string> > + (0, "NullSrcSink") + (0xFF70000000000000, "FFT") + (0xF112000000000001, "FIR") + (0xF1F0000000000000, "FIFO") + (0xD053000000000000, "Window") + (0x5CC0000000000000, "SchmidlCox") + ; + + std::cout << blocknames.size() << std::endl; + + for (std::map<boost::uint64_t, std::string>::iterator it = blocknames.begin(); it != blocknames.end(); ++it) { + std::cout << "Testing " << it->second << " => " << str(boost::format("%016X") % it->first) << std::endl; + blockdef::sptr block_definition = blockdef::make_from_noc_id(it->first); + // If the previous function fails, it'll return a NULL pointer + BOOST_REQUIRE(block_definition); + BOOST_CHECK(block_definition->is_block()); + BOOST_CHECK_EQUAL(block_definition->get_name(), it->second); + } +} + +BOOST_AUTO_TEST_CASE(test_ports) { + // Create an FFT: + blockdef::sptr block_definition = blockdef::make_from_noc_id(0xFF70000000000000); + blockdef::ports_t in_ports = block_definition->get_input_ports(); + BOOST_REQUIRE_EQUAL(in_ports.size(), 1); + BOOST_CHECK_EQUAL(in_ports[0]["name"], "in"); + BOOST_CHECK_EQUAL(in_ports[0]["type"], "sc16"); + BOOST_CHECK(in_ports[0].has_key("vlen")); + BOOST_CHECK(in_ports[0].has_key("pkt_size")); + + blockdef::ports_t out_ports = block_definition->get_output_ports(); + BOOST_REQUIRE_EQUAL(out_ports.size(), 1); + BOOST_CHECK_EQUAL(out_ports[0]["name"], "out"); + BOOST_CHECK(out_ports[0].has_key("vlen")); + BOOST_CHECK(out_ports[0].has_key("pkt_size")); + + BOOST_CHECK_EQUAL(block_definition->get_all_port_numbers().size(), 1); + BOOST_CHECK_EQUAL(block_definition->get_all_port_numbers()[0], 0); +} + +BOOST_AUTO_TEST_CASE(test_args) { + // Create an FFT: + blockdef::sptr block_definition = blockdef::make_from_noc_id(0xFF70000000000000); + blockdef::args_t args = block_definition->get_args(); + BOOST_REQUIRE(args.size() >= 3); + BOOST_CHECK_EQUAL(args[0]["name"], "spp"); + BOOST_CHECK_EQUAL(args[0]["type"], "int"); + BOOST_CHECK_EQUAL(args[0]["value"], "256"); +} + +BOOST_AUTO_TEST_CASE(test_regs) { + // Create an FFT: + blockdef::sptr block_definition = blockdef::make_from_noc_id(0xFF70000000000000); + blockdef::registers_t sregs = block_definition->get_settings_registers(); + BOOST_REQUIRE_EQUAL(sregs.size(), 3); + BOOST_CHECK_EQUAL(sregs["FFT_RESET"], 131); + BOOST_CHECK_EQUAL(sregs["FFT_SIZE_LOG2"], 132); + BOOST_CHECK_EQUAL(sregs["MAGNITUDE_OUT"], 133); + blockdef::registers_t user_regs = block_definition->get_readback_registers(); + BOOST_REQUIRE_EQUAL(user_regs.size(), 2); + BOOST_CHECK_EQUAL(user_regs["RB_FFT_RESET"], 0); + BOOST_CHECK_EQUAL(user_regs["RB_MAGNITUDE_OUT"], 1); +} + diff --git a/host/tests/device3_test.cpp b/host/tests/device3_test.cpp new file mode 100644 index 000000000..593facb9a --- /dev/null +++ b/host/tests/device3_test.cpp @@ -0,0 +1,175 @@ +// +// 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 <exception> +#include <iostream> +#include <boost/test/unit_test.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/types/wb_iface.hpp> +#include <uhd/device3.hpp> +#include <uhd/rfnoc/block_ctrl.hpp> +#include <uhd/rfnoc/graph.hpp> + +using namespace uhd; +using namespace uhd::rfnoc; + +static const boost::uint64_t TEST_NOC_ID = 0xAAAABBBBCCCCDDDD; +static const sid_t TEST_SID0 = 0x00000200; // 0.0.2.0 +static const sid_t TEST_SID1 = 0x00000210; // 0.0.2.F + +// Pseudo-wb-iface +class pseudo_wb_iface_impl : public uhd::wb_iface +{ + public: + pseudo_wb_iface_impl() {}; + ~pseudo_wb_iface_impl() {}; + + void poke64(const wb_addr_type addr, const boost::uint64_t data) { + std::cout << str(boost::format("[PSEUDO] poke64 to addr: %016X, data == %016X") % addr % data) << std::endl; + }; + + boost::uint64_t peek64(const wb_addr_type addr) { + std::cout << str(boost::format("[PSEUDO] peek64 to addr: %016X") % addr) << std::endl; + switch (addr) { + case SR_READBACK_REG_ID: + return TEST_NOC_ID; + case SR_READBACK_REG_FIFOSIZE: + return 0x000000000000000B; + case SR_READBACK_REG_USER: + return 0x0123456789ABCDEF; + default: + return 0; + } + return 0; + } + + void poke32(const wb_addr_type addr, const boost::uint32_t data) { + std::cout << str(boost::format("poke32 to addr: %08X, data == %08X") % addr % data) << std::endl; + } + + boost::uint32_t peek32(const wb_addr_type addr) { + std::cout << str(boost::format("peek32 to addr: %08X") % addr) << std::endl; + return 0; + } +}; + +// Pseudo-device +class pseudo_device3_impl : public uhd::device3 +{ + public: + pseudo_device3_impl() + { + _tree = uhd::property_tree::make(); + _tree->create<std::string>("/name").set("Test Pseudo-Device3"); + + // We can re-use this: + std::map<size_t, wb_iface::sptr> ctrl_ifaces = boost::assign::map_list_of + (0, wb_iface::sptr(new pseudo_wb_iface_impl())) + ; + + // Add two block controls: + uhd::rfnoc::make_args_t make_args; + make_args.ctrl_ifaces = ctrl_ifaces; + make_args.base_address = TEST_SID0.get_dst(); + make_args.device_index = 0; + make_args.tree = _tree; + make_args.is_big_endian = false; + std::cout << "[PSEUDO] Generating block controls 1/2:" << std::endl; + _rfnoc_block_ctrl.push_back( block_ctrl_base::make(make_args) ); + + std::cout << "[PSEUDO] Generating block controls 2/2:" << std::endl; + make_args.base_address = TEST_SID1.get_dst(); + _rfnoc_block_ctrl.push_back( block_ctrl::make(make_args) ); + } + + rx_streamer::sptr get_rx_stream(const stream_args_t &args) { + throw uhd::not_implemented_error(args.args.to_string()); + } + + tx_streamer::sptr get_tx_stream(const stream_args_t &args) { + throw uhd::not_implemented_error(args.args.to_string()); + } + + bool recv_async_msg(async_metadata_t &async_metadata, double timeout) { + throw uhd::not_implemented_error(str(boost::format("%d %f") % async_metadata.channel % timeout)); + } + + rfnoc::graph::sptr create_graph(const std::string &) { return rfnoc::graph::sptr(); } +}; + +device3::sptr make_pseudo_device() +{ + return device3::sptr(new pseudo_device3_impl()); +} + +class dummy_block_ctrl : public block_ctrl { + int foo; +}; + +BOOST_AUTO_TEST_CASE(test_device3) { + device3::sptr my_device = make_pseudo_device(); + + std::cout << "Checking block 0..." << std::endl; + BOOST_REQUIRE(my_device->find_blocks("Block").size()); + + std::cout << "Getting block 0..." << std::endl; + block_ctrl_base::sptr block0 = my_device->get_block_ctrl(my_device->find_blocks("Block")[0]); + BOOST_REQUIRE(block0); + BOOST_CHECK_EQUAL(block0->get_block_id(), "0/Block_0"); + + std::cout << "Checking block 1..." << std::endl; + BOOST_REQUIRE(my_device->has_block(block_id_t("0/Block_1"))); + + std::cout << "Getting block 1..." << std::endl; + block_ctrl_base::sptr block1 = my_device->get_block_ctrl(block_id_t("0/Block_1")); + BOOST_REQUIRE(block1); + BOOST_CHECK_EQUAL(block1->get_block_id(), "0/Block_1"); +} + +BOOST_AUTO_TEST_CASE(test_device3_cast) { + device3::sptr my_device = make_pseudo_device(); + + std::cout << "Getting block 0..." << std::endl; + block_ctrl::sptr block0 = my_device->get_block_ctrl<block_ctrl>(block_id_t("0/Block_0")); + BOOST_REQUIRE(block0); + BOOST_CHECK_EQUAL(block0->get_block_id(), "0/Block_0"); + + std::cout << "Getting block 1..." << std::endl; + block_ctrl_base::sptr block1 = my_device->get_block_ctrl<block_ctrl>(block_id_t("0/Block_1")); + BOOST_CHECK_EQUAL(block1->get_block_id(), "0/Block_1"); +} + +BOOST_AUTO_TEST_CASE(test_device3_fail) { + device3::sptr my_device = make_pseudo_device(); + + BOOST_CHECK(not my_device->has_block(block_id_t("0/FooBarBlock_0"))); + BOOST_CHECK(not my_device->has_block<dummy_block_ctrl>(block_id_t("0/Block_1"))); + + BOOST_CHECK(my_device->find_blocks("FooBarBlock").size() == 0); + BOOST_CHECK(my_device->find_blocks<block_ctrl>("FooBarBlock").size() == 0); + + BOOST_REQUIRE_THROW( + my_device->get_block_ctrl(block_id_t("0/FooBarBlock_17")), + uhd::lookup_error + ); + BOOST_REQUIRE_THROW( + my_device->get_block_ctrl<dummy_block_ctrl>(block_id_t("0/Block_1")), + uhd::lookup_error + ); +} + +// vim: sw=4 et: diff --git a/host/tests/graph.hpp b/host/tests/graph.hpp new file mode 100644 index 000000000..0c2be0347 --- /dev/null +++ b/host/tests/graph.hpp @@ -0,0 +1,51 @@ +// +// 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_TEST_GRAPH_HPP +#define INCLUDED_TEST_GRAPH_HPP + +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/rfnoc/sink_node_ctrl.hpp> +#include <uhd/rfnoc/source_node_ctrl.hpp> + +#define MAKE_NODE(name) test_node::sptr name(new test_node(#name)); + +// Smallest possible test class +class test_node : virtual public uhd::rfnoc::sink_node_ctrl, virtual public uhd::rfnoc::source_node_ctrl +{ +public: + typedef boost::shared_ptr<test_node> sptr; + + test_node(const std::string &test_id) : _test_id(test_id) {}; + + void issue_stream_cmd(const uhd::stream_cmd_t &, const size_t) {/* nop */}; + + std::string get_test_id() const { return _test_id; }; + +private: + const std::string _test_id; + +}; /* class test_node */ + +void connect_nodes(uhd::rfnoc::source_node_ctrl::sptr A, uhd::rfnoc::sink_node_ctrl::sptr B) +{ + A->connect_downstream(B); + B->connect_upstream(A); +} + +#endif /* INCLUDED_TEST_GRAPH_HPP */ +// vim: sw=4 et: diff --git a/host/tests/graph_search_test.cpp b/host/tests/graph_search_test.cpp new file mode 100644 index 000000000..4106d724e --- /dev/null +++ b/host/tests/graph_search_test.cpp @@ -0,0 +1,169 @@ +// +// 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 "graph.hpp" +#include <boost/test/unit_test.hpp> +#include <iostream> + +using namespace uhd::rfnoc; + +// test class derived, this is what we search for +class result_node : public test_node +{ +public: + typedef boost::shared_ptr<result_node> sptr; + + result_node(const std::string &test_id) : test_node(test_id) {}; + +}; /* class result_node */ + +#define MAKE_RESULT_NODE(name) result_node::sptr name(new result_node(#name)); + +BOOST_AUTO_TEST_CASE(test_simplest_downstream_search) +{ + MAKE_NODE(node_A); + MAKE_NODE(node_B); + + // Simplest possible scenario: Connect B downstream of A and let + // A find B + connect_nodes(node_A, node_B); + + test_node::sptr result = node_A->find_downstream_node<test_node>()[0]; + BOOST_REQUIRE(result); + BOOST_CHECK_EQUAL(result->get_test_id(), "node_B"); +} + +BOOST_AUTO_TEST_CASE(test_simple_downstream_search) +{ + MAKE_NODE(node_A); + MAKE_NODE(node_B0); + MAKE_NODE(node_B1); + + // Simple scenario: Connect both B{1,2} downstream of A and let + // it find them + connect_nodes(node_A, node_B0); + connect_nodes(node_A, node_B1); + + // We're still searching for test_node, so any downstream block will match + std::vector< test_node::sptr > result = node_A->find_downstream_node<test_node>(); + BOOST_REQUIRE(result.size() == 2); + BOOST_CHECK( + (result[0]->get_test_id() == "node_B0" and result[1]->get_test_id() == "node_B1") or + (result[1]->get_test_id() == "node_B0" and result[0]->get_test_id() == "node_B1") + ); + BOOST_CHECK(result[0] == node_B0 or result[0] == node_B1); +} + +BOOST_AUTO_TEST_CASE(test_linear_downstream_search) +{ + MAKE_NODE(node_A); + MAKE_RESULT_NODE(node_B); + MAKE_RESULT_NODE(node_C); + + // Slightly more complex graph: + connect_nodes(node_A, node_B); + connect_nodes(node_B, node_C); + + // This time, we search for result_node + std::vector< result_node::sptr > result = node_A->find_downstream_node<result_node>(); + std::cout << "size: " << result.size() << std::endl; + BOOST_CHECK_EQUAL(result.size(), 1); + BOOST_CHECK_EQUAL(result[0]->get_test_id(), "node_B"); + BOOST_FOREACH(const result_node::sptr &node, result) { + std::cout << node->get_test_id() << std::endl; + } +} + +BOOST_AUTO_TEST_CASE(test_multi_iter_downstream_search) +{ + MAKE_NODE(node_A); + MAKE_NODE(node_B0); + MAKE_NODE(node_B1); + MAKE_NODE(node_C0); + MAKE_RESULT_NODE(node_C1); + MAKE_RESULT_NODE(node_C2); + MAKE_RESULT_NODE(node_C3); + MAKE_RESULT_NODE(node_D0); + + // Slightly more complex graph: + connect_nodes(node_A, node_B0); + connect_nodes(node_A, node_B1); + connect_nodes(node_B0, node_C0); + connect_nodes(node_B0, node_C1); + connect_nodes(node_B1, node_C2); + connect_nodes(node_B1, node_C3); + connect_nodes(node_C0, node_D0); + + // This time, we search for result_node + std::vector< result_node::sptr > result = node_A->find_downstream_node<result_node>(); + BOOST_REQUIRE(result.size() == 4); + BOOST_FOREACH(const result_node::sptr &node, result) { + std::cout << node->get_test_id() << std::endl; + } +} + +BOOST_AUTO_TEST_CASE(test_multi_iter_cycle_downstream_search) +{ + MAKE_NODE(node_A); + MAKE_NODE(node_B0); + MAKE_NODE(node_B1); + MAKE_NODE(node_C0); + MAKE_RESULT_NODE(node_C1); + MAKE_RESULT_NODE(node_C2); + MAKE_RESULT_NODE(node_C3); + MAKE_RESULT_NODE(node_D0); + + // Slightly more complex graph: + connect_nodes(node_A, node_B0); + // This connection goes both ways, causing a cycle + connect_nodes(node_A, node_B1); connect_nodes(node_B1, node_A); + connect_nodes(node_B0, node_C0); + connect_nodes(node_B0, node_C1); + connect_nodes(node_B1, node_C2); + connect_nodes(node_B1, node_C3); + connect_nodes(node_C0, node_D0); + + // This time, we search for result_node + std::vector< result_node::sptr > result = node_A->find_downstream_node<result_node>(); + BOOST_REQUIRE(result.size() == 4); + BOOST_FOREACH(const result_node::sptr &node, result) { + std::cout << node->get_test_id() << std::endl; + } +} + +BOOST_AUTO_TEST_CASE(test_mini_cycle_downstream_and_upstream) +{ + MAKE_NODE(node_A); + MAKE_NODE(node_B); + + // Connect them in a loop + connect_nodes(node_A, node_B); connect_nodes(node_B, node_A); + + std::vector< test_node::sptr > result; + result = node_A->find_downstream_node<test_node>(); + BOOST_REQUIRE_EQUAL(result.size(), 1); + BOOST_REQUIRE(result[0] == node_B); + result = node_B->find_downstream_node<test_node>(); + BOOST_REQUIRE_EQUAL(result.size(), 1); + BOOST_REQUIRE(result[0] == node_A); + result = node_A->find_upstream_node<test_node>(); + BOOST_REQUIRE_EQUAL(result.size(), 1); + BOOST_REQUIRE(result[0] == node_B); + result = node_B->find_upstream_node<test_node>(); + BOOST_REQUIRE_EQUAL(result.size(), 1); + BOOST_REQUIRE(result[0] == node_A); +} diff --git a/host/tests/nocscript_common.hpp b/host/tests/nocscript_common.hpp new file mode 100644 index 000000000..7467e05fb --- /dev/null +++ b/host/tests/nocscript_common.hpp @@ -0,0 +1,35 @@ +// +// 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 "../lib/rfnoc/nocscript/expression.hpp" +#include <boost/assign/list_of.hpp> + +using namespace uhd::rfnoc::nocscript; + +// Some global defs to make tests easier to write +expression_function::argtype_list_type one_int_arg = boost::assign::list_of(expression::TYPE_INT); +expression_function::argtype_list_type two_int_args = boost::assign::list_of(expression::TYPE_INT)(expression::TYPE_INT); +expression_function::argtype_list_type one_double_arg = boost::assign::list_of(expression::TYPE_DOUBLE); +expression_function::argtype_list_type two_double_args = boost::assign::list_of(expression::TYPE_DOUBLE)(expression::TYPE_DOUBLE); +expression_function::argtype_list_type one_bool_arg = boost::assign::list_of(expression::TYPE_BOOL); +expression_function::argtype_list_type two_bool_args = boost::assign::list_of(expression::TYPE_BOOL)(expression::TYPE_BOOL); +expression_function::argtype_list_type no_args; + +expression_container::expr_list_type empty_arg_list; + +#define E(x) expression_literal::make(x) + diff --git a/host/tests/nocscript_expr_test.cpp b/host/tests/nocscript_expr_test.cpp new file mode 100644 index 000000000..2ee5dafdf --- /dev/null +++ b/host/tests/nocscript_expr_test.cpp @@ -0,0 +1,451 @@ +// +// 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 "../lib/rfnoc/nocscript/function_table.hpp" +#include <boost/test/unit_test.hpp> +#include <boost/test/floating_point_comparison.hpp> +#include <boost/foreach.hpp> +#include <boost/bind.hpp> +#include <boost/make_shared.hpp> +#include <boost/format.hpp> +#include <algorithm> +#include <iostream> + +#include "nocscript_common.hpp" + +// We need this global variable for one of the later tests +int and_counter = 0; + +BOOST_AUTO_TEST_CASE(test_literals) +{ + expression_literal literal_int("5", expression::TYPE_INT); + BOOST_CHECK_EQUAL(literal_int.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(literal_int.get_int(), 5); + BOOST_CHECK_EQUAL(literal_int.to_bool(), true); + BOOST_REQUIRE_THROW(literal_int.get_string(), uhd::type_error); + BOOST_REQUIRE_THROW(literal_int.get_bool(), uhd::type_error); + + expression_literal literal_int0("0", expression::TYPE_INT); + BOOST_CHECK_EQUAL(literal_int0.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(literal_int0.to_bool(), false); + + expression_literal literal_double("2.3", expression::TYPE_DOUBLE); + BOOST_CHECK_EQUAL(literal_double.infer_type(), expression::TYPE_DOUBLE); + BOOST_CHECK_CLOSE(literal_double.get_double(), 2.3, 0.01); + BOOST_CHECK_EQUAL(literal_double.to_bool(), true); + BOOST_REQUIRE_THROW(literal_double.get_string(), uhd::type_error); + BOOST_REQUIRE_THROW(literal_double.get_bool(), uhd::type_error); + + expression_literal literal_bool(true); + BOOST_CHECK_EQUAL(literal_bool.infer_type(), expression::TYPE_BOOL); + BOOST_CHECK_EQUAL(literal_bool.get_bool(), true); + BOOST_CHECK_EQUAL(literal_bool.to_bool(), true); + BOOST_CHECK_EQUAL(literal_bool.eval().get_bool(), true); + BOOST_REQUIRE_THROW(literal_bool.get_string(), uhd::type_error); + BOOST_REQUIRE_THROW(literal_bool.get_int(), uhd::type_error); + + expression_literal literal_bool_false(false); + BOOST_CHECK_EQUAL(literal_bool_false.infer_type(), expression::TYPE_BOOL); + BOOST_CHECK_EQUAL(literal_bool_false.get_bool(), false); + BOOST_CHECK_EQUAL(literal_bool_false.to_bool(), false); + BOOST_REQUIRE_EQUAL(literal_bool_false.eval().get_bool(), false); + BOOST_REQUIRE_THROW(literal_bool_false.get_string(), uhd::type_error); + BOOST_REQUIRE_THROW(literal_bool_false.get_int(), uhd::type_error); + + expression_literal literal_string("'foo bar'", expression::TYPE_STRING); + BOOST_CHECK_EQUAL(literal_string.infer_type(), expression::TYPE_STRING); + BOOST_CHECK_EQUAL(literal_string.get_string(), "foo bar"); + BOOST_REQUIRE_THROW(literal_string.get_bool(), uhd::type_error); + BOOST_REQUIRE_THROW(literal_string.get_int(), uhd::type_error); + + expression_literal literal_int_vec("[1, 2, 3]", expression::TYPE_INT_VECTOR); + BOOST_CHECK_EQUAL(literal_int_vec.infer_type(), expression::TYPE_INT_VECTOR); + std::vector<int> test_data = boost::assign::list_of(1)(2)(3); + std::vector<int> result = literal_int_vec.get_int_vector(); + BOOST_CHECK_EQUAL_COLLECTIONS(test_data.begin(), test_data.end(), + result.begin(), result.end()); + BOOST_REQUIRE_THROW(literal_int_vec.get_bool(), uhd::type_error); + BOOST_REQUIRE_THROW(literal_int_vec.get_int(), uhd::type_error); +} + + +// Need those for the variable testing: +expression::type_t variable_get_type(const std::string &var_name) +{ + if (var_name == "spp") { + std::cout << "Returning type for $spp..." << std::endl; + return expression::TYPE_INT; + } + if (var_name == "is_true") { + std::cout << "Returning type for $is_true..." << std::endl; + return expression::TYPE_BOOL; + } + + throw uhd::syntax_error("Cannot infer type (unknown variable)"); +} + +expression_literal variable_get_value(const std::string &var_name) +{ + if (var_name == "spp") { + std::cout << "Returning value for $spp..." << std::endl; + return expression_literal(5); + } + if (var_name == "is_true") { + std::cout << "Returning value for $is_true..." << std::endl; + return expression_literal(true); + } + + throw uhd::syntax_error("Cannot read value (unknown variable)"); +} + +BOOST_AUTO_TEST_CASE(test_variables) +{ + BOOST_REQUIRE_THROW( + expression_variable v_fail( + "foo", // Invalid token + boost::bind(&variable_get_type, _1), boost::bind(&variable_get_value, _1) + ), + uhd::assertion_error + ); + + expression_variable v( + "$spp", // The token + boost::bind(&variable_get_type, _1), // type-getter + boost::bind(&variable_get_value, _1) // value-getter + ); + BOOST_CHECK_EQUAL(v.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(v.eval().get_int(), 5); +} + +BOOST_AUTO_TEST_CASE(test_container) +{ + // Create some sub-expressions: + expression_literal::sptr l_true = E(true); + expression_literal::sptr l_false = E(false); + expression_literal::sptr l_int = E(5); + BOOST_REQUIRE_EQUAL(l_false->get_bool(), false); + BOOST_REQUIRE_EQUAL(l_false->to_bool(), false); + expression_variable::sptr l_boolvar = boost::make_shared<expression_variable>( + "$is_true", + boost::bind(&variable_get_type, _1), + boost::bind(&variable_get_value, _1) + ); + + // This will throw anytime it's evaluated: + expression_variable::sptr l_failvar = boost::make_shared<expression_variable>( + "$does_not_exist", + boost::bind(&variable_get_type, _1), + boost::bind(&variable_get_value, _1) + ); + + expression_container c; + std::cout << "One true, OR: " << std::endl; + c.add(l_true); + c.set_combiner_safe(expression_container::COMBINE_OR); + expression_literal ret_val_1 = c.eval(); + BOOST_CHECK_EQUAL(ret_val_1.infer_type(), expression::TYPE_BOOL); + BOOST_CHECK_EQUAL(ret_val_1.eval().get_bool(), true); + + std::cout << std::endl << std::endl << "Two true, one false, OR: " << std::endl; + c.add(l_true); + c.add(l_false); + expression_literal ret_val_2 = c.eval(); + BOOST_CHECK_EQUAL(ret_val_2.infer_type(), expression::TYPE_BOOL); + BOOST_CHECK_EQUAL(ret_val_2.eval().get_bool(), true); + + expression_container c2; + c2.add(l_false); + c2.add(l_false); + c2.set_combiner(expression_container::COMBINE_AND); + std::cout << std::endl << std::endl << "Two false, AND: " << std::endl; + expression_literal ret_val_3 = c2.eval(); + BOOST_CHECK_EQUAL(ret_val_3.infer_type(), expression::TYPE_BOOL); + BOOST_REQUIRE_EQUAL(ret_val_3.eval().get_bool(), false); + + c2.add(l_failvar); + // Will not fail, because l_failvar never gets eval'd: + expression_literal ret_val_4 = c2.eval(); + BOOST_CHECK_EQUAL(ret_val_4.infer_type(), expression::TYPE_BOOL); + BOOST_CHECK_EQUAL(ret_val_4.eval().get_bool(), false); + + // Same here: + c.add(l_failvar); + expression_literal ret_val_5 = c.eval(); + BOOST_CHECK_EQUAL(ret_val_5.infer_type(), expression::TYPE_BOOL); + BOOST_CHECK_EQUAL(ret_val_5.eval().get_bool(), true); + + // Now it'll throw: + c.set_combiner(expression_container::COMBINE_ALL); + BOOST_REQUIRE_THROW(c.eval(), uhd::syntax_error); + + std::cout << "Checking type inference on ',' sequences: " << std::endl; + // Check types match + BOOST_CHECK_EQUAL(c2.infer_type(), expression::TYPE_BOOL); + expression_container c3; + c3.set_combiner(expression_container::COMBINE_ALL); + c3.add(l_false); + c3.add(l_int); + BOOST_CHECK_EQUAL(c3.infer_type(), expression::TYPE_INT); +} + + +// We'll define two functions here: ADD and XOR. The former shall +// be defined for INT and DOUBLE +class functable_mockup_impl : public function_table +{ + public: + functable_mockup_impl(void) {}; + + bool function_exists(const std::string &name) const { + return name == "ADD" or name == "XOR" or name == "AND"; + } + + bool function_exists( + const std::string &name, + const expression_function::argtype_list_type &arg_types + ) const { + if (name == "ADD") { + if (arg_types.size() == 2 + and arg_types[0] == expression::TYPE_DOUBLE + and arg_types[1] == expression::TYPE_DOUBLE + ) { + return true; + } + if (arg_types.size() == 2 + and arg_types[0] == expression::TYPE_INT + and arg_types[1] == expression::TYPE_INT + ) { + return true; + } + return false; + } + + if (name == "XOR" or name == "AND") { + if (arg_types.size() == 2 + and arg_types[0] == expression::TYPE_BOOL + and arg_types[1] == expression::TYPE_BOOL + ) { + return true; + } + return false; + } + + return false; + } + + expression::type_t get_type( + const std::string &name, + const expression_function::argtype_list_type &arg_types + ) const { + if (not function_exists(name, arg_types)) { + throw uhd::syntax_error(str( + boost::format("[EXPR_TEXT] get_type(): Unknown function: %s, %d arguments") + % name % arg_types.size() + )); + } + + if (name == "XOR" or name == "AND") { + return expression::TYPE_BOOL; + } + if (name == "ADD") { + return arg_types[0]; + } + UHD_THROW_INVALID_CODE_PATH(); + } + + expression_literal eval( + const std::string &name, + const expression_function::argtype_list_type &arg_types, + expression_container::expr_list_type &args + ) { + if (name == "XOR") { + if (arg_types.size() != 2 + or args.size() != 2 + or arg_types[0] != expression::TYPE_BOOL + or arg_types[1] != expression::TYPE_BOOL + or args[0]->infer_type() != expression::TYPE_BOOL + or args[1]->infer_type() != expression::TYPE_BOOL + ) { + throw uhd::syntax_error("eval(): XOR type mismatch"); + } + return expression_literal(bool( + args[0]->eval().get_bool() xor args[1]->eval().get_bool() + )); + } + + if (name == "AND") { + if (arg_types.size() != 2 + or args.size() != 2 + or arg_types[0] != expression::TYPE_BOOL + or arg_types[1] != expression::TYPE_BOOL + or args[0]->infer_type() != expression::TYPE_BOOL + or args[1]->infer_type() != expression::TYPE_BOOL + ) { + throw uhd::syntax_error("eval(): AND type mismatch"); + } + std::cout << "Calling AND" << std::endl; + and_counter++; + return expression_literal(bool( + args[0]->eval().get_bool() and args[1]->eval().get_bool() + )); + } + + if (name == "ADD") { + if (args.size() != 2) { + throw uhd::syntax_error("eval(): ADD type mismatch"); + } + if ((args[0]->infer_type() == expression::TYPE_INT) and + (args[1]->infer_type() == expression::TYPE_INT)) { + return expression_literal(int( + args[0]->eval().get_int() + args[1]->eval().get_int() + )); + } + else if ((args[0]->infer_type() == expression::TYPE_DOUBLE) and + (args[1]->infer_type() == expression::TYPE_DOUBLE)) { + return expression_literal(double( + args[0]->eval().get_double() + args[1]->eval().get_double() + )); + } + throw uhd::syntax_error("eval(): ADD type mismatch"); + } + throw uhd::syntax_error("eval(): unknown function"); + } + + // We don't actually need this + 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 + ) {}; + +}; + + +// The annoying part: Testing the test fixtures +BOOST_AUTO_TEST_CASE(test_functable_mockup) +{ + functable_mockup_impl functable; + + BOOST_CHECK(functable.function_exists("ADD")); + BOOST_CHECK(functable.function_exists("XOR")); + BOOST_CHECK(not functable.function_exists("FOOBAR")); + + BOOST_CHECK(functable.function_exists("ADD", two_int_args)); + BOOST_CHECK(functable.function_exists("ADD", two_double_args)); + BOOST_CHECK(functable.function_exists("XOR", two_bool_args)); + BOOST_CHECK(not functable.function_exists("ADD", two_bool_args)); + BOOST_CHECK(not functable.function_exists("ADD", no_args)); + BOOST_CHECK(not functable.function_exists("XOR", no_args)); + + BOOST_CHECK_EQUAL(functable.get_type("ADD", two_int_args), expression::TYPE_INT); + BOOST_CHECK_EQUAL(functable.get_type("ADD", two_double_args), expression::TYPE_DOUBLE); + BOOST_CHECK_EQUAL(functable.get_type("XOR", two_bool_args), expression::TYPE_BOOL); + + expression_container::expr_list_type add_args_int = + boost::assign::list_of(E(2))(E(3)) + ; + expression_container::expr_list_type add_args_dbl = + boost::assign::list_of + (E(2.25)) + (E(5.0)) + ; + expression_container::expr_list_type xor_args_bool = + boost::assign::list_of + (E(true)) + (E(false)) + ; + + BOOST_CHECK_EQUAL(functable.eval("ADD", two_int_args, add_args_int), expression_literal(5)); + BOOST_CHECK_EQUAL(functable.eval("ADD", two_double_args, add_args_dbl), expression_literal(7.25)); + BOOST_CHECK_EQUAL(functable.eval("XOR", two_bool_args, xor_args_bool), expression_literal(true)); +} + +BOOST_AUTO_TEST_CASE(test_function_expression) +{ + function_table::sptr ft = boost::make_shared<functable_mockup_impl>(); + + // Very simple function: ADD(2, 3) + expression_function func1("ADD", ft); + func1.add(E(2)); + func1.add(E(3)); + + BOOST_CHECK_EQUAL(func1.eval(), expression_literal(5)); + + // More elaborate: ADD(ADD(2, 3), ADD(ADD(4, 5), 6)) ?= 20 + // f4 f1 f3 f2 + expression_function f1("ADD", ft); + f1.add(E(2)); + f1.add(E(3)); + expression_function f2("ADD", ft); + f2.add(E(4)); + f2.add(E(5)); + expression_function f3("ADD", ft); + f3.add(boost::make_shared<expression_function>(f2)); + f3.add(E(6)); + expression_function f4("ADD", ft); + f4.add(boost::make_shared<expression_function>(f1)); + f4.add(boost::make_shared<expression_function>(f3)); + + BOOST_CHECK_EQUAL(f4.eval().get_int(), 20); +} + +BOOST_AUTO_TEST_CASE(test_function_expression_laziness) +{ + function_table::sptr ft = boost::make_shared<functable_mockup_impl>(); + + // We run AND(AND(false, false), AND(false, false)). + // f1 f2 f3 + // That makes three ANDs + // in total. However, we will only see AND being evaluated twice, because + // the outcome is clear after running the first AND in the argument list. + expression_function::sptr f2 = boost::make_shared<expression_function>("AND", ft); + f2->add(E(false)); + f2->add(E(false)); + BOOST_CHECK(not f2->eval().get_bool()); + + expression_function::sptr f3 = boost::make_shared<expression_function>("AND", ft); + f3->add(E(false)); + f3->add(E(false)); + BOOST_CHECK(not f3->eval().get_bool()); + + and_counter = 0; + expression_function::sptr f1 = boost::make_shared<expression_function>("AND", ft); + f1->add(f2); + f1->add(f3); + + BOOST_CHECK(not f1->eval().get_bool()); + BOOST_CHECK_EQUAL(and_counter, 2); +} + +BOOST_AUTO_TEST_CASE(test_sptrs) +{ + expression_container::sptr c = expression_container::make(); + BOOST_CHECK_EQUAL(c->infer_type(), expression::TYPE_BOOL); + BOOST_CHECK(c->eval().get_bool()); + + expression_variable::sptr v = expression_variable::make( + "$spp", + boost::bind(&variable_get_type, _1), // type-getter + boost::bind(&variable_get_value, _1) // value-getter + ); + + c->add(v); + BOOST_REQUIRE_EQUAL(c->infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(c->eval().get_int(), 5); +} + diff --git a/host/tests/nocscript_ftable_test.cpp b/host/tests/nocscript_ftable_test.cpp new file mode 100644 index 000000000..283245132 --- /dev/null +++ b/host/tests/nocscript_ftable_test.cpp @@ -0,0 +1,259 @@ +// +// 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 "../lib/rfnoc/nocscript/function_table.hpp" +#include <boost/test/unit_test.hpp> +#include <boost/test/floating_point_comparison.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/foreach.hpp> +#include <boost/bind.hpp> +#include <boost/make_shared.hpp> +#include <algorithm> +#include <iostream> + +#include "nocscript_common.hpp" + +BOOST_AUTO_TEST_CASE(test_basic_funcs) +{ + function_table::sptr ft = function_table::make(); + BOOST_CHECK(ft->function_exists("ADD")); + BOOST_CHECK(ft->function_exists("ADD", two_int_args)); + BOOST_CHECK(ft->function_exists("LE", two_int_args)); + BOOST_CHECK(ft->function_exists("GE")); + BOOST_CHECK(ft->function_exists("GE", two_int_args)); + BOOST_CHECK(ft->function_exists("TRUE")); + BOOST_CHECK(ft->function_exists("FALSE")); + + // Math + expression_container::expr_list_type two_int_values = boost::assign::list_of(E(2))(E(3)); + expression_container::expr_list_type two_int_values2 = boost::assign::list_of(E(3))(E(2)); + expression_container::expr_list_type two_double_values = boost::assign::list_of(E(2.0))(E(3.0)); + + BOOST_REQUIRE_EQUAL(ft->get_type("ADD", two_int_args), expression::TYPE_INT); + expression_literal e_add = ft->eval("ADD", two_int_args, two_int_values); + BOOST_REQUIRE_EQUAL(e_add.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(e_add.get_int(), 5); + + BOOST_REQUIRE_EQUAL(ft->get_type("MULT", two_int_args), expression::TYPE_INT); + expression_literal e_mult_i = ft->eval("MULT", two_int_args, two_int_values); + BOOST_REQUIRE_EQUAL(e_mult_i.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(e_mult_i.get_int(), 6); + + BOOST_REQUIRE_EQUAL(ft->get_type("MULT", two_double_args), expression::TYPE_DOUBLE); + expression_literal e_mult_d = ft->eval("MULT", two_double_args, two_double_values); + BOOST_REQUIRE_EQUAL(e_mult_d.infer_type(), expression::TYPE_DOUBLE); + BOOST_CHECK_CLOSE(e_mult_d.get_double(), 6.0, 0.01); + + BOOST_REQUIRE_EQUAL(ft->get_type("DIV", two_double_args), expression::TYPE_DOUBLE); + expression_literal e_div_d = ft->eval("DIV", two_double_args, two_double_values); + BOOST_REQUIRE_EQUAL(e_div_d.infer_type(), expression::TYPE_DOUBLE); + BOOST_CHECK_CLOSE(e_div_d.get_double(), 2.0/3.0, 0.01); + + BOOST_REQUIRE_EQUAL(ft->get_type("MODULO", two_int_args), expression::TYPE_INT); + expression_literal e_modulo_i = ft->eval("MODULO", two_int_args, two_int_values); + BOOST_REQUIRE_EQUAL(e_modulo_i.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(e_modulo_i.get_int(), 2 % 3); + + BOOST_REQUIRE_EQUAL(ft->get_type("LE", two_int_args), expression::TYPE_BOOL); + expression_literal e_le = ft->eval("LE", two_int_args, two_int_values); + BOOST_REQUIRE_EQUAL(e_le.infer_type(), expression::TYPE_BOOL); + BOOST_CHECK_EQUAL(e_le.get_bool(), true); + BOOST_CHECK_EQUAL(ft->eval("LE", two_int_args, two_int_values2).get_bool(), false); + expression_literal e_ge = ft->eval("GE", two_int_args, two_int_values); + BOOST_REQUIRE_EQUAL(ft->get_type("GE", two_int_args), expression::TYPE_BOOL); + BOOST_REQUIRE_EQUAL(e_ge.infer_type(), expression::TYPE_BOOL); + BOOST_CHECK_EQUAL(e_ge.get_bool(), false); + + expression_container::expr_list_type sixty_four = boost::assign::list_of(E(64)); + expression_literal e_pwr2 = ft->eval("IS_PWR_OF_2", one_int_arg, sixty_four); + BOOST_REQUIRE_EQUAL(e_pwr2.infer_type(), expression::TYPE_BOOL); + BOOST_CHECK_EQUAL(e_pwr2.get_bool(), true); + expression_literal e_log2 = ft->eval("LOG2", one_int_arg, sixty_four); + BOOST_REQUIRE_EQUAL(e_log2.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(e_log2.get_int(), 6); + + // Boolean Logic + expression_container::expr_list_type e_true = boost::assign::list_of(E(true)); + expression_container::expr_list_type e_false = boost::assign::list_of(E(false)); + BOOST_CHECK(ft->eval("TRUE", no_args, empty_arg_list).to_bool()); + BOOST_CHECK(ft->eval("TRUE", no_args, empty_arg_list).get_bool()); + BOOST_CHECK_EQUAL(ft->eval("FALSE", no_args, empty_arg_list).to_bool(), false); + BOOST_CHECK_EQUAL(ft->eval("FALSE", no_args, empty_arg_list).get_bool(), false); + BOOST_CHECK(ft->eval("NOT", one_bool_arg, e_false).to_bool()); + + // Control + std::cout << "Checking ~1s sleep until... "; + expression_container::expr_list_type e_sleeptime = boost::assign::list_of(E(.999)); + BOOST_CHECK(ft->eval("SLEEP", one_double_arg, e_sleeptime).get_bool()); + std::cout << "Now." << std::endl; +} + +// Some bogus function to test the registry +expression_literal add_plus2_int(expression_container::expr_list_type args) +{ + return expression_literal(args[0]->eval().get_int() + args[1]->eval().get_int() + 2); +} + +BOOST_AUTO_TEST_CASE(test_add_funcs) +{ + function_table::sptr ft = function_table::make(); + + BOOST_CHECK(not ft->function_exists("ADD_PLUS_2")); + + expression_function::argtype_list_type add_int_args = boost::assign::list_of(expression::TYPE_INT)(expression::TYPE_INT); + ft->register_function( + "ADD_PLUS_2", + boost::bind(&add_plus2_int, _1), + expression::TYPE_INT, + add_int_args + ); + + BOOST_CHECK(ft->function_exists("ADD_PLUS_2")); + BOOST_CHECK(ft->function_exists("ADD_PLUS_2", add_int_args)); + + expression_container::expr_list_type add_int_values = boost::assign::list_of(E(2))(E(3)); + expression_literal e = ft->eval("ADD_PLUS_2", two_int_args, add_int_values); + BOOST_REQUIRE_EQUAL(e.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(e.get_int(), 7); +} + +int dummy_true_counter = 0; +// Some bogus function to test the registry +expression_literal dummy_true(expression_container::expr_list_type) +{ + dummy_true_counter++; + std::cout << "Running dummy/true statement." << std::endl; + return expression_literal(true); +} + +int dummy_false_counter = 0; +// Some bogus function to test the registry +expression_literal dummy_false(expression_container::expr_list_type) +{ + dummy_false_counter++; + std::cout << "Running dummy/false statement." << std::endl; + return expression_literal(false); +} + +BOOST_AUTO_TEST_CASE(test_conditionals) +{ + function_table::sptr ft = function_table::make(); + ft->register_function( + "DUMMY", + boost::bind(&dummy_true, _1), + expression::TYPE_BOOL, + no_args + ); + ft->register_function( + "DUMMY_F", + boost::bind(&dummy_false, _1), + expression::TYPE_BOOL, + no_args + ); + BOOST_REQUIRE(ft->function_exists("DUMMY", no_args)); + BOOST_REQUIRE(ft->function_exists("DUMMY_F", no_args)); + + expression_function::sptr dummy_statement = boost::make_shared<expression_function>("DUMMY", ft); + expression_function::sptr if_statement = boost::make_shared<expression_function>("IF", ft); + if_statement->add(E(true)); + if_statement->add(dummy_statement); + + std::cout << "Dummy statement should run once until END:" << std::endl; + dummy_true_counter = 0; + BOOST_CHECK(if_statement->eval().get_bool()); + BOOST_CHECK_EQUAL(dummy_true_counter, 1); + std::cout << "END." << std::endl; + + std::cout << "Dummy statement should not run until END:" << std::endl; + expression_function::sptr if_statement2 = boost::make_shared<expression_function>("IF", ft); + if_statement2->add(E(false)); + if_statement2->add(dummy_statement); + dummy_true_counter = 0; + BOOST_CHECK(not if_statement2->eval().get_bool()); + BOOST_CHECK_EQUAL(dummy_true_counter, 0); + std::cout << "END." << std::endl; + + expression_function::sptr if_else_statement = boost::make_shared<expression_function>("IF_ELSE", ft); + expression_function::sptr dummy_statement_f = boost::make_shared<expression_function>("DUMMY_F", ft); + if_else_statement->add(E(true)); + if_else_statement->add(dummy_statement); + if_else_statement->add(dummy_statement_f); + dummy_true_counter = 0; + dummy_false_counter = 0; + std::cout << "Should execute dummy/true statement before END:" << std::endl; + BOOST_CHECK(if_else_statement->eval().get_bool()); + BOOST_CHECK_EQUAL(dummy_true_counter, 1); + BOOST_CHECK_EQUAL(dummy_false_counter, 0); + std::cout << "END." << std::endl; + + expression_function::sptr if_else_statement2 = boost::make_shared<expression_function>("IF_ELSE", ft); + if_else_statement2->add(E(false)); + if_else_statement2->add(dummy_statement); + if_else_statement2->add(dummy_statement_f); + dummy_true_counter = 0; + dummy_false_counter = 0; + std::cout << "Should execute dummy/false statement before END:" << std::endl; + BOOST_CHECK(not if_else_statement2->eval().get_bool()); + BOOST_CHECK_EQUAL(dummy_true_counter, 0); + BOOST_CHECK_EQUAL(dummy_false_counter, 1); + std::cout << "END." << std::endl; +} + +BOOST_AUTO_TEST_CASE(test_bitwise_funcs) +{ + function_table::sptr ft = function_table::make(); + BOOST_CHECK(ft->function_exists("SHIFT_RIGHT")); + BOOST_CHECK(ft->function_exists("SHIFT_RIGHT", two_int_args)); + BOOST_CHECK(ft->function_exists("SHIFT_LEFT")); + BOOST_CHECK(ft->function_exists("SHIFT_LEFT", two_int_args)); + BOOST_CHECK(ft->function_exists("BITWISE_AND")); + BOOST_CHECK(ft->function_exists("BITWISE_AND", two_int_args)); + BOOST_CHECK(ft->function_exists("BITWISE_OR")); + BOOST_CHECK(ft->function_exists("BITWISE_OR", two_int_args)); + BOOST_CHECK(ft->function_exists("BITWISE_XOR")); + BOOST_CHECK(ft->function_exists("BITWISE_XOR", two_int_args)); + + // Bitwise Math + int int_value1 = 0x2; + int int_value2 = 0x3; + expression_container::expr_list_type two_int_values = boost::assign::list_of(E(int_value1))(E(int_value2)); + + BOOST_REQUIRE_EQUAL(ft->get_type("SHIFT_RIGHT", two_int_args), expression::TYPE_INT); + expression_literal e_shift_right = ft->eval("SHIFT_RIGHT", two_int_args, two_int_values); + BOOST_REQUIRE_EQUAL(e_shift_right.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(e_shift_right.get_int(), int_value1 >> int_value2); + + BOOST_REQUIRE_EQUAL(ft->get_type("SHIFT_LEFT", two_int_args), expression::TYPE_INT); + expression_literal e_shift_left = ft->eval("SHIFT_LEFT", two_int_args, two_int_values); + BOOST_REQUIRE_EQUAL(e_shift_left.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(e_shift_left.get_int(), int_value1 << int_value2); + + BOOST_REQUIRE_EQUAL(ft->get_type("BITWISE_AND", two_int_args), expression::TYPE_INT); + expression_literal e_bitwise_and = ft->eval("BITWISE_AND", two_int_args, two_int_values); + BOOST_REQUIRE_EQUAL(e_bitwise_and.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(e_bitwise_and.get_int(), int_value1 & int_value2); + + BOOST_REQUIRE_EQUAL(ft->get_type("BITWISE_OR", two_int_args), expression::TYPE_INT); + expression_literal e_bitwise_or = ft->eval("BITWISE_OR", two_int_args, two_int_values); + BOOST_REQUIRE_EQUAL(e_bitwise_or.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(e_bitwise_or.get_int(), int_value1 | int_value2); + + BOOST_REQUIRE_EQUAL(ft->get_type("BITWISE_XOR", two_int_args), expression::TYPE_INT); + expression_literal e_bitwise_xor = ft->eval("BITWISE_XOR", two_int_args, two_int_values); + BOOST_REQUIRE_EQUAL(e_bitwise_xor.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(e_bitwise_xor.get_int(), int_value1 ^ int_value2); +} diff --git a/host/tests/nocscript_parser_test.cpp b/host/tests/nocscript_parser_test.cpp new file mode 100644 index 000000000..a9c25977e --- /dev/null +++ b/host/tests/nocscript_parser_test.cpp @@ -0,0 +1,167 @@ +// +// 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 "../lib/rfnoc/nocscript/function_table.hpp" +#include "../lib/rfnoc/nocscript/parser.hpp" +#include <uhd/exception.hpp> +#include <boost/test/unit_test.hpp> +#include <boost/test/floating_point_comparison.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/foreach.hpp> +#include <boost/bind.hpp> +#include <boost/make_shared.hpp> +#include <algorithm> +#include <iostream> + +#include "nocscript_common.hpp" + +const int SPP_VALUE = 64; + +// Need those for the variable testing: +expression::type_t variable_get_type(const std::string &var_name) +{ + if (var_name == "spp") { + std::cout << "Returning type for $spp..." << std::endl; + return expression::TYPE_INT; + } + if (var_name == "is_true") { + std::cout << "Returning type for $is_true..." << std::endl; + return expression::TYPE_BOOL; + } + + throw uhd::syntax_error("Cannot infer type (unknown variable)"); +} + +expression_literal variable_get_value(const std::string &var_name) +{ + if (var_name == "spp") { + std::cout << "Returning value for $spp..." << std::endl; + return expression_literal(SPP_VALUE); + } + if (var_name == "is_true") { + std::cout << "Returning value for $is_true..." << std::endl; + return expression_literal(true); + } + + throw uhd::syntax_error("Cannot read value (unknown variable)"); +} + +#define SETUP_FT_AND_PARSER() \ + function_table::sptr ft = function_table::make(); \ + parser::sptr p = parser::make( \ + ft, \ + boost::bind(&variable_get_type, _1), \ + boost::bind(&variable_get_value, _1) \ + ); + +BOOST_AUTO_TEST_CASE(test_fail) +{ + SETUP_FT_AND_PARSER(); + + // Missing closing parens: + BOOST_REQUIRE_THROW(p->create_expr_tree("ADD1(1, "), uhd::syntax_error); + // Double comma: + BOOST_REQUIRE_THROW(p->create_expr_tree("ADD(1,, 2)"), uhd::syntax_error); + // No comma: + BOOST_REQUIRE_THROW(p->create_expr_tree("ADD(1 2)"), uhd::syntax_error); + // Double closing parens: + BOOST_REQUIRE_THROW(p->create_expr_tree("ADD(1, 2))"), uhd::syntax_error); + // Unknown function: + BOOST_REQUIRE_THROW(p->create_expr_tree("GLORPGORP(1, 2)"), uhd::syntax_error); +} + +BOOST_AUTO_TEST_CASE(test_adds_no_vars) +{ + SETUP_FT_AND_PARSER(); + BOOST_REQUIRE(ft->function_exists("ADD")); + + const std::string line("ADD(1, ADD(2, ADD(3, 4)))"); + expression::sptr e = p->create_expr_tree(line); + expression_literal result = e->eval(); + + BOOST_REQUIRE_EQUAL(result.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(result.get_int(), 1+2+3+4); +} + +BOOST_AUTO_TEST_CASE(test_adds_with_vars) +{ + SETUP_FT_AND_PARSER(); + + const std::string line("ADD(1, ADD(2, $spp))"); + expression::sptr e = p->create_expr_tree(line); + expression_literal result = e->eval(); + + BOOST_REQUIRE_EQUAL(result.infer_type(), expression::TYPE_INT); + BOOST_CHECK_EQUAL(result.get_int(), 1+2+SPP_VALUE); +} + +BOOST_AUTO_TEST_CASE(test_fft_check) +{ + SETUP_FT_AND_PARSER(); + + const std::string line("GE($spp, 16) AND LE($spp, 4096) AND IS_PWR_OF_2($spp)"); + expression::sptr e = p->create_expr_tree(line); + expression_literal result = e->eval(); + + BOOST_REQUIRE_EQUAL(result.infer_type(), expression::TYPE_BOOL); + BOOST_CHECK(result.get_bool()); +} + +BOOST_AUTO_TEST_CASE(test_pure_string) +{ + SETUP_FT_AND_PARSER(); + + // Eval all, return last expression + const std::string line("'foo foo', \"bar\""); + expression_literal result = p->create_expr_tree(line)->eval(); + + BOOST_REQUIRE_EQUAL(result.infer_type(), expression::TYPE_STRING); + BOOST_CHECK_EQUAL(result.get_string(), "bar"); +} + +int dummy_false_counter = 0; +expression_literal dummy_false(expression_container::expr_list_type) +{ + dummy_false_counter++; + std::cout << "Running dummy/false statement." << std::endl; + return expression_literal(false); +} + +BOOST_AUTO_TEST_CASE(test_multi_commmand) +{ + SETUP_FT_AND_PARSER(); + + ft->register_function( + "DUMMY", + boost::bind(&dummy_false, _1), + expression::TYPE_BOOL, + no_args + ); + + dummy_false_counter = 0; + p->create_expr_tree("DUMMY(), DUMMY(), DUMMY()")->eval(); + BOOST_CHECK_EQUAL(dummy_false_counter, 3); + + dummy_false_counter = 0; + p->create_expr_tree("DUMMY() AND DUMMY() AND DUMMY()")->eval(); + BOOST_CHECK_EQUAL(dummy_false_counter, 1); + + dummy_false_counter = 0; + p->create_expr_tree("DUMMY() OR DUMMY() OR DUMMY()")->eval(); + BOOST_CHECK_EQUAL(dummy_false_counter, 3); +} + diff --git a/host/tests/node_connect_test.cpp b/host/tests/node_connect_test.cpp new file mode 100644 index 000000000..ff01828e3 --- /dev/null +++ b/host/tests/node_connect_test.cpp @@ -0,0 +1,143 @@ +// +// 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 "graph.hpp" +#include <boost/test/unit_test.hpp> +#include <iostream> + +using namespace uhd::rfnoc; + +class source_node : public test_node +{ +public: + typedef boost::shared_ptr<source_node> sptr; + + source_node(const std::string &test_id, size_t output_port) + : test_node(test_id) + , active_rx_streamer_on_port(0) + , _output_port(output_port) {}; + + void set_rx_streamer(bool active, const size_t port) + { + if (active) { + std::cout << "[source_node] Someone is registering a rx streamer on port " << port << std::endl; + active_rx_streamer_on_port = port; + } + } + size_t active_rx_streamer_on_port; + +protected: + size_t _request_output_port( + const size_t, + const uhd::device_addr_t & + ) const { + return _output_port; + } + + const size_t _output_port; + +}; /* class result_node */ + +class sink_node : public test_node +{ +public: + typedef boost::shared_ptr<sink_node> sptr; + + sink_node(const std::string &test_id, size_t input_port) + : test_node(test_id) + , active_tx_streamer_on_port(0) + , _input_port(input_port) {}; + + void set_tx_streamer(bool active, const size_t port) + { + if (active) { + std::cout << "[sink_node] Someone is registering a tx streamer on port " << port << std::endl; + active_tx_streamer_on_port = port; + } + } + size_t active_tx_streamer_on_port; + +protected: + size_t _request_input_port( + const size_t, + const uhd::device_addr_t & + ) const { + return _input_port; + } + + const size_t _input_port; + +}; /* class result_node */ + +#define MAKE_SOURCE_NODE(name, port) source_node::sptr name(new source_node(#name, port)); +#define MAKE_SINK_NODE(name, port) sink_node::sptr name(new sink_node(#name, port)); + +BOOST_AUTO_TEST_CASE(test_simple_connect) +{ + MAKE_SOURCE_NODE(node_A, 42); + MAKE_SINK_NODE(node_B, 23); + + size_t src_port = node_A->connect_downstream(node_B, 1); + size_t dst_port = node_B->connect_upstream(node_A, 2); + + BOOST_CHECK_EQUAL(src_port, 42); + BOOST_CHECK_EQUAL(dst_port, 23); + + node_A->set_downstream_port(src_port, dst_port); + node_B->set_upstream_port(dst_port, src_port); + BOOST_CHECK_EQUAL(node_A->get_downstream_port(src_port), dst_port); + BOOST_CHECK_EQUAL(node_B->get_upstream_port(dst_port), src_port); + + BOOST_REQUIRE_THROW(node_A->get_downstream_port(999), uhd::value_error); +} + +BOOST_AUTO_TEST_CASE(test_fail) +{ + MAKE_SOURCE_NODE(node_A, 42); + MAKE_SINK_NODE(node_B, ANY_PORT); + + node_A->connect_downstream(node_B, 1); + BOOST_REQUIRE_THROW(node_B->connect_upstream(node_A, 2), uhd::type_error); +} + +BOOST_AUTO_TEST_CASE(test_set_streamers) +{ + MAKE_SOURCE_NODE(node_A, 0); + MAKE_NODE(node_B); + MAKE_SINK_NODE(node_C, 0); + + size_t src_port_A = node_A->connect_downstream(node_B, 0); + size_t src_port_B = node_B->connect_downstream(node_C, 0); + size_t dst_port_B = node_B->connect_upstream(node_A, 0); + size_t dst_port_C = node_C->connect_upstream(node_B, 0); + + std::cout << "src_port_A: " << src_port_A << std::endl; + std::cout << "src_port_B: " << src_port_B << std::endl; + std::cout << "dst_port_B: " << dst_port_B << std::endl; + std::cout << "dst_port_C: " << dst_port_C << std::endl; + + node_A->set_downstream_port(src_port_A, dst_port_B); + node_B->set_upstream_port(dst_port_B, src_port_A); + node_B->set_downstream_port(src_port_B, dst_port_C); + node_C->set_upstream_port(dst_port_C, src_port_B); + + node_A->set_tx_streamer(true, 0); + node_C->set_rx_streamer(true, 0); + + BOOST_CHECK_EQUAL(node_A->active_rx_streamer_on_port, src_port_A); + BOOST_CHECK_EQUAL(node_C->active_tx_streamer_on_port, dst_port_C); +} diff --git a/host/tests/rate_node_test.cpp b/host/tests/rate_node_test.cpp new file mode 100644 index 000000000..2e17c02a5 --- /dev/null +++ b/host/tests/rate_node_test.cpp @@ -0,0 +1,139 @@ +// +// 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 "graph.hpp" +#include <uhd/rfnoc/rate_node_ctrl.hpp> +#include <boost/test/unit_test.hpp> +#include <iostream> + +using namespace uhd::rfnoc; + +// test class derived, knows about rates +class rate_aware_node : public test_node, public rate_node_ctrl +{ +public: + typedef boost::shared_ptr<rate_aware_node> sptr; + + rate_aware_node(const std::string &test_id) : test_node(test_id) {}; + +}; /* class rate_aware_node */ + +// test class derived, sets rates +class rate_setting_node : public test_node, public rate_node_ctrl +{ +public: + typedef boost::shared_ptr<rate_setting_node> sptr; + + rate_setting_node(const std::string &test_id, double samp_rate) : test_node(test_id), _samp_rate(samp_rate) {}; + + double get_input_samp_rate(size_t) { return _samp_rate; }; + double get_output_samp_rate(size_t) { return _samp_rate; }; + +private: + double _samp_rate; + +}; /* class rate_setting_node */ + +#define MAKE_RATE_NODE(name) rate_aware_node::sptr name(new rate_aware_node(#name)); +#define MAKE_RATE_SETTING_NODE(name, rate) rate_setting_node::sptr name(new rate_setting_node(#name, rate)); + +BOOST_AUTO_TEST_CASE(test_simplest_downstream_search) +{ + const double test_rate = 0.25; + MAKE_RATE_NODE(node_A); + MAKE_RATE_SETTING_NODE(node_B, test_rate); + + // Simplest possible scenario: Connect B downstream of A and let + // it find B + connect_nodes(node_A, node_B); + + double result_rate = node_A->get_input_samp_rate(); + BOOST_CHECK_EQUAL(result_rate, test_rate); +} + +BOOST_AUTO_TEST_CASE(test_skip_downstream_search) +{ + const double test_rate = 0.25; + MAKE_RATE_NODE(node_A); + MAKE_NODE(node_B); + MAKE_RATE_SETTING_NODE(node_C, test_rate); + + // Slightly more elaborate: Add another block in between that has no + // clue about rates + connect_nodes(node_A, node_B); + connect_nodes(node_B, node_C); + + double result_rate = node_A->get_input_samp_rate(); + BOOST_CHECK_EQUAL(result_rate, test_rate); +} + +BOOST_AUTO_TEST_CASE(test_tree_downstream_search) +{ + const double test_rate = 0.25; + MAKE_RATE_NODE(node_A); + MAKE_NODE(node_B0); + MAKE_RATE_SETTING_NODE(node_B1, test_rate); + MAKE_RATE_SETTING_NODE(node_C0, test_rate); + MAKE_RATE_SETTING_NODE(node_C1, rate_node_ctrl::RATE_UNDEFINED); + + // Tree: Downstream of our first node are 3 rate setting blocks. + // Two set the same rate, the third does not care. + connect_nodes(node_A, node_B0); + connect_nodes(node_A, node_B1); + connect_nodes(node_B0, node_C0); + connect_nodes(node_B0, node_C1); + + double result_rate = node_A->get_input_samp_rate(); + BOOST_CHECK_EQUAL(result_rate, test_rate); +} + +BOOST_AUTO_TEST_CASE(test_tree_downstream_search_throw) +{ + const double test_rate = 0.25; + MAKE_RATE_NODE(node_A); + MAKE_NODE(node_B0); + MAKE_RATE_SETTING_NODE(node_B1, test_rate); + MAKE_RATE_SETTING_NODE(node_C0, test_rate); + MAKE_RATE_SETTING_NODE(node_C1, test_rate * 2); + + // Tree: Downstream of our first node are 3 rate setting blocks. + // Two set the same rate, the third has a different rate. + // This will cause a throw. + connect_nodes(node_A, node_B0); + connect_nodes(node_A, node_B1); + connect_nodes(node_B0, node_C0); + connect_nodes(node_B0, node_C1); + + BOOST_CHECK_THROW(node_A->get_input_samp_rate(), uhd::runtime_error); +} + +BOOST_AUTO_TEST_CASE(test_skip_upstream_search) +{ + const double test_rate = 0.25; + MAKE_RATE_SETTING_NODE(node_A, test_rate); + MAKE_NODE(node_B); + MAKE_RATE_NODE(node_C); + + // Slightly more elaborate: Add another block in between that has no + // clue about rates + connect_nodes(node_A, node_B); + connect_nodes(node_B, node_C); + + double result_rate = node_C->get_output_samp_rate(); + BOOST_CHECK_EQUAL(result_rate, test_rate); +} + diff --git a/host/tests/stream_sig_test.cpp b/host/tests/stream_sig_test.cpp new file mode 100644 index 000000000..904a14053 --- /dev/null +++ b/host/tests/stream_sig_test.cpp @@ -0,0 +1,82 @@ +// +// 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 <iostream> +#include <boost/test/unit_test.hpp> +#include <uhd/exception.hpp> +#include <uhd/rfnoc/stream_sig.hpp> + +using namespace uhd::rfnoc; + +BOOST_AUTO_TEST_CASE(test_stream_sig) { + stream_sig_t stream_sig; + + BOOST_CHECK_EQUAL(stream_sig.item_type, ""); + BOOST_CHECK_EQUAL(stream_sig.vlen, 0); + BOOST_CHECK_EQUAL(stream_sig.packet_size, 0); + BOOST_CHECK_EQUAL(stream_sig.is_bursty, false); + + std::stringstream ss; + ss << stream_sig; + // Eventually actually test the contents + std::cout << ss.str() << std::endl; +} + +BOOST_AUTO_TEST_CASE(test_stream_sig_compat) { + stream_sig_t upstream_sig; + stream_sig_t downstream_sig; + + BOOST_CHECK(stream_sig_t::is_compatible(upstream_sig, downstream_sig)); + upstream_sig.vlen = 32; + BOOST_CHECK(stream_sig_t::is_compatible(upstream_sig, downstream_sig)); + downstream_sig.vlen = 32; + BOOST_CHECK(stream_sig_t::is_compatible(upstream_sig, downstream_sig)); + upstream_sig.vlen = 16; + BOOST_CHECK(not stream_sig_t::is_compatible(upstream_sig, downstream_sig)); + upstream_sig.vlen = 32; + BOOST_CHECK(stream_sig_t::is_compatible(upstream_sig, downstream_sig)); + upstream_sig.packet_size = 8; + BOOST_CHECK(stream_sig_t::is_compatible(upstream_sig, downstream_sig)); + downstream_sig.packet_size = 12; + BOOST_CHECK(not stream_sig_t::is_compatible(upstream_sig, downstream_sig)); + upstream_sig.packet_size = 0; + BOOST_CHECK(stream_sig_t::is_compatible(upstream_sig, downstream_sig)); + downstream_sig.item_type = ""; + BOOST_CHECK(stream_sig_t::is_compatible(upstream_sig, downstream_sig)); + upstream_sig.item_type = "sc16"; + downstream_sig.item_type = "s8"; + BOOST_CHECK(not stream_sig_t::is_compatible(upstream_sig, downstream_sig)); +} + +BOOST_AUTO_TEST_CASE(test_stream_sig_types) { + stream_sig_t stream_sig; + BOOST_CHECK_EQUAL(stream_sig.get_bytes_per_item(), 0); + stream_sig.item_type = "sc16"; + BOOST_CHECK_EQUAL(stream_sig.get_bytes_per_item(), 4); + stream_sig.item_type = "sc12"; + BOOST_CHECK_EQUAL(stream_sig.get_bytes_per_item(), 3); + stream_sig.item_type = "sc8"; + BOOST_CHECK_EQUAL(stream_sig.get_bytes_per_item(), 2); + stream_sig.item_type = "s16"; + BOOST_CHECK_EQUAL(stream_sig.get_bytes_per_item(), 2); + stream_sig.item_type = "s8"; + BOOST_CHECK_EQUAL(stream_sig.get_bytes_per_item(), 1); + stream_sig.item_type = "fc32"; + BOOST_CHECK_EQUAL(stream_sig.get_bytes_per_item(), 8); + stream_sig.item_type = "not_a_type"; + BOOST_REQUIRE_THROW(stream_sig.get_bytes_per_item(), uhd::key_error); +} diff --git a/host/tests/tick_node_test.cpp b/host/tests/tick_node_test.cpp new file mode 100644 index 000000000..fc1b8c544 --- /dev/null +++ b/host/tests/tick_node_test.cpp @@ -0,0 +1,110 @@ +// +// 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 "graph.hpp" +#include <uhd/rfnoc/tick_node_ctrl.hpp> +#include <boost/test/unit_test.hpp> +#include <iostream> + +using namespace uhd::rfnoc; + +// test class derived, knows about rates +class tick_aware_node : public test_node, public tick_node_ctrl +{ +public: + typedef boost::shared_ptr<tick_aware_node> sptr; + + tick_aware_node(const std::string &test_id) : test_node(test_id) {}; + +}; /* class tick_aware_node */ + +// test class derived, sets rates +class tick_setting_node : public test_node, public tick_node_ctrl +{ +public: + typedef boost::shared_ptr<tick_setting_node> sptr; + + tick_setting_node(const std::string &test_id, double tick_rate) : test_node(test_id), _tick_rate(tick_rate) {}; + +protected: + double _get_tick_rate() { return _tick_rate; }; + +private: + const double _tick_rate; + +}; /* class tick_setting_node */ + +#define MAKE_TICK_NODE(name) tick_aware_node::sptr name(new tick_aware_node(#name)); +#define MAKE_TICK_SETTING_NODE(name, rate) tick_setting_node::sptr name(new tick_setting_node(#name, rate)); + +BOOST_AUTO_TEST_CASE(test_simplest_downstream_search) +{ + const double test_rate = 0.25; + MAKE_TICK_NODE(node_A); + MAKE_TICK_SETTING_NODE(node_B, test_rate); + + // Simplest possible scenario: Connect B downstream of A and let + // it find B + connect_nodes(node_A, node_B); + + double result_rate = node_A->get_tick_rate(); + BOOST_CHECK_EQUAL(result_rate, test_rate); +} + +BOOST_AUTO_TEST_CASE(test_both_ways_search) +{ + const double test_rate = 0.25; + MAKE_TICK_SETTING_NODE(node_A, tick_node_ctrl::RATE_UNDEFINED); + MAKE_TICK_NODE(node_B); + MAKE_TICK_SETTING_NODE(node_C, test_rate); + + std::cout << "a->b" << std::endl; + connect_nodes(node_A, node_B); + std::cout << "b->a" << std::endl; + connect_nodes(node_B, node_C); + + std::cout << "search" << std::endl; + double result_rate = node_B->get_tick_rate(); + BOOST_CHECK_EQUAL(result_rate, test_rate); +} + +BOOST_AUTO_TEST_CASE(test_both_ways_search_reversed) +{ + const double test_rate = 0.25; + MAKE_TICK_SETTING_NODE(node_A, test_rate); + MAKE_TICK_NODE(node_B); + MAKE_TICK_SETTING_NODE(node_C, tick_node_ctrl::RATE_UNDEFINED); + + connect_nodes(node_A, node_B); + connect_nodes(node_B, node_C); + + double result_rate = node_B->get_tick_rate(); + BOOST_CHECK_EQUAL(result_rate, test_rate); +} + +BOOST_AUTO_TEST_CASE(test_both_ways_search_fail) +{ + const double test_rate = 0.25; + MAKE_TICK_SETTING_NODE(node_A, test_rate); + MAKE_TICK_NODE(node_B); + MAKE_TICK_SETTING_NODE(node_C, 2 * test_rate); + + connect_nodes(node_A, node_B); + connect_nodes(node_B, node_C); + + BOOST_CHECK_THROW(node_B->get_tick_rate(), uhd::runtime_error); +} diff --git a/host/utils/uhd_usrp_probe.cpp b/host/utils/uhd_usrp_probe.cpp index b19a59a8e..49e00d53a 100644 --- a/host/utils/uhd_usrp_probe.cpp +++ b/host/utils/uhd_usrp_probe.cpp @@ -18,6 +18,7 @@ #include <uhd/utils/safe_main.hpp> #include <uhd/version.hpp> #include <uhd/device.hpp> +#include <uhd/device3.hpp> #include <uhd/types/ranges.hpp> #include <uhd/property_tree.hpp> #include <boost/algorithm/string.hpp> //for split @@ -53,7 +54,7 @@ static std::string make_border(const std::string &text){ static std::string get_dsp_pp_string(const std::string &type, property_tree::sptr tree, const fs_path &path){ std::stringstream ss; ss << boost::format("%s DSP: %s") % type % path.leaf() << std::endl; - //ss << std::endl; + ss << std::endl; meta_range_t freq_range = tree->access<meta_range_t>(path / "freq/range").get(); ss << boost::format("Freq range: %.3f to %.3f MHz") % (freq_range.start()/1e6) % (freq_range.stop()/1e6) << std::endl;; return ss.str(); @@ -135,6 +136,16 @@ static std::string get_dboard_pp_string(const std::string &type, property_tree:: return ss.str(); } + +static std::string get_rfnoc_pp_string(property_tree::sptr tree, const fs_path &path){ + std::stringstream ss; + ss << "RFNoC blocks on this device:" << std::endl << std::endl; + BOOST_FOREACH(const std::string &name, tree->list(path)){ + ss << "* " << name << std::endl; + } + return ss.str(); +} + static std::string get_mboard_pp_string(property_tree::sptr tree, const fs_path &path){ std::stringstream ss; ss << boost::format("Mboard: %s") % (tree->access<std::string>(path / "name").get()) << std::endl; @@ -149,6 +160,9 @@ static std::string get_mboard_pp_string(property_tree::sptr tree, const fs_path if (tree->exists(path / "fpga_version")){ ss << "FPGA Version: " << tree->access<std::string>(path / "fpga_version").get() << std::endl; } + if (tree->exists(path / "xbar")){ + ss << "RFNoC capable: Yes" << std::endl; + } ss << std::endl; try { if (tree->exists(path / "time_source" / "options")){ @@ -160,18 +174,23 @@ static std::string get_mboard_pp_string(property_tree::sptr tree, const fs_path ss << "Clock sources: " << prop_names_to_pp_string(clock_sources) << std::endl; } ss << "Sensors: " << prop_names_to_pp_string(tree->list(path / "sensors")) << std::endl; - BOOST_FOREACH(const std::string &name, tree->list(path / "rx_dsps")){ - ss << make_border(get_dsp_pp_string("RX", tree, path / "rx_dsps" / name)); + if (tree->exists(path / "rx_dsps")){ + BOOST_FOREACH(const std::string &name, tree->list(path / "rx_dsps")){ + ss << make_border(get_dsp_pp_string("RX", tree, path / "rx_dsps" / name)); + } } BOOST_FOREACH(const std::string &name, tree->list(path / "dboards")){ ss << make_border(get_dboard_pp_string("RX", tree, path / "dboards" / name)); } - BOOST_FOREACH(const std::string &name, tree->list(path / "tx_dsps")){ - ss << make_border(get_dsp_pp_string("TX", tree, path / "tx_dsps" / name)); + if (tree->exists(path / "tx_dsps")){ + BOOST_FOREACH(const std::string &name, tree->list(path / "tx_dsps")){ + ss << make_border(get_dsp_pp_string("TX", tree, path / "tx_dsps" / name)); + } } BOOST_FOREACH(const std::string &name, tree->list(path / "dboards")){ ss << make_border(get_dboard_pp_string("TX", tree, path / "dboards" / name)); } + ss << make_border(get_rfnoc_pp_string(tree, path / "xbar")); } catch (const uhd::lookup_error&) { /* nop */ |