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